using LaaPackages.Features.Cordonel.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows.Threading; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Production.ProductionUiCordonel.Properties; using Timer = System.Threading.Timer; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { /// /// Implementation for all methods needed to drive the state machine of the process controller /// public class ProcessController { #region ------------------------------------------ Variables -------------------------------------------------- // actual genesis meter private IGenesisMeter _genesisMeter; // Cordonel requirements for production private CordonelRequirements _cordonelRequirements; // EOL progress states for production private EOLProgressModel _cordonelEolProgress; // last pcbId for check of changed genesisMeter private String _lastPcbId; // batch just used for setup, as only one meter will be assigned private MeterBatch _meterBatch; // state of the state machine private ProcessState _stateMachineState; // locker to avoid repeated state execution private ProcessState _lockedStateMachineState; // sender of state change event to evaluate in e.g. error process private IProductionProcess _stateChangeRequestProcess; // List of production processes public List Processes { get; private set; } // Container for production process states private IProcessStateDef _processStateDef; // Container for production status information, a list is used to keep the content over all classes of production // processes as a reference seems not to return their value (always reset to the initial value) private GenesisStatus _productionStatus; // register restorer private RegisterRestorer _registerRestorer; // sensus radio encryption key private List _radioEncryptionKey; // Password container with all information, a list is used to keep the content over all classes of production // processes as a reference seems not to return their value (always reset to the initial value) private MeterPwdDb _pwdContainer; // reminder for slot private readonly Int32 _slot; /// /// Change the overview table of the GUI to the state of the individual process /// public event EventHandler OnProcessStateChanged; /// /// The overall process chain changed needed to signal to the GUI /// - Can be forced by the state machine to invoke, /// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next /// required sate after break, error or completion to change the state of the state machine. /// public event EventHandler OnStateMachineStateChanged; /// /// Request for logging to GUI output and log-file /// public event EventHandler OnProcessLogRequest; /// /// Keep the progress bars of the GUI up to date /// public event EventHandler OnProcessProgressChanged; /// /// Dispatch a message to the GUI /// public event EventHandler OnGuiMessageDispatcher; // cancellation token private CancellationTokenSource _cancellationTokenSource; private CancellationToken _cancellationToken; // cyclic timer for state machine call private Timer _processCtrlTimer; private const Int32 ProcessTimerCycleMs = 250; // Delay timer between finish exec-parametrization and 'StoreConfiguration' as 'PERIODICLOG' doesn't // have 'StoreConfiguration' and needs at least 20 s to update contents before 'StoreConfiguration' // can be dispatched to 'SENSUSRADIO'. private DispatcherTimer _paramExeFinishedToStoreConfigDelayTimer; // Cycle time in milliseconds private const Int32 ParamExeFinishedToStoreConfigDelayCycleMs = 1000; // Time to keep in seconds private const Int32 ParamExeFinishedToStoreConfigTimeS = 22; private Boolean _paramExeFinishedToStoreConfigDelayTimerExceeded; private DateTimeOffset _paramExeFinishedToStoreConfigDelayTimerStartTime; /// /// Avoid waiting for a single process completion. /// public const IProductionProcess DoNotWaitForSingleProcessCompletion = null; /// /// Kick off a parallel execution of the state machine. /// public const Boolean ExecuteInParallel = true; #endregion --------------------------------------- Variables -------------------------------------------------- #region ------------------------------------------ Class ------------------------------------------------------ /// /// Ctor: /// - Setup process timer for cyclic call of state machine, /// - Create and setup Cordonel. /// /// physical slot for this meter /// /// /// - Initial /// /// /// - Always set to production mode to load the registers for this FW and not the latest, /// - Always RegisterWatchService on, /// - Always MinMaxCHeck on. /// /// /// - _paramExeFinishedToStoreConfigurationDelayTimer. /// public ProcessController(Int32 slot, IProcessStateDef processSateDef) { // Timer for cyclic call of state machine _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); // Delay timer between final-param-exec finished and start of 'StoreConfiguration' _paramExeFinishedToStoreConfigDelayTimer = new DispatcherTimer(); _paramExeFinishedToStoreConfigDelayTimer.Tick += TmrParamExeFinishedToStoreConfigCycle_Tick; _paramExeFinishedToStoreConfigDelayTimer.Interval = TimeSpan.FromMilliseconds(ParamExeFinishedToStoreConfigDelayCycleMs); _slot = slot; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; _processStateDef = processSateDef; InitNewMeter(); InitProcesses(); // program start with idle process _stateMachineState = ProcessState.Idle; } /// /// Initialization of new meter /// /// /// - Initial /// /// /// - Initialize processes to build a list of all production processes. /// /// /// - Removed list of failed production processes /// private void InitNewMeter() { _genesisMeter = new GenesisMeter(); _genesisMeter.SetupFromConfigFile(_slot); // Important setup to overwrite setting from "Setup" in GTB _genesisMeter.Configuration.ProductionMode = true; _genesisMeter.Configuration.UseRegisterWatchService = true; _genesisMeter.Configuration.UseMinMaxCheck = true; // Meter batch even if only one meter in use as the meter batch routines can be used _meterBatch = new MeterBatch(); _meterBatch.AddMeter(_genesisMeter); // Set lock process to unreachable state delimiter to force initial entry of state machine _lockedStateMachineState = ProcessState.StateListDelimiter; // Create a register restorer _registerRestorer = new RegisterRestorer(_genesisMeter); _radioEncryptionKey = new List(); // Containers for production status and standard or special requirement for production _productionStatus = new GenesisStatus(); _cordonelRequirements = new CordonelRequirements(); // Password container as reminder for repeated test on identical device _pwdContainer = new MeterPwdDb(); // EOL progress to feed the sentinel _cordonelEolProgress = new EOLProgressModel(); } /// /// Removal of old meter /// private void RemoveMeter() { _genesisMeter?.ClearPassword(); // meter batch dispose disposes all IMeters _meterBatch?.Dispose(); _meterBatch = null; _genesisMeter = null; _registerRestorer = null; _radioEncryptionKey = null; _productionStatus = null; _cordonelRequirements = null; _pwdContainer = null; _cordonelEolProgress = null; } /// /// Dispose. /// /// /// - Initial /// public void Dispose() { _processCtrlTimer?.Dispose(); _processCtrlTimer = null; _paramExeFinishedToStoreConfigDelayTimer?.Stop(); _paramExeFinishedToStoreConfigDelayTimer = null; _processStateDef = null; KillProcesses(); RemoveMeter(); } #endregion --------------------------------------- Class ------------------------------------------------------ #region ------------------------------------------ Events ----------------------------------------------------- /// /// Single process state or process list changed event, /// Use event to force special procedure like uninstalling events /// /// /// /// /// - Initial /// /// /// - Remove process events if process is finished. /// /// /// - Remove process events if process is finished or skipped. /// /// /// - Init and start the delay timer between finish exec-parametrization and 'StoreConfiguration /// to all apps as 'PERIODICLOG' doesn't have 'StoreConfiguration' and needs at least 20 s to /// update contents before 'SENSUSRADIO' tries to synchronize all settings. /// private void ProcessStateChanged_Handler(Object sender, SingleProcessStateArgs e) { IProductionProcess pp = null; if (sender is IProductionProcess process) { pp = process; } // uninstall events switch (e.SingleProcessState) { // after execution independent of the result, the events coming from the process // are needed to be removed case SingleProcessState.Succeeded: case SingleProcessState.Skipped: case SingleProcessState.Abort: case SingleProcessState.Error: RemoveProcessEvents(pp); break; case SingleProcessState.Retry: case SingleProcessState.Waiting: case SingleProcessState.Running: break; } // Delay timer init and start if parametrization exec finished but only for 'EMEA' region if (pp != null && pp.ProcessName != null && pp.Meter != null && pp.Meter.Region.Equals("EMEA") && pp.ProcessName.Equals(Resources.StrStateFinalParametrization) && e.SingleProcessState == SingleProcessState.Succeeded) { var teaTime = new TimeT(); _paramExeFinishedToStoreConfigDelayTimerExceeded = false; _paramExeFinishedToStoreConfigDelayTimerStartTime = teaTime.DateTimeUtc; _paramExeFinishedToStoreConfigDelayTimer.Start(); var msg = $"{Resources.StrMsgRebootDelayTimerStarted} {ParamExeFinishedToStoreConfigTimeS} s" + $" - {teaTime}"; OnProcessLogRequest?.Invoke(sender, new ProcessLogArgs(msg)); } // signal state change to GUI OnProcessStateChanged?.Invoke(sender, e); } private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e) { OnProcessProgressChanged?.Invoke(sender, e); } private void GuiMessageDispatcher_Handler(Object sender, GuiMessageArgs e) { OnGuiMessageDispatcher?.Invoke(sender, e); } private void ProcessLog_Handler(Object sender, ProcessLogArgs e) { OnProcessLogRequest?.Invoke(sender, e); } /// /// Handler to set a new state for the state machine regarding the entire final-process /// /// /// /// /// - Initial /// private void ForceStateMachineStateChange_Handler(Object sender, ProcessStateArgs e) { // remind caller of the state change request to act on e.g. error processing _stateChangeRequestProcess = (IProductionProcess)sender; // received new status from external if (e.State != null) { _stateMachineState = (ProcessState)e.State; } } /// /// Process timer /// /// /// /// - Initial /// private void TmrProcessCtrlCycle_Elapsed(Object state) { StateMachine(); } /// /// Delay timer between parametrization exec finished and store config to support the 'PERIODICLOG' update /// which needs at least 20 s after writing to update the internal contents. Not holding this time will cause /// to take the value before the writing access as 'SENSUSRADIO' will synchronize all parameters if it receives /// the 'StoreConfiguration' command. /// /// /// /// /// - Initial /// private void TmrParamExeFinishedToStoreConfigCycle_Tick(Object state, System.EventArgs e) { var teaTime = new TimeT(); var timeSpan = teaTime.DateTimeUtc - _paramExeFinishedToStoreConfigDelayTimerStartTime; if (timeSpan.Seconds >= ParamExeFinishedToStoreConfigTimeS) { _paramExeFinishedToStoreConfigDelayTimerExceeded = true; _paramExeFinishedToStoreConfigDelayTimer.Stop(); var msg = $"{Resources.StrMsgRebootDelayTimerExceeded} {ParamExeFinishedToStoreConfigTimeS} s - {teaTime}"; OnProcessLogRequest?.Invoke(this, new ProcessLogArgs(msg)); } } #endregion --------------------------------------- Events ----------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- /// /// State-machine of ProcessController which serves all production processes. /// This-state machine executes all states defined in the "GenericStateMachineDef" and the individual /// production-process specific "XxxStateMachineDef". /// /// A few states with special functions will be caught in advance in this StateMachine /// /// /// - Initial /// /// /// - Generic implementation /// private void StateMachine() {// avoid repeated execution of the state machine on unchanged state if (_stateMachineState == _lockedStateMachineState) { Thread.Sleep(ProcessTimerCycleMs >> 1); } else { // ATTENTION: Has to be the very first step here! // Remind backup state to avoid repeated execution and side effects _lockedStateMachineState = _stateMachineState; try { // Signal overall process progress to GUI based on ready processes CalcAndPushOverAllProgress(); // Signal state change to logger OnStateMachineStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); if (_processStateDef != null) { // Check if state is the first in the list of production processes to reset cancellationToken and // calculate the over all process steps to feed the progress bars. var startProcessState = _processStateDef.GetStateOfFirstProcess(); if (startProcessState == _stateMachineState) { // Kill all processes at start KillProcesses(); // Avoid immediately cancellation for first process of the process chain ResetCancellationToken(); // Set up the counter for maximum overall process progress bar in GUI CalcAndPushMaxOverAllProcessSteps(); } // Get the processStateStruct of this new required stateMachineState var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState); switch (_stateMachineState) { case ProcessState.Idle: break; case ProcessState.CheckNewMeter: // Detection of Cordonel has to be completed before assigning next state. It has to be checked if // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then // enter the "ConnectCordonel"! if (processStateStruct.NextStateOnSuccess != null) { _stateMachineState = CheckForNewMeter() ? (ProcessState)processStateStruct.NextStateOnSuccess : processStateStruct.ErrorExitState; } else { _stateMachineState = ProcessState.Error; } break; case ProcessState.RepeatFailedTests: _stateMachineState = RepeatFailedTests(); break; case ProcessState.AbortTest: AbortProcesses(); _stateMachineState = ProcessState.Idle; break; case ProcessState.Stop: KillProcesses(); _stateMachineState = ProcessState.Idle; break; case ProcessState.Error: // Common error handling routine ProcessErrorHandler(_stateChangeRequestProcess); // Start the error handler with its user feedback request. It has to be started // always even if the abortion of all processes is required! The error handler // will change the exit state based on the user input. StartProcess(processStateStruct); break; case ProcessState.StateListDelimiter: _stateMachineState = ProcessState.Idle; break; default: // This is the call to the processes for a specific test StartProcess(processStateStruct); if (processStateStruct.KickOffParallelState != null) { _stateMachineState = (ProcessState)processStateStruct.KickOffParallelState; } break; } } } catch (ThreadAbortException ex) { _genesisMeter?.WriteLog(ex.Message); _stateMachineState = ProcessState.Idle; } catch (Exception ex) { _genesisMeter?.WriteLog(ex.Message); _stateMachineState = ProcessState.Error; } } }// state locked against repeated execution #endregion --------------------------------------- State Machine ---------------------------------------------- #region ------------------------------------------ Tools ------------------------------------------------------ /// /// Common routine to reset the cancellation token on restart or new meter /// /// /// - Initial /// /// /// - Rest all cancellation tokens in processes. /// private void ResetCancellationToken() { // This state needs an external state change like user input to go ahead // Remove cancellation token if idle reached for clean start if (_cancellationToken.IsCancellationRequested) { // Reset the cancellation request _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; foreach (var pp in Processes) { pp.CancellationToken = _cancellationToken; } } } /// /// Returns the currentGenesis information for display /// /// /// /// - Initial /// public CordonelRequirements GetProductionRequirements() { return _cordonelRequirements; } /// /// Returns the currentGenesis information for display /// /// /// /// - Initial /// public IGenesisMeter GetGenesisMeter() { return _genesisMeter; } /// /// Clears all collected data on new genesisMeter and reminds the detected for next /// detection and run of this comparison. /// /// true for new meter /// /// - Initial /// /// /// - The StartProcessSequence has already removed all processes from list to have a clean start, /// avoid it here again, because the detect is now in the list! /// /// /// - Handle genesis meter is null as new meter. /// /// /// - Check _genesisMeter not null before equality with old pcbid. /// /// /// - ResetCancellationToken. /// /// /// - Remind order number, /// - avoid to kill the order number scan and avoid to reset this production state to idle. /// private Boolean CheckForNewMeter() { if (_genesisMeter == null || (!string.IsNullOrEmpty(_lastPcbId) && _genesisMeter?.PcbId != null && !_genesisMeter.PcbId.Equals(_lastPcbId))) { // This loop will be entered after the meter has changed, but not on the initial meter // after program start. var pp = _processStateDef.GetProcessOfState(ProcessState.DetectCordonel); var ppNo = _processStateDef.GetProductionProcessNoOfProcess(pp); // Remind order number to restore it for new genesis for assembly line, where the order // number scan is the first step var orderNoBackup = _genesisMeter?.OrderNumber; KillProcesses(ppNo); RemoveMeter(); InitNewMeter(); InitProcesses(ppNo); ResetCancellationToken(); // Remind actual genesis for next call _lastPcbId = _genesisMeter?.PcbId; // Restore production order number for recurrent call with identical order but different // new meter if (_genesisMeter != null) _genesisMeter.OrderNumber = orderNoBackup ?? 0; // New meter detected return true; } // Remind actual genesis _lastPcbId = _genesisMeter?.PcbId; // Old meter return false; } /// /// Restart final test on first failed or aborted test. /// /// /// - Initial /// /// /// - Removed list of failed production processes and search in Processes /// /// /// - Leave skipped processes as is. /// private ProcessState RepeatFailedTests() { var state = ProcessState.Idle; // Search for first test in processes list which failed or aborted due to final test in // parallel processing forced abort of active processes. The production processes are sorted, // so the first can start the process chain again. foreach (var p in Processes) { if (p.SingleProcessState == SingleProcessState.Error || p.SingleProcessState == SingleProcessState.Abort || p.SingleProcessState == SingleProcessState.Retry) { // set the state to the first entry to kick off the state machine here state = _processStateDef.GetStateOfProcess(p); break; } } // Put all processes which are not successfully completed to init (Waiting for execution) foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded && pp.SingleProcessState != SingleProcessState.Skipped)) { pp.IdleProcess(); } return state; } /// /// Error handler /// /// /// - Initial /// /// /// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization /// to re-start with ProductionProcessExecFinalParametrization. /// /// /// - Removed list of failed production processes /// /// /// - Kick off exec parametrization if check after reboot failed. /// private void ProcessErrorHandler(IProductionProcess process) { if (process == null) return; // Special action if parametrization check failed to repeat it if (ProcessState.CheckFinalParametrization == _processStateDef.GetStateOfProcess(process)) { // Signal state change to retry sequence exec config, store config, reboot, check config var pp = _processStateDef.GetProcessOfState(ProcessState.ExecFinalParametrization); pp.RetryProcess(); pp = _processStateDef.GetProcessOfState(ProcessState.ExecStoreConfiguration); pp.RetryProcess(); pp = _processStateDef.GetProcessOfState(ProcessState.RebootCordonel); pp.RetryProcess(); pp = _processStateDef.GetProcessOfState(ProcessState.CheckRadio); pp.RetryProcess(); pp = _processStateDef.GetProcessOfState(ProcessState.CheckFinalParametrization); pp.RetryProcess(); } // Error signal needed to abort to all processes AbortProcesses(); } /// /// Calculate the maximum of all processes expected to execute and signal to GUI. /// private void CalcAndPushMaxOverAllProcessSteps() { if (_processStateDef == null) return; var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses(); OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps, ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps)); } /// /// Calculate all processes which are completed or skipped and signal to GUI. /// private void CalcAndPushOverAllProgress() { if (Processes == null) return; // check the readiness of all processes in the loop var _overallReadyProcesses = 0; foreach (var p in Processes) { if (p.SingleProcessState == SingleProcessState.Succeeded || p.SingleProcessState == SingleProcessState.Skipped) _overallReadyProcesses++; } OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_overallReadyProcesses, ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps)); } /// /// Initialize all processes which are connected to a production process. /// /// An optional number to start the killing /// /// - Initial /// /// /// - Leave preceding processes to the input process number alive. /// private void InitProcesses(UInt32? productionProcessNo = null) { if (Processes == null) Processes = new List(); // Get all processes relevant for production from table which has to be sorted var processes = _processStateDef.GetAllProductionProcesses(); foreach (var process in processes) { // Without defined start process all will be reset to idle if (productionProcessNo == null) process.IdleProcess(); // All processes from and behind this process will be killed to kep the preceding as is else if (_processStateDef.GetProductionProcessNoOfProcess(process) >= productionProcessNo) process.IdleProcess(); // Add process to list to feed the process window in main screen if (Processes.All(pp => pp.ProcessName != process.ProcessName)) { Processes.Add(process); } } } /// /// Production process kick off: /// - Sets single process state to waiting within the process init, /// - Adds new process to process list, /// - Optional waits for single process to complete before process is going to be started, /// - Optional waits for all previously assigned and started processes to complete before process is going to /// be started, /// - Assigns slot, productionStatus, programmingParameters and genesisMeter to process, /// - Adds event handlers for process information, /// - Starts process. /// /// /// actual state /// /// - Initial /// /// /// - Skipped process due to one of the previous processes failed /// /// /// - Restructured to avoid process start if processes to wait for are not completed /// /// /// - Changed from programming parameters to register restorer /// /// /// - Changed setup process to start process being able to create a process list in advance. /// /// /// - Changed setup process to start process being able to create a process list in advance. /// /// /// - Feed with processStateStruct. /// /// /// - Removed cancellationToken assignment. /// /// /// Delay timer between finish exec-parametrization and 'StoreConfiguration' as 'PERIODICLOG' doesn't /// have 'StoreConfiguration' and needs at least 20 s to update contents before 'StoreConfiguration' /// can be dispatched to 'SENSUSRADIO'. /// /// /// Abort enabled during waiting loop. /// /// /// Skip the process if already succeeded and kick off the next process if succeeded. /// private void StartProcess(ProcessStateStruct processStateStruct) { // Deny access if uninitialized if (Processes == null || processStateStruct.ProductionProcess == null) return; // Do not start a new process if any error or abort request is signaled if (AnyProcessRequiresAbortion() && !processStateStruct.StartAlways) { return; } // Skip the process if already succeeded and kick off the 'NextStateOnSuccess' but avoid some hidden states if (processStateStruct.ProcessState != ProcessState.Error && processStateStruct.ProcessState != ProcessState.CreateEolProgress && processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Succeeded && processStateStruct.NextStateOnSuccess != null) { _stateMachineState = (ProcessState)processStateStruct.NextStateOnSuccess; return; } // Process is waiting for execution after init, this overrides the eventually abort state processStateStruct.ProductionProcess.InitProcess(); // Wait for '_paramExeFinishedToRebootDelayTimerExceeded' before 'StoreConfiguration' to all apps while (processStateStruct.ProcessState == ProcessState.ExecStoreConfiguration && _paramExeFinishedToStoreConfigDelayTimer != null && _paramExeFinishedToStoreConfigDelayTimer.IsEnabled && !_paramExeFinishedToStoreConfigDelayTimerExceeded && !AnyProcessRequiresAbortion()) { var teaTime = new TimeT(); var timeSpan = teaTime.DateTimeUtc - _paramExeFinishedToStoreConfigDelayTimerStartTime; var msg = $"{Resources.StrMsgRebootDelayTimerActive} " + $"{timeSpan.Seconds}/{ParamExeFinishedToStoreConfigTimeS} s - {teaTime}"; OnProcessLogRequest?.Invoke(this, new ProcessLogArgs(msg)); OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(ParamExeFinishedToStoreConfigTimeS, ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps)); OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(timeSpan.Seconds, ProcessProgressArgs.ProcessProgressType.ActualProcessSteps)); Thread.Sleep(1000); } // Wait for a single process to complete before start of new depending on process if (processStateStruct.WaitForSingleProcess != null) { // Each process will have its own timer until a timeout will change the state while (Processes.Any(pp => !AnyProcessRequiresAbortion() && _processStateDef.GetStateOfProcess(pp) == processStateStruct.WaitForSingleProcess && WaitForProcess(pp))) { Thread.Sleep(50); } } // Wait for all initialized processes to complete before start the new process if (processStateStruct.WaitForAllProcesses) { // Each process will have its own timer until a timeout will change the state, // avoid checking the new process, which is not started due to this waiting loop while (Processes.Any(pp => pp.ProcessName != processStateStruct.ProductionProcess.ProcessName && WaitForProcess(pp) && !AnyProcessRequiresAbortion())) { Thread.Sleep(50); } } // If any process exits meanwhile with an error or abort request avoid a start of this process if ((!processStateStruct.StartAlways && AnyProcessRequiresAbortion()) || processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Idle || processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Abort) { processStateStruct.ProductionProcess.AbortProcess(); return; } // References to commonly used production process objects processStateStruct.ProductionProcess.Meter = _genesisMeter; processStateStruct.ProductionProcess.RegisterRestorer = _registerRestorer; processStateStruct.ProductionProcess.RadioEncryptionKey = _radioEncryptionKey; processStateStruct.ProductionProcess.PwdContainer = _pwdContainer; processStateStruct.ProductionProcess.ProductionStatus = _productionStatus; processStateStruct.ProductionProcess.ProductionRequirements = _cordonelRequirements; processStateStruct.ProductionProcess.EolProgress = _cordonelEolProgress; // Kick off process execution, the breakExitState will not be used processStateStruct.ProductionProcess.StartProcess(processStateStruct.NextStateOnSuccess, processStateStruct.ErrorExitState, processStateStruct.BreakExitState); // Connect process events to actual process AddProcessEvents(processStateStruct.ProductionProcess); } /// /// Check all processes for error or abort request /// /// true if abort required /// /// - Cancellation token. /// private Boolean AnyProcessRequiresAbortion() { return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error || pp.SingleProcessState == SingleProcessState.Abort || _cancellationToken.IsCancellationRequested); } /// /// Check process if in status waiting or running (incomplete) /// /// production process /// true if process to wait for execution or exit running state private Boolean WaitForProcess(IProductionProcess pp) { return pp.SingleProcessState == SingleProcessState.Waiting || pp.SingleProcessState == SingleProcessState.Running; } /// /// Install process events /// /// private void AddProcessEvents(IProductionProcess process) { if (process == null) return; process.OnGuiMessageDispatcher += GuiMessageDispatcher_Handler; process.OnProcessProgressChanged += ProcessProgress_Handler; process.OnProcessLogRequest += ProcessLog_Handler; process.OnProcessStateChanged += ProcessStateChanged_Handler; process.OnForceStateMachineStateChange += ForceStateMachineStateChange_Handler; } /// /// Remove process events /// /// private void RemoveProcessEvents(IProductionProcess process) { if (process == null) return; process.OnGuiMessageDispatcher -= GuiMessageDispatcher_Handler; process.OnProcessProgressChanged -= ProcessProgress_Handler; process.OnProcessLogRequest -= ProcessLog_Handler; process.OnProcessStateChanged -= ProcessStateChanged_Handler; process.OnForceStateMachineStateChange -= ForceStateMachineStateChange_Handler; } /// /// Get the actual process state of the state machine /// /// actual process state /// /// - Initial /// public ProcessState GetProcessState() { return _stateMachineState; } /// /// Set the new process state of the state machine /// /// true, if process state is set /// /// - Initial /// public Boolean SetProcessState(ProcessState newProcessState) { if (newProcessState != _stateMachineState) { _stateMachineState = newProcessState; return true; } return false; } /// /// Forces the state machine to start the first production process /// /// true, if process state is set /// /// - Initial /// public Boolean StartFirstProcess() { var newProcessState = _processStateDef.GetStateOfFirstProcess(); return SetProcessState(newProcessState); } /// /// Remove all processes from the list: /// - Signal change to GUI, /// - Remove process events all, /// - Set state to idle. /// /// An optional number to start the killing /// /// - Initial /// /// /// - Abort register access. /// /// /// - Cancellation token source. /// /// /// - Kill processes from the start process as all others are already finished. /// private void KillProcesses(UInt32? productionProcessNo = null) { _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); if (Processes == null || Processes.Count == 0) return; // put all processes to idle and remove events of these processes foreach (var pp in Processes) { // Without defined start process all will be killed if (productionProcessNo == null) pp.IdleProcess(); // All processes from and behind this process will be killed to kep the preceding as is else if (_processStateDef.GetProductionProcessNoOfProcess(pp) >= productionProcessNo) pp.IdleProcess(); } } /// /// Abort all processes if one previous process failed, but leave the list intact. /// Leave all events for those which are already running intact to force a clean stop. /// /// /// - Initial /// private void AbortProcesses() { if (Processes == null || Processes.Count == 0) return; // Skip all processes which are not succeeded, erroneous or skipped to leave the status intact // and request abort for all others foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded && pp.SingleProcessState != SingleProcessState.Error && pp.SingleProcessState != SingleProcessState.Skipped && pp.SingleProcessState != SingleProcessState.Retry)) { pp.AbortProcess(); } } #endregion --------------------------------------- Tools ------------------------------------------------------ } }