1073 lines
49 KiB
C#
1073 lines
49 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Implementation for all methods needed to drive the state machine of the process controller
|
|
/// </summary>
|
|
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<IProductionProcess> 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<String> _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;
|
|
|
|
/// <summary>
|
|
/// Change the overview table of the GUI to the state of the individual process
|
|
/// </summary>
|
|
public event EventHandler<SingleProcessStateArgs> OnProcessStateChanged;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public event EventHandler<ProcessStateArgs> OnStateMachineStateChanged;
|
|
|
|
/// <summary>
|
|
/// Request for logging to GUI output and log-file
|
|
/// </summary>
|
|
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
|
|
|
|
/// <summary>
|
|
/// Keep the progress bars of the GUI up to date
|
|
/// </summary>
|
|
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
|
|
|
|
/// <summary>
|
|
/// Dispatch a message to the GUI
|
|
/// </summary>
|
|
public event EventHandler<GuiMessageArgs> 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;
|
|
|
|
/// <summary>
|
|
/// Avoid waiting for a single process completion.
|
|
/// </summary>
|
|
public const IProductionProcess DoNotWaitForSingleProcessCompletion = null;
|
|
|
|
/// <summary>
|
|
/// Kick off a parallel execution of the state machine.
|
|
/// </summary>
|
|
public const Boolean ExecuteInParallel = true;
|
|
|
|
#endregion --------------------------------------- Variables --------------------------------------------------
|
|
#region ------------------------------------------ Class ------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Ctor:
|
|
/// - Setup process timer for cyclic call of state machine,
|
|
/// - Create and setup Cordonel.
|
|
/// </summary>
|
|
/// <param name="slot">physical slot for this meter</param>
|
|
/// <param name="processSateDef"></param>
|
|
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jul-04" author="Thomas Wiedebusch">
|
|
/// - Always set to production mode to load the registers for this FW and not the latest,
|
|
/// - Always RegisterWatchService on,
|
|
/// - Always MinMaxCHeck on.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Nov-20" author="Thomas Wiedebusch">
|
|
/// - _paramExeFinishedToStoreConfigurationDelayTimer.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialization of new meter
|
|
/// </summary>
|
|
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
|
/// - Initialize processes to build a list of all production processes.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
|
/// - Removed list of failed production processes
|
|
/// </remarks>
|
|
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<String>();
|
|
// 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removal of old meter
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose.
|
|
/// </summary>
|
|
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public void Dispose()
|
|
{
|
|
_processCtrlTimer?.Dispose();
|
|
_processCtrlTimer = null;
|
|
_paramExeFinishedToStoreConfigDelayTimer?.Stop();
|
|
_paramExeFinishedToStoreConfigDelayTimer = null;
|
|
_processStateDef = null;
|
|
KillProcesses();
|
|
RemoveMeter();
|
|
}
|
|
|
|
#endregion --------------------------------------- Class ------------------------------------------------------
|
|
#region ------------------------------------------ Events -----------------------------------------------------
|
|
/// <summary>
|
|
/// Single process state or process list changed event,
|
|
/// Use event to force special procedure like uninstalling events
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
|
/// - Remove process events if process is finished.
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
|
/// - Remove process events if process is finished or skipped.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Nov-20" author="Thomas Wiedebusch">
|
|
/// - 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.
|
|
/// </remarks>
|
|
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);
|
|
}
|
|
/// <summary>
|
|
/// Handler to set a new state for the state machine regarding the entire final-process
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2023-Mar-03" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process timer
|
|
/// </summary>
|
|
/// <param name="state"></param>
|
|
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
private void TmrProcessCtrlCycle_Elapsed(Object state)
|
|
{
|
|
StateMachine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="state"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2025-Nov-20" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
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 ----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
|
/// - Generic implementation
|
|
/// </remarks>
|
|
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 ------------------------------------------------------
|
|
/// <summary>
|
|
/// Common routine to reset the cancellation token on restart or new meter
|
|
/// </summary>
|
|
/// <remarks date="2025-Jul-07" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2025-Aug-1" author="Thomas Wiedebusch">
|
|
/// - Rest all cancellation tokens in processes.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the currentGenesis information for display
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public CordonelRequirements GetProductionRequirements()
|
|
{
|
|
return _cordonelRequirements;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the currentGenesis information for display
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public IGenesisMeter GetGenesisMeter()
|
|
{
|
|
return _genesisMeter;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Clears all collected data on new genesisMeter and reminds the detected for next
|
|
/// detection and run of this comparison.
|
|
/// </summary>
|
|
/// <returns>true for new meter</returns>
|
|
/// <remarks date="2023-Jun-28" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
|
/// - 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!
|
|
/// </remarks>
|
|
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
|
/// - Handle genesis meter is null as new meter.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
|
|
/// - Check _genesisMeter not null before equality with old pcbid.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Jun-07" author="Thomas Wiedebusch">
|
|
/// - ResetCancellationToken.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Jun-08" author="Thomas Wiedebusch">
|
|
/// - Remind order number,
|
|
/// - avoid to kill the order number scan and avoid to reset this production state to idle.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restart final test on first failed or aborted test.
|
|
/// </summary>
|
|
/// <remarks date="2023-Jul-18" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
|
/// - Removed list of failed production processes and search in Processes
|
|
/// </remarks>
|
|
/// <remarks date="2024-Oct-21" author="Thomas Wiedebusch">
|
|
/// - Leave skipped processes as is.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error handler
|
|
/// </summary>
|
|
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2024-Feb-07" author="Thomas Wiedebusch">
|
|
/// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization
|
|
/// to re-start with ProductionProcessExecFinalParametrization.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
|
/// - Removed list of failed production processes
|
|
/// </remarks>
|
|
/// <remarks date="2025-Jan-28" author="Thomas Wiedebusch">
|
|
/// - Kick off exec parametrization if check after reboot failed.
|
|
/// </remarks>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the maximum of all processes expected to execute and signal to GUI.
|
|
/// </summary>
|
|
private void CalcAndPushMaxOverAllProcessSteps()
|
|
{
|
|
if (_processStateDef == null)
|
|
return;
|
|
|
|
var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses();
|
|
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps,
|
|
ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate all processes which are completed or skipped and signal to GUI.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize all processes which are connected to a production process.
|
|
/// </summary>
|
|
/// <param name="productionProcessNo">An optional number to start the killing</param>
|
|
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2025-Jul-08" author="Thomas Wiedebusch">
|
|
/// - Leave preceding processes to the input process number alive.
|
|
/// </remarks>
|
|
private void InitProcesses(UInt32? productionProcessNo = null)
|
|
{
|
|
if (Processes == null)
|
|
Processes = new List<IProductionProcess>();
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="processStateStruct"></param>
|
|
/// <returns>actual state</returns>
|
|
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
|
/// - Skipped process due to one of the previous processes failed
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
|
/// - Restructured to avoid process start if processes to wait for are not completed
|
|
/// </remarks>
|
|
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
|
|
/// - Changed from programming parameters to register restorer
|
|
/// </remarks>
|
|
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
|
/// - Changed setup process to start process being able to create a process list in advance.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
|
|
/// - Changed setup process to start process being able to create a process list in advance.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
|
/// - Feed with processStateStruct.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Aug-11" author="Thomas Wiedebusch">
|
|
/// - Removed cancellationToken assignment.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Nov-20" author="Thomas Wiedebusch">
|
|
/// 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'.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Nov-21" author="Thomas Wiedebusch">
|
|
/// Abort enabled during waiting loop.
|
|
/// </remarks>
|
|
/// <remarks date="2026-Feb-12" author="Thomas Wiedebusch">
|
|
/// Skip the process if already succeeded and kick off the next process if succeeded.
|
|
/// </remarks>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check all processes for error or abort request
|
|
/// </summary>
|
|
/// <returns>true if abort required</returns>
|
|
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
|
|
/// - Cancellation token.
|
|
/// </remarks>
|
|
private Boolean AnyProcessRequiresAbortion()
|
|
{
|
|
return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error ||
|
|
pp.SingleProcessState == SingleProcessState.Abort ||
|
|
_cancellationToken.IsCancellationRequested);
|
|
}
|
|
/// <summary>
|
|
/// Check process if in status waiting or running (incomplete)
|
|
/// </summary>
|
|
/// <param name="pp">production process</param>
|
|
/// <returns>true if process to wait for execution or exit running state</returns>
|
|
private Boolean WaitForProcess(IProductionProcess pp)
|
|
{
|
|
return pp.SingleProcessState == SingleProcessState.Waiting ||
|
|
pp.SingleProcessState == SingleProcessState.Running;
|
|
}
|
|
/// <summary>
|
|
/// Install process events
|
|
/// </summary>
|
|
/// <param name="process"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove process events
|
|
/// </summary>
|
|
/// <param name="process"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the actual process state of the state machine
|
|
/// </summary>
|
|
/// <returns>actual process state</returns>
|
|
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public ProcessState GetProcessState()
|
|
{
|
|
return _stateMachineState;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the new process state of the state machine
|
|
/// </summary>
|
|
/// <returns>true, if process state is set</returns>
|
|
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public Boolean SetProcessState(ProcessState newProcessState)
|
|
{
|
|
if (newProcessState != _stateMachineState)
|
|
{
|
|
_stateMachineState = newProcessState;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces the state machine to start the first production process
|
|
/// </summary>
|
|
/// <returns>true, if process state is set</returns>
|
|
/// <remarks date="2025-Jun-30" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public Boolean StartFirstProcess()
|
|
{
|
|
var newProcessState = _processStateDef.GetStateOfFirstProcess();
|
|
return SetProcessState(newProcessState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all processes from the <see cref="Processes"/> list:
|
|
/// - Signal change to GUI,
|
|
/// - Remove process events all,
|
|
/// - Set state to idle.
|
|
/// </summary>
|
|
/// <param name="productionProcessNo">An optional number to start the killing</param>
|
|
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
|
|
/// - Abort register access.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
|
/// - Cancellation token source.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Jul-08" author="Thomas Wiedebusch">
|
|
/// - Kill processes from the start process as all others are already finished.
|
|
/// </remarks>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Abort all processes if one previous process failed, but leave the <see cref="Processes"/> list intact.
|
|
/// Leave all events for those which are already running intact to force a clean stop.
|
|
/// </summary>
|
|
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
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 ------------------------------------------------------
|
|
}
|
|
}
|