diff --git a/Common/.shared/SharedAssemblyInfo.cs b/Common/.shared/SharedAssemblyInfo.cs index bcca328f..6c4ebd47 100644 --- a/Common/.shared/SharedAssemblyInfo.cs +++ b/Common/.shared/SharedAssemblyInfo.cs @@ -14,4 +14,4 @@ using System.Reflection; //[assembly: AssemblyVersion("1.2.*.0")] -[assembly: AssemblyVersion("2.6.16.*")] +[assembly: AssemblyVersion("2.7.0.*")] diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs index 96393683..4e1fae51 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs +++ b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs @@ -21,6 +21,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -53,7 +54,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private FinalProcessController _processCtrl; + private BaseProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -177,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new FinalProcessController(Slot); + _processCtrl = new EolProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/Picking.xaml.cs b/Common/Production/ProductionUiCordonel/Picking.xaml.cs index 5aee7568..72e42923 100644 --- a/Common/Production/ProductionUiCordonel/Picking.xaml.cs +++ b/Common/Production/ProductionUiCordonel/Picking.xaml.cs @@ -21,6 +21,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -53,7 +54,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private FinalProcessController _processCtrl; + private BaseProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -177,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new FinalProcessController(Slot); + _processCtrl = new EolProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs similarity index 62% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs index 408c53a3..8ddac59b 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs @@ -8,27 +8,20 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Timer = System.Threading.Timer; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { /// - /// Preparing the Cordonel for shipping: - /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, - /// - If radio is installed: radio test, - /// - Requiring radio key from LuDB, - /// - Requiring 7 passwords for the Cordonel access levels, - /// - Building the password file (Level 3 is the modified skeleton key), - /// - Store all configurations, - /// - Reboot the Cordonel to load all settings from FLASH to RAM, - /// - Configuration check of all parameters defined by VAKO and CSD. + /// Implementation for all methods needed to drive the state machine of the process controller /// - public sealed class FinalProcessController + public abstract class BaseProcessController : IProcessController { #region ------------------------------------------ Variables -------------------------------------------------- // actual genesis meter - private IGenesisMeter _genesisMeter; + protected IGenesisMeter _genesisMeter; // Cordonel requirements for production private CordonelRequirements _cordonelRequirements; // EOL progress states for production @@ -38,19 +31,20 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // batch just used for setup, as only one meter will be assigned private MeterBatch _meterBatch; // state of the state machine - private ProcessState _stateMachineState; + protected ProcessState _stateMachineState; // locker to avoid repeated state execution - private ProcessState _lockedProcessState; + protected ProcessState _lockedProcessState; // sender of state change event to evaluate in e.g. error process - private IProductionProcess _stateChangeRequestProcess; + protected IProductionProcess _stateChangeRequestProcess; // List of production processes public List Processes { - get; private set; + get; + private set; } // Container for production process states - private ProcessStateDef _processStateDef; + protected EolProcessStates _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) @@ -69,24 +63,36 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // reminder for slot private readonly Int32 _slot; - // processing retries - private const Int32 MaxProcessRetries = 2; - //private static Int32 _processRetryCtr; - - //event for process list change or state of single process step change + /// + /// Change the overview table of the GUI to the state of the individual process + /// public event EventHandler OnSingleProcessStateChanged; + + /// + /// 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 OnOverallProcessStateChanged; + + /// + /// Request for logging to GUI output and log-file + /// public event EventHandler OnProcessLogRequest; - // progress bar and screen info of ongoing processes + + /// + /// Keep the progress bars of the GUI up to date + /// public event EventHandler OnProcessProgressChanged; // cancellation token - private CancellationTokenSource _cancellationTokenSource; - private CancellationToken _cancellationToken; + protected CancellationTokenSource _cancellationTokenSource; + protected CancellationToken _cancellationToken; // cyclic timer for state machine call private Timer _processCtrlTimer; - private const Int32 ProcessTimerCycleMs = 250; + protected const Int32 ProcessTimerCycleMs = 250; // defines for state machine /// @@ -111,7 +117,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Always RegisterWatchService on, /// - Always MinMaxCHeck on. /// - public FinalProcessController(Int32 slot) + protected BaseProcessController(Int32 slot) { _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); _slot = slot; @@ -248,6 +254,20 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses OnProcessLogRequest?.Invoke(sender, e); } + /// + /// Wrapper to fire this event from a derived class + /// + /// + /// + /// + /// - Initial + /// + protected void OverallProcessStateChanged(Object sender, ProcessStateArgs e) + { + // signal state change to logger + OnOverallProcessStateChanged?.Invoke(sender, e); + } + /// /// Handler to set a new state for the state machine regarding the entire final-process /// @@ -282,272 +302,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses #endregion --------------------------------------- Events ----------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- - /// - /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). - /// - /// - /// INSTRUCTION FOR NORMAL USAGE: - /// ----------------------------- - /// Call the SetupProcess() with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState set to the next required state after successfully execution. - /// That's it! The normal state change has always only one production process active and waits for completion - /// before changing to a new state and therefore a new production process. - /// - /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the - /// errorExitState is linked to the error state, the breakExitState to the stop state. - /// - /// - /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: - /// -------------------------------------------------------------------------- - /// Sometimes production processes may be able to be executed simultaneously as they are independent of each - /// other. One example is a database access for data collection from different tables. - /// - /// Call the SetupProcess with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState being set to (null). - /// This will lock the sate change after completion as multiple processes may be active and a state change after - /// completion may cause unpredictable operation, the state is already where it should be (manually changed), - /// - change the state after the SetupProcess() call manually. - /// - /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of - /// the actual new assigned process): - /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without - /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully - /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait - /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! - /// - /// - /// INSTRUCTION TO DELAY THE START OF A PROCESS: - /// -------------------------------------------- - /// Each new process execution can be delayed as it may need the result of another process which is active. - /// - /// Wait for a single process to complete: - /// - set the "waitForSingleProcess" to the process to wait for. - /// Wait for all preceding processes to complete: - /// - set the "waitForAllProcesses" to true. - /// - /// - /// - /// - Initial - /// - private void StateMachine() - { - // avoid repeated execution of the state machine on unchanged state - if (_stateMachineState == _lockedProcessState) - { - Thread.Sleep(ProcessTimerCycleMs >> 1); - } - else - { - try - { - // remind backup state to avoid repeated execution and side effects - _lockedProcessState = _stateMachineState; - // signal overall process progress to GUI based on ready processes - CalcAndPushOverAllProgress(); - - // signal state change to logger - OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); - - // get the actual required process from the state definition list - var process = _processStateDef?.GetProcessOfState(_stateMachineState); - - switch (_stateMachineState) - { - case ProcessState.Idle: - // This state needs an external state change like user input to go ahead - break; - - case ProcessState.DetectCordonel: - // Wait for cancellation - if (_cancellationToken.IsCancellationRequested) - { - Thread.Sleep(1000); - // Reset the cancellation request - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - _cancellationToken = _cancellationTokenSource.Token; - } - - // Set up the counter for maximum overall process progress bar in GUI - CalcAndPushMaxOverAllProcessSteps(); - - // Detection of Cordonel has to be completed before assigning next state, so leave to automatic - // to change the state after successfully execution. The detection is the base for all others. - StartProcess(process, ProcessState.CheckNewMeter); - 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"! - _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; - break; - - case ProcessState.ConnectCordonel: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; - break; - - case ProcessState.GetSerialNumberAndRadioAddress: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetProgrammingParameters; - break; - - case ProcessState.GetProgrammingParameters: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.CheckProductionState; - break; - - case ProcessState.CheckProductionState: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.ProcessPasswordFile; - break; - - case ProcessState.ProcessPasswordFile: - // Precondition: - The connection process has to be completed as a password change in the DB - // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side - // effects on login. Wait for all processes to complete. - // Leave state switch to automatic to avoid side effects on incomplete updated DB for next - // steps. - StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); - break; - - case ProcessState.ExecFinalParametrization: - // This writes all setups to the device, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.ExecStoreConfiguration); - break; - - case ProcessState.ExecStoreConfiguration: - // Precondition: - The final parametrization has to be finished as this shall be stored - // to non-volatile memory and checked if remaining valid after the reboot. - // Store configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.RebootCordonel); - break; - - case ProcessState.RebootCordonel: - // Precondition: - The store configuration has to be executed to safe all settings - // to non-volatile memory and checked if remaining valid after the reboot. - // Reboot has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckFinalParametrization); - break; - - case ProcessState.CheckFinalParametrization: - // Precondition: - The reboot process including the store all configurations has to be - // completed. - // Check final configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckRadio); - break; - - case ProcessState.CheckRadio: - // Precondition: - The final parametrization, reboot process including the store all - // configurations and the check of all setups survived the reboot have to be completed. - // Radio signal level check, skipped on NA region or if skipping is required by special - // approval - StartProcess(process, ProcessState.CheckOrderNumber); - break; - - case ProcessState.CheckOrderNumber: - // This scans the order number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); - break; - - case ProcessState.CheckSerialNumber: - // This scans the serial number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckAttachments); - break; - - case ProcessState.CheckAttachments: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ExecVisualInspection); - break; - - case ProcessState.ExecVisualInspection: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.PrepareShippingMode); - break; - - case ProcessState.PrepareShippingMode: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ReadyForShipping); - break; - - case ProcessState.ReadyForShipping: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.RepeatFailedTests: - _stateMachineState = RepeatFailedTests(); - break; - - case ProcessState.PrintReturnReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.PrintSuccessReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.AbortFinalTest: - 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(process, ProcessState.Idle, startAlways: true); - break; - - case ProcessState.StateListDelimiter: - default: - _stateMachineState = ProcessState.Idle; - 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 + public abstract void StateMachine(); #endregion --------------------------------------- State Machine ---------------------------------------------- #region ------------------------------------------ Tools ------------------------------------------------------ @@ -588,7 +344,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Handle genesis meter is null as new meter. /// - private Boolean CheckForNewMeter() + protected Boolean CheckForNewMeter() { if (_genesisMeter == null || (!string.IsNullOrEmpty(_lastPcbId) && @@ -626,7 +382,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Leave skipped processes as is. /// - private ProcessState RepeatFailedTests() + protected ProcessState RepeatFailedTests() { var state = ProcessState.Idle; @@ -668,7 +424,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Kick off exec parametrization if check after reboot failed. /// - private void ProcessErrorHandler(IProductionProcess process) + protected void ProcessErrorHandler(IProductionProcess process) { if (process == null) return; @@ -693,7 +449,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate the maximum of all processes expected to execute and signal to GUI. /// - private void CalcAndPushMaxOverAllProcessSteps() + protected void CalcAndPushMaxOverAllProcessSteps() { if (_processStateDef == null) return; @@ -707,7 +463,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate all processes which are completed or skipped and signal to GUI. /// - private void CalcAndPushOverAllProgress() + protected void CalcAndPushOverAllProgress() { if (Processes == null) return; @@ -736,7 +492,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses if (Processes == null) Processes = new List(); if (_processStateDef == null) - _processStateDef = new ProcessStateDef(); + _processStateDef = new EolProcessStates(); // get all processes relevant for EOL from table which has to be sorted var processes = _processStateDef.GetAllEolRelevantProcesses(); @@ -791,7 +547,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Changed setup process to start process being able to create a process list in advance. /// - private void StartProcess(IProductionProcess process, ProcessState? successExitState, + protected void StartProcess(IProductionProcess process, ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error, ProcessState breakExitState = ProcessState.Stop, IProductionProcess waitForSingleProcess = DoNotWaitForSingleProcessCompletion, Boolean waitForAllProcesses = false, Boolean startAlways = false) @@ -956,7 +712,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Cancellation token source. /// - private void KillProcesses() + protected void KillProcesses() { _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); @@ -978,7 +734,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - private void AbortProcesses() + protected void AbortProcesses() { if (Processes == null || Processes.Count == 0) return; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs similarity index 94% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs index aa619be1..a0fac18a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs @@ -1,10 +1,9 @@ using System; using Xylem.Common.CommonCore.Consts; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { public class ConnectCordonel : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs index efe7c97d..562d993e 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs @@ -8,7 +8,7 @@ using Xylem.Common.Logic.SoftwareAccessHelper; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { /// /// Request the serial number and radio address from DB. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs index a5607356..72993b35 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs @@ -15,6 +15,7 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.ProductionOrderCore.OrderData; using Xylem.Common.Logic.ProductionOrderCore.TestResults; using Xylem.Common.Production.ProductionUiCordonel.Model; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs new file mode 100644 index 00000000..58041c78 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs @@ -0,0 +1,314 @@ +using System; +using System.Threading; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +{ + /// + /// Preparing the Cordonel for shipping: + /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, + /// - If radio is installed: radio test, + /// - Requiring radio key from LuDB, + /// - Requiring 7 passwords for the Cordonel access levels, + /// - Building the password file (Level 3 is the modified skeleton key), + /// - Store all configurations, + /// - Reboot the Cordonel to load all settings from FLASH to RAM, + /// - Configuration check of all parameters defined by VAKO and CSD. + /// + public sealed class EolProcessController : BaseProcessController, IProcessController + { + #region ------------------------------------------ Variables -------------------------------------------------- + // + #endregion --------------------------------------- Variables -------------------------------------------------- + #region ------------------------------------------ Class ------------------------------------------------------ + + /// + /// Ctor + /// + /// physical slot for this meter + /// + /// - Initial + /// + public EolProcessController(Int32 slot) : base(slot) + { + } + + #endregion --------------------------------------- Class ------------------------------------------------------ + #region ------------------------------------------ Events ----------------------------------------------------- + // + #endregion --------------------------------------- Events ----------------------------------------------------- + #region ------------------------------------------ State Machine ---------------------------------------------- + /// + /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// Call the SetupProcess() with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState set to the next required state after successfully execution. + /// That's it! The normal state change has always only one production process active and waits for completion + /// before changing to a new state and therefore a new production process. + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// errorExitState is linked to the error state, the breakExitState to the stop state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// Call the SetupProcess with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState being set to (null). + /// This will lock the sate change after completion as multiple processes may be active and a state change after + /// completion may cause unpredictable operation, the state is already where it should be (manually changed), + /// - change the state after the SetupProcess() call manually. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully + /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait + /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process to wait for. + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + /// + /// - Initial + /// + public override void StateMachine() + { + // avoid repeated execution of the state machine on unchanged state + if (_stateMachineState == _lockedProcessState) + { + Thread.Sleep(ProcessTimerCycleMs >> 1); + } + else + { + try + { + // remind backup state to avoid repeated execution and side effects + _lockedProcessState = _stateMachineState; + + // signal overall process progress to GUI based on ready processes + CalcAndPushOverAllProgress(); + + // signal state change to logger + OverallProcessStateChanged(this, new ProcessStateArgs(_stateMachineState)); + + // get the actual required process from the state definition list + var process = _processStateDef?.GetProcessOfState(_stateMachineState); + + switch (_stateMachineState) + { + case ProcessState.Idle: + // This state needs an external state change like user input to go ahead + break; + + case ProcessState.DetectCordonel: + // Wait for cancellation + if (_cancellationToken.IsCancellationRequested) + { + Thread.Sleep(1000); + // Reset the cancellation request + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + } + + // Set up the counter for maximum overall process progress bar in GUI + CalcAndPushMaxOverAllProcessSteps(); + + // Detection of Cordonel has to be completed before assigning next state, so leave to automatic + // to change the state after successfully execution. The detection is the base for all others. + StartProcess(process, ProcessState.CheckNewMeter); + 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"! + _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; + break; + + case ProcessState.ConnectCordonel: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; + break; + + case ProcessState.GetSerialNumberAndRadioAddress: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetProgrammingParameters; + break; + + case ProcessState.GetProgrammingParameters: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.CheckProductionState; + break; + + case ProcessState.CheckProductionState: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.ProcessPasswordFile; + break; + + case ProcessState.ProcessPasswordFile: + // Precondition: - The connection process has to be completed as a password change in the DB + // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side + // effects on login. Wait for all processes to complete. + // Leave state switch to automatic to avoid side effects on incomplete updated DB for next + // steps. + StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); + break; + + case ProcessState.ExecFinalParametrization: + // This writes all setups to the device, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.ExecStoreConfiguration); + break; + + case ProcessState.ExecStoreConfiguration: + // Precondition: - The final parametrization has to be finished as this shall be stored + // to non-volatile memory and checked if remaining valid after the reboot. + // Store configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.RebootCordonel); + break; + + case ProcessState.RebootCordonel: + // Precondition: - The store configuration has to be executed to safe all settings + // to non-volatile memory and checked if remaining valid after the reboot. + // Reboot has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckFinalParametrization); + break; + + case ProcessState.CheckFinalParametrization: + // Precondition: - The reboot process including the store all configurations has to be + // completed. + // Check final configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckRadio); + break; + + case ProcessState.CheckRadio: + // Precondition: - The final parametrization, reboot process including the store all + // configurations and the check of all setups survived the reboot have to be completed. + // Radio signal level check, skipped on NA region or if skipping is required by special + // approval + StartProcess(process, ProcessState.CheckOrderNumber); + break; + + case ProcessState.CheckOrderNumber: + // This scans the order number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); + break; + + case ProcessState.CheckSerialNumber: + // This scans the serial number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckAttachments); + break; + + case ProcessState.CheckAttachments: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ExecVisualInspection); + break; + + case ProcessState.ExecVisualInspection: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.PrepareShippingMode); + break; + + case ProcessState.PrepareShippingMode: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ReadyForShipping); + break; + + case ProcessState.ReadyForShipping: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.RepeatFailedTests: + _stateMachineState = RepeatFailedTests(); + break; + + case ProcessState.PrintReturnReport: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.PrintSuccessReport: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.AbortFinalTest: + 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(process, ProcessState.Idle, startAlways: true); + break; + + case ProcessState.StateListDelimiter: + default: + _stateMachineState = ProcessState.Idle; + 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 ------------------------------------------------------ + // + #endregion --------------------------------------- Tools ------------------------------------------------------ + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index 5cfecbdc..b7c8b4c9 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Linq; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; -using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { /// /// Concatenation for all production process states: @@ -17,7 +16,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - public class ProcessStateDef + public class EolProcessStates { /// /// Collection of state information @@ -31,7 +30,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Sort the active steps from first til last step as this is the order as those will be sorted in the /// process list UI. /// - public ProcessStateDef() + public EolProcessStates() { _stateContainer = new List { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs index 3d7f3bb4..11d6bcf6 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs @@ -66,7 +66,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if passwords are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs index 9f72441f..2a322f9a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs @@ -39,7 +39,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if programming parameters are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs new file mode 100644 index 00000000..26615686 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs @@ -0,0 +1,58 @@ +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +{ + public interface IProcessController + { + /// + /// State machine of ProcessController which serves as template for all production processes. + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// Call the SetupProcess() with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState set to the next required state after successfully execution. + /// That's it! The normal state change has always only one production process active and waits for completion + /// before changing to a new state and therefore a new production process. + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// errorExitState is linked to the error state, the breakExitState to the stop state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// Call the SetupProcess with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState being set to (null). + /// This will lock the sate change after completion as multiple processes may be active and a state change after + /// completion may cause unpredictable operation, the state is already where it should be (manually changed), + /// - change the state after the SetupProcess() call manually. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully + /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait + /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process to wait for. + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + /// + /// - Initial + /// + void StateMachine(); + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 6f4ea12c..767a4554 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -291,7 +291,7 @@ - + @@ -299,7 +299,9 @@ - + + + @@ -325,12 +327,12 @@ - + - +