using System; using System.Collections.Generic; using System.Linq; using System.Text; using ChamChat.Models; namespace ChamChat { public class Watlow988U_Task : Task { // Each step is separately programmed to the Watlow988U. public Watlow988U_Task(Client Client, Profile Profile) : base(Client, Profile) { _Profile = Profile; _Client = Client; _SerSteps = _Profile.SerializedStepList; // Limit tA is overall limit _tAProfileLimit = 0; foreach (Step s in _SerSteps) if (s.LimitT < _tAProfileLimit) _tAProfileLimit = s.LimitT; // Limit tB is overall limit _tBProfileLimit = 60; foreach (Step s in _SerSteps) if (s.LimitT > _tBProfileLimit) _tBProfileLimit = s.LimitT; } private DateTime _dtIdlingStart = DateTime.Now.AddYears(10); private const Int32 PollingIntervalSeconds = 10; private const Int32 maxIdlingTimeSec = 60; private List _SerSteps; private double _tAProfileLimit = 0; private double _tBProfileLimit = 0; public override Boolean IsControlledProgrammable(Profile profile) { // Clear existing ClientMessages _Client.ClientMessages = new List(); _NewProfileDuration = 0; _ProfileNumOfSerialSteps = 1; List profiles = new List(); int k = 0; while (k < _SerSteps.Count) { Profile p = GetProfile(k); profiles.Add(p); k += _ProfileNumOfSerialSteps; } foreach (Profile p in profiles) if (!_Client.IsProgrammable(p)) return false; // If subsequent tA resp. tB steps differ more than 50 °C, overheat/overcool alarm will sound immediately. // Limits can be set to 50 °C from temperature. List tASteps = new List(); List tBSteps = new List(); // Create list of all tA steps resp. tB steps foreach (Step s in _SerSteps) if (s.T > 25) tBSteps.Add(s); else tASteps.Add(s); // Compare subsequent tA steps for (int i = 1; i < tASteps.Count; i++) { double t1 = tASteps[i - 1].T; double t2 = tASteps[i].T; if (Math.Abs(t1 - t2) > 50) { string msg = string.Format("stepping from {0:0} to {1:0} will cause overcool alarm", t1, t2); _Client.ClientMessages.Add(new ClientMessage("Program cannot be written to client: " + msg)); return false; } } // Compare subsequent tB steps for (int i = 1; i < tBSteps.Count; i++) { double t1 = tBSteps[i - 1].T; double t2 = tBSteps[i].T; if (Math.Abs(t1 - t2) > 50) { string msg = string.Format("stepping from {0:0} to {1:0} will cause overheat alarm", t1, t2); _Client.ClientMessages.Add(new ClientMessage("Program cannot be written to client: " + msg)); return false; } } return true; } protected override void ThreadStandAlone() { // Write profile try { if (!_Client.WriteProfile(_Profile)) { _Status = TaskStatus.Stopped; return; } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client WriteProfile() exception: {0}", ex.Message); return; } // Start client try { if (!_Client.Start()) { _Status = TaskStatus.Stopped; return; } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client Start() exception: {0}", ex.Message); return; } DateTime lastPolled = DateTime.Now; while (_Status == TaskStatus.IsRunning) { // Poll only every 'PollingIntervalSeconds' seconds if (DateTime.Now > lastPolled.AddSeconds(PollingIntervalSeconds)) { // Is finished? try { if (_Client.IsFinished()) { _Status = TaskStatus.Finished; continue; } } catch { } try { _TaskProgress = _Client.Progress(); } catch { } lastPolled = DateTime.Now; } System.Threading.Thread.Sleep(100); if (_Status == TaskStatus.AbortRequested) { try { if (_Client.Stop()) _Status = TaskStatus.Aborted; } catch { } } _isKicking = true; } _isKicking = false; } private Double _NewProfileDuration = 0; // Duration of new profile in minutes private int _ProfileNumOfSerialSteps = 1; // Number of steps from serial step list performed in new profile. protected override void ThreadControlled() { Int32 iCurrentSerialStep = -1; DateTime startOfCurrentProgram = new DateTime(1900, 1, 1); DateTime endOfCurrentProgram = new DateTime(1900, 1, 1); Step currentStep = _SerSteps[0]; DateTime lastPolled = DateTime.Now; _NewProfileDuration = 0; _ProfileNumOfSerialSteps = 1; Profile newProfile; while (_Status == TaskStatus.IsRunning) { if (DateTime.Now > endOfCurrentProgram) { iCurrentSerialStep += _ProfileNumOfSerialSteps; // Is starting step of newProfile to be executed // Last step has been performed? if (iCurrentSerialStep > _SerSteps.Count - 1) { _Client.Stop(); _Status = TaskStatus.Finished; AddEventLog("Last step performed"); continue; } newProfile = new Profile(); try { //AddEventLog("Debugging: Getting profile for iCurrentSerialStep=" + iCurrentSerialStep.ToString()); newProfile = GetProfile(iCurrentSerialStep); //AddEventLog("Debugging: Profile for iCurrentSerialStep: " + newProfile.ToString()); } catch (Exception ex) { AddEventLog("GetProfile() error: " + ex.Message); } RestartClient(newProfile); // try-catch in RestartClient() for every action if (_Status != TaskStatus.IsRunning) continue; // Update variables startOfCurrentProgram = DateTime.Now; endOfCurrentProgram = DateTime.Now.AddMinutes(_NewProfileDuration); lastPolled = DateTime.Now; // Update events try { Step s1 = newProfile.Steps[0]; Step s2 = newProfile.Steps[1]; //AddEventLog("Profile in client: " + newProfile.ToString()); if (newProfile.Loops.Count == 1) AddEventLog(string.Format("Set conditions: {0:0}h{1:00} @ {2:0} °C, {3:0}h{4:00} @ {5:0} °C, {6:0} cycles", s1.Hours, s1.Minutes, s1.T, s2.Hours, s2.Minutes, s2.T, newProfile.Loops[0].N)); else AddEventLog(string.Format("Set conditions: {0:0}h{1:00} @ {2:0} °C, {3:0}h{4:00} @ {5:0} °C", s1.Hours, s1.Minutes, s1.T, s2.Hours, s2.Minutes, s2.T)); string s = string.Format("Partial profile end is {0:00}-{1:00} @ {2:00}:{3:00}:{4:00}", endOfCurrentProgram.Day, endOfCurrentProgram.Month, endOfCurrentProgram.Hour, endOfCurrentProgram.Minute, endOfCurrentProgram.Second); AddEventLog(s); } catch (Exception ex) { AddEventLog("Update events: " + ex.Message); } } // Poll only every 'PollingIntervalSeconds' seconds if (DateTime.Now > lastPolled.AddSeconds(PollingIntervalSeconds)) { // Is finished? try { if (_Client.IsFinished()) { AddEventLog("Client finished, but task did not!"); if (_dtIdlingStart < DateTime.Now.AddSeconds(-maxIdlingTimeSec)) { AddEventLog("Debugging: _dtIdlingStart < DateTime.Now.AddSeconds(-maxIdlingTimeSec)"); // _Status = TaskStatus.Interrupted; // Profile did not finish, client did //continue; } else { _dtIdlingStart = DateTime.Now; AddEventLog("Debugging: _dtIdlingStart set to "+_dtIdlingStart.ToString()); } } else _dtIdlingStart = DateTime.Now.AddYears(10); } catch { _dtIdlingStart = DateTime.Now.AddYears(10); } // Always continue and keep trying #region Taskprogress try { double taskRunTime = (DateTime.Now - _startThread).TotalMinutes; double pTest = Math.Min(100, Math.Floor(taskRunTime / _Profile.DurationMin * 100)); double taskTimeRemain = _Profile.DurationMin - taskRunTime; double taskTimeRemainHrs = Math.Floor(taskTimeRemain / 60); double taskTimeRemainMin = Math.Floor(taskTimeRemain - 60 * taskTimeRemainHrs); double newProfileRuntime = (DateTime.Now - startOfCurrentProgram).TotalMinutes; double pNewProfile = Math.Min(100, Math.Floor(newProfileRuntime / _NewProfileDuration * 100)); double profileTimeExecutedSoFar = newProfileRuntime; for (int i = 0; i < iCurrentSerialStep; i++) profileTimeExecutedSoFar += _SerSteps[i].DurationMin; int serialStepNo = iCurrentSerialStep; double min = 0; for (int i = 0; i < _SerSteps.Count; i++) { min += _SerSteps[i].DurationMin; if (min > profileTimeExecutedSoFar) { serialStepNo = i; break; } } _TaskProgress = String.Format("Step #{0:00}/{1:00} progress {2:0}%, test progress {3:0}%, remaining test time {4:0}h{5:00}m", serialStepNo + 1, _Profile.SerializedStepList.Count, pNewProfile, pTest, taskTimeRemainHrs, taskTimeRemainMin); if (serialStepNo + 1 == _Profile.SerializedStepList.Count && pTest > 99.99) _TaskProgress = "Profile executed"; } catch { _TaskProgress = "n/a"; } #endregion Taskprogress lastPolled = DateTime.Now; } System.Threading.Thread.Sleep(200); _isKicking = true; } if (_Status == TaskStatus.AbortRequested) { try { if (_Client.Stop()) _Status = TaskStatus.Aborted; } catch { } } _isKicking = false; } private void RestartClient(Profile newProfile) { // Stop client try { AddEventLog("Info: Stopping client"); if (!_Client.Stop()) { _Status = TaskStatus.Interrupted; AddEventLog("Task interrupted while trying to stop client!"); return; } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client Stop() exception: {0}", ex.Message); AddEventLog(_TaskProgress); return; } // Wait to finish try { Boolean timedOut = true; DateTime startWait = DateTime.Now; while (DateTime.Now < startWait.AddSeconds(60)) { System.Threading.Thread.Sleep(1000); if (_Client.IsFinished()) { timedOut = false; break; } } if (timedOut) { _Status = TaskStatus.Interrupted; AddEventLog("Time out (60 s) in RestartClient.WaitForFinish"); return; } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client WaitForFinish exception: {0}", ex.Message); AddEventLog(_TaskProgress); return; } AddEventLog("Info: Client stopped"); // Write profile try { AddEventLog("Info: Writing to client"); if (!_Client.WriteProfile(newProfile)) { _Status = TaskStatus.Interrupted; AddEventLog("Task interrupted while trying to write new profile to client!"); return; } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client WriteProfile() exception: {0}", ex.Message); AddEventLog(_TaskProgress); return; } AddEventLog("Info: Profile written to client"); // Start client try { AddEventLog("Info: Starting client"); if (!_Client.Start()) { // Try to start again after 10 sec AddEventLog("First attempt to start client failed!"); System.Threading.Thread.Sleep(10000); if (!_Client.Start()) { _Status = TaskStatus.Interrupted; AddEventLog("Task interrupted while trying to start client!"); return; } } } catch (Exception ex) { _Status = TaskStatus.ExceptionOccured; _TaskProgress = string.Format("Client Start() exception: {0}", ex.Message); AddEventLog(_TaskProgress); return; } AddEventLog("Info: Client started"); } private Profile GetProfile(int iStep) { Step nextStep = null; if (iStep + 1 < _SerSteps.Count) nextStep = _SerSteps[iStep + 1].DeepClone(); // Last tA set to client double tALastSet = 0; for (int i = 0; i < iStep; i++) if (_SerSteps[i].T < 40) tALastSet = _SerSteps[i].T; double tAsetting = Math.Min(0, tALastSet + 40); // As high as possible without exceeding limits of prev step // Last tB set to client double tBLastSet = 0; for (int i = 0; i < iStep; i++) if (_SerSteps[i].T > 40) tBLastSet = _SerSteps[i].T; double tBsetting = Math.Max(60, tBLastSet - 40); // As low as possible without exceeding limits of prev step // Create standard steps with 1 min duration Step stdA = new Step(1); stdA.T = tAsetting; stdA.LimitT = tAsetting - 50; // Reset later stdA.PreTemp = tAsetting; Step stdB = new Step(1); stdB.T = tBsetting; stdB.LimitT = tBsetting + 50; // Reset later stdB.PreTemp = tBsetting; Step step1 = _SerSteps[iStep].DeepClone(); Step step2 = new Step(15); // Programmed step is minimum 10 minutes, maxmimum 1.5 times the required step duration. // If multiple steps can be performed without restart, the duration is altered below. step1.DurationMin = Math.Max(10, Math.Round(1.5 * step1.DurationMin)); // Set time of restart to end of current step. // If multiple steps can be performed without restart, the duration is altered below. _NewProfileDuration = _SerSteps[iStep].DurationMin; _ProfileNumOfSerialSteps = 1; // Remark: Area to start in is defined by client based on step order. if (step1.T > 40) { // Current step is tB, step 2 programmed to client must be tA step2 = stdA; if (nextStep != null) if (nextStep.T < 40) { // If next step is tA, then program it as is. step2 = nextStep.DeepClone(); //// The two steps can be run by client without restarting. //// If no restarting takes place, then the next opposite step is not being prepared. The previous retains. //step2.DurationMin = Math.Max(30, Math.Round(1.5 * step2.DurationMin)); //step1.DurationMin = _SerSteps[iStep].DurationMin; //_NewProfileDuration = step1.DurationMin + step2.DurationMin; //_ProfileNumOfSerialSteps = 2; } } else { // Current step is tA, step 2 programmed to client must be tB step2 = stdB; if (nextStep != null) if (nextStep.T > 40) { // If next step is tB, then program it as is. step2 = nextStep.DeepClone(); //// The two steps can be run by client without restarting. //// If no restarting takes place, then the next opposite step is not being prepared. The previous retains. //step2.DurationMin = Math.Max(30, Math.Round(1.5 * step2.DurationMin)); //step1.DurationMin = _SerSteps[iStep].DurationMin; //_NewProfileDuration = step1.DurationMin + step2.DurationMin; //_ProfileNumOfSerialSteps = 2; } } // Set PK step1.PK = _SerSteps[iStep].PK; // Set for Loop.ToString() step2.PK = _SerSteps[iStep].PK + 1; // If current step is start of a loop comprising 2 opposite steps, then create loop in newProfile and set end time of newProfile List loops = new List(); int N = 0; foreach (Loop loop in _Profile.Loops) { if (loop.First == _SerSteps[iStep].PK && loop.Last == _SerSteps[iStep].PK + 1) { if ((step1.T < 40 && step2.T > 40) || (step1.T > 40 && step2.T < 40)) { // Steps are in different chambers of client. N = loop.N; break; } } } if (N != 0) { loops.Add(new Loop(N, 0, 1)); // Loop is always from 1st to 2nd step step1.DurationMin = _SerSteps[iStep].DurationMin; step2.DurationMin = nextStep.DurationMin; _NewProfileDuration = N * (step1.DurationMin + step2.DurationMin); _ProfileNumOfSerialSteps = 2 * N; } // Set limits to max, but do not exceed profile limits if (step1.T < 40) { step1.LimitT = Math.Max(step1.T - 50, _tAProfileLimit); step2.LimitT = Math.Min(step2.T + 50, _tBProfileLimit); } else { step1.LimitT = Math.Min(step1.T + 50, _tBProfileLimit); step2.LimitT = Math.Max(step2.T - 50, _tAProfileLimit); } List steps = new List(); steps.Add(step1); steps.Add(step2); List options = new List(); options.Add(ProfileOption.TSx_EndModeOff); // Shut down client // NOTE: It is impossible to wait for setup, unless endofCurrentProgram is moved forwards. options.Add(ProfileOption.TSx_StartNoSetup); // Do not wait for setup //if (iStep == 0 && _Profile.Options.Contains(ProfileOption.TSE11A_StartSetupTest)) // options.Add(ProfileOption.TSE11A_StartSetupTest); // Wait for setup in first step //else // options.Add(ProfileOption.TSE11A_StartNoSetup); // Do not wait for setup Profile newProfile = new Profile(_Profile.Title, steps, loops, options); return newProfile; } } }