560 lines
22 KiB
C#
560 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using LaaPackages.Features.Cordonel.Models;
|
|
using Xylem.Common.CommonCore.Consts;
|
|
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;
|
|
using Xylem.Common.Logic.ProductionOrderCore;
|
|
using Xylem.Common.Production.ProductionUiCordonel.Properties;
|
|
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
|
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs;
|
|
using Xylem.Common.Production.ProductionUiCordonel.UserControls;
|
|
using Xylem.Common.Utils.ProcessExec;
|
|
using Xylem.Common.Utils.ProcessExec.EventArguments;
|
|
|
|
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
|
|
{
|
|
/// <summary>
|
|
/// The base production process defines the tools needed to initiate, start and finalize a process. It holds the
|
|
/// statistic counters.
|
|
/// </summary>
|
|
public abstract class BaseProductionProcess : IProductionProcess
|
|
{
|
|
#region ------------------------------------------ Variables --------------------------------------------------
|
|
// remind manually changed culture setting
|
|
private static readonly CultureInfo _cultureInfo = CultureInfo.CurrentUICulture;
|
|
|
|
// user defined control for main screen
|
|
protected UserControl UserControl
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Avoid state change after successfully completion of production process. This will be used if multiple
|
|
/// processes are executed and the state has been changed manually.
|
|
/// </summary>
|
|
public static readonly ProcessState? DoNotChangeStateAfterCompletion = null;
|
|
|
|
/// <summary>
|
|
/// Number of processing steps for this actual process progress
|
|
/// </summary>
|
|
private Int32 _maxActualProcessSteps;
|
|
private Int32 MaxActualProcessProgress
|
|
{
|
|
get => _maxActualProcessSteps;
|
|
set
|
|
{
|
|
_maxActualProcessSteps = value;
|
|
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxActualProcessSteps,
|
|
ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Progress of actual process
|
|
/// </summary>
|
|
private Int32 _actualProcessProgress;
|
|
protected Int32 ActualProcessProgress
|
|
{
|
|
get => _actualProcessProgress;
|
|
set
|
|
{
|
|
_actualProcessProgress = value;
|
|
if (_actualProcessProgress > _maxActualProcessSteps)
|
|
_actualProcessProgress = _maxActualProcessSteps;
|
|
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_actualProcessProgress,
|
|
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
|
|
}
|
|
}
|
|
|
|
///<inheritdoc/>
|
|
public SingleProcessState SingleProcessState
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The forced exit state will be used by e.g. the error process to keep the standard handling
|
|
/// intact but switch to a dynamic (e.g. based on a dialog input) exit which is not predictable
|
|
/// by the standard state machine implementation.
|
|
/// </summary>
|
|
protected ProcessState? ForcedSuccessExitState
|
|
{
|
|
set; get;
|
|
}
|
|
|
|
///<inheritdoc/>
|
|
public String ProcessName
|
|
{
|
|
get;
|
|
}
|
|
|
|
///<inheritdoc/>
|
|
public Version Version => _version;
|
|
|
|
/// <inheritdoc />
|
|
public GenesisStatus ProductionStatus
|
|
{
|
|
set; protected get;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public CordonelRequirements ProductionRequirements
|
|
{
|
|
set; protected get;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public EOLProgressModel EolProgress
|
|
{
|
|
set; protected get;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IGenesisMeter Meter
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public RegisterRestorer RegisterRestorer
|
|
{
|
|
protected get; set;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public List<String> RadioEncryptionKey
|
|
{
|
|
protected get; set;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public MeterPwdDb PwdContainer
|
|
{
|
|
set; protected get;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is for processes which do not want to log the execution or state change like ERROR process,
|
|
/// </summary>
|
|
protected Boolean IsSilentProcess
|
|
{
|
|
set; get;
|
|
}
|
|
|
|
// assembly version information
|
|
private static readonly Version _version = Assembly.GetExecutingAssembly().GetName().Version;
|
|
|
|
/// <inheritdoc/>
|
|
public CancellationToken CancellationToken { set; get; }
|
|
|
|
/// <inheritdoc/>
|
|
public event EventHandler<SingleProcessStateArgs> OnProcessStateChanged;
|
|
|
|
///<inheritdoc/>
|
|
public event EventHandler<ProcessStateArgs> OnForceStateMachineStateChange;
|
|
|
|
///<inheritdoc/>
|
|
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
|
|
|
|
///<inheritdoc/>
|
|
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
|
|
|
|
///<inheritdoc/>
|
|
public event EventHandler<GuiMessageArgs> OnGuiMessageDispatcher;
|
|
|
|
#endregion --------------------------------------- Variables --------------------------------------------------
|
|
#region ------------------------------------------ Init -------------------------------------------------------
|
|
/// <summary>
|
|
/// Ctor:
|
|
/// - Init.
|
|
/// </summary>
|
|
/// <param name="name">name for process to display a message</param>
|
|
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
protected BaseProductionProcess(String name)
|
|
{
|
|
ProcessName = name;
|
|
SingleProcessState = SingleProcessState.Waiting;
|
|
}
|
|
|
|
#endregion --------------------------------------- Init -------------------------------------------------------
|
|
#region ------------------------------------------ Tools ------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Set overall process to 100 %
|
|
/// </summary>
|
|
protected void SetProcessProgressTo100Percent()
|
|
{
|
|
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(Int32.MaxValue,
|
|
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public UserControl GetUserControl()
|
|
{
|
|
// Invoker needed for UserControls
|
|
Application.Current.Dispatcher.Invoke(InitUserControl);
|
|
// Null as return is going to be used to remove the userControl from main screen
|
|
return UserControl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common routine for single register write, log of result and increase process counter.
|
|
/// </summary>
|
|
/// <param name="proPar"></param>
|
|
/// <returns>true if successfully</returns>
|
|
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
|
|
/// - Inverse raw register bytes before conversion to text using the
|
|
/// <see cref="RegisterConverter.GetRegisterContentText"/>.
|
|
/// </remarks>
|
|
protected Boolean WriteRegisterLogAndProcessCtr(ProgrammingParameters proPar)
|
|
{
|
|
var retVal = false;
|
|
var msg = "?";
|
|
try
|
|
{
|
|
// write without check
|
|
retVal = Meter.WriteRegister(proPar.RegisterName, proPar.RegisterValue);
|
|
|
|
// convert to readable value
|
|
var registerDefinitions = Meter.GetConfigRegistersDefinitions();
|
|
var regDef = registerDefinitions.GetRegisterDefinitionByName(proPar.RegisterName);
|
|
msg = RegisterConverter.GetRegisterContentText(regDef, proPar.RegisterValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Meter.WriteLog(ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
if (retVal)
|
|
{
|
|
SuccessMsgDispatcher($"{Resources.StrSuccessMsgWrite}: {msg}");
|
|
}
|
|
else
|
|
{
|
|
ErrorMsgDispatcher($"{Resources.StrErrorMsgWrite}: {msg}");
|
|
}
|
|
}
|
|
ActualProcessProgress++;
|
|
return retVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Action for cancellation token to abort process
|
|
/// </summary>
|
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
protected StatusReturn CancellationProcedure()
|
|
{
|
|
AbortProcess();
|
|
return StatusReturn.Failed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set new single process state and fire event from base class:
|
|
/// - First set new SingleProcessState,
|
|
/// - Last fire the SingleProcessState.
|
|
///
|
|
/// ATTENTION: Made it private to avoid INTERRUPTION of state machine!
|
|
/// This is caused by the eventual removal of the process events using the OnSingleProcessStateChanged.
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="state"></param>
|
|
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
|
/// - Set single process state for calling production process,
|
|
/// - fire single process state event.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Nov-08" author="Thomas Wiedebusch">
|
|
/// - PreExecuteProcess.
|
|
/// </remarks>
|
|
private void SetAndSignalProcessState(Object sender, SingleProcessState state)
|
|
{
|
|
SingleProcessState = state;
|
|
OnProcessStateChanged?.Invoke(sender, new SingleProcessStateArgs(state));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fire event for GUI messages
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
protected void DispatchGuiMessage(GuiMessageArgs args)
|
|
{
|
|
OnGuiMessageDispatcher?.Invoke(this, args);
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public void StartProcess(ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error,
|
|
ProcessState breakExitState = ProcessState.AbortTest)
|
|
{
|
|
if (Meter == null)
|
|
{
|
|
// First signal to change the state machine because the single process state change uninstalls
|
|
// all events from process
|
|
OnForceStateMachineStateChange?.Invoke(this, new ProcessStateArgs(ProcessState.Error));
|
|
// Set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Error);
|
|
}
|
|
|
|
var userControl = (IUserControl)UserControl;
|
|
userControl?.Clear();
|
|
|
|
var exitProcessStateObjects = new Object[3];
|
|
exitProcessStateObjects[0] = successExitState;
|
|
exitProcessStateObjects[1] = errorExitState;
|
|
exitProcessStateObjects[2] = breakExitState;
|
|
|
|
var processExec = new ProcessExec();
|
|
processExec.NewProcess(InitProcess, PreExecuteProcess, ExecuteProcess, FinalizeProcess,
|
|
_cultureInfo, exitProcessStateObjects, CancellationToken);
|
|
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual void InitProcess()
|
|
{
|
|
// Set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Waiting);
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual void IdleProcess()
|
|
{
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Idle);
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual StatusReturn PreExecuteProcess()
|
|
{
|
|
return StatusReturn.Okay;
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual void AbortProcess()
|
|
{
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Abort);
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual void RetryProcess()
|
|
{
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Retry);
|
|
}
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public abstract StatusReturn ExecuteProcess();
|
|
|
|
/// <inheritdoc cref="IProductionProcess"/>
|
|
public virtual void FinalizeProcess(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
|
|
{
|
|
ActualProcessProgress = MaxActualProcessProgress;
|
|
|
|
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState);
|
|
if (ForcedSuccessExitState != null)
|
|
successExitState = ForcedSuccessExitState;
|
|
|
|
// Catch the abort request or command to idle on process exit
|
|
if (SingleProcessState == SingleProcessState.Abort || SingleProcessState == SingleProcessState.Idle)
|
|
{
|
|
// Log user abort for process before uninstalling the events for this process
|
|
ErrorMsgDispatcher($"{Resources.StrErrorMsgAbortProcess}: {ProcessName}");
|
|
|
|
return;
|
|
}
|
|
|
|
if (success == StatusReturn.Okay || success == StatusReturn.Warning || success == StatusReturn.Skipped)
|
|
{
|
|
// Change state only if allowed or on abort or idle request
|
|
if (successExitState != DoNotChangeStateAfterCompletion &&
|
|
SingleProcessState != SingleProcessState.Abort &&
|
|
SingleProcessState != SingleProcessState.Idle)
|
|
{
|
|
// Signal state change to state machine
|
|
OnForceStateMachineStateChange?.Invoke(this, new ProcessStateArgs(successExitState));
|
|
}
|
|
|
|
if (success == StatusReturn.Okay)
|
|
{
|
|
// log successfully execution of process before uninstalling the events for this process
|
|
SuccessMsgDispatcher($"{Resources.StrSuccessMsgProcess}: {ProcessName}");
|
|
// ATTENTION: This will uninstall all events for this process!
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Succeeded);
|
|
}
|
|
else if (success == StatusReturn.Skipped)
|
|
{
|
|
// log successfully execution of process before uninstalling the events for this process
|
|
SuccessMsgDispatcher($"{Resources.StrSkippedMsgProcess}: {ProcessName}");
|
|
// ATTENTION: This will uninstall all events for this process!
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Skipped);
|
|
}
|
|
else
|
|
{
|
|
// log successfully execution of process before uninstalling the events for this process
|
|
WarningMsgDispatcher($"{Resources.StrWarningMsgProcess}: {ProcessName}");
|
|
// ATTENTION: This will uninstall all events for this process!
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Warning);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// First signal to change the state machine because the single process state change uninstalls
|
|
// all events from process
|
|
OnForceStateMachineStateChange?.Invoke(this, new ProcessStateArgs(errorExitState));
|
|
// set message for error of process before uninstalling the events for this process
|
|
ErrorMsgDispatcher($"{Resources.StrErrorMsgProcess}: {ProcessName}");
|
|
|
|
// ATTENTION: This will uninstall all events for this process!
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common routine to convert objects to process states.
|
|
/// Objects are sorted:
|
|
/// 1. successState
|
|
/// 2. errorState
|
|
/// 3. breakState
|
|
/// </summary>
|
|
/// <param name="objects">input of object array</param>
|
|
/// <param name="successState">success state, default set to null meaning leave state as is</param>
|
|
/// <param name="errorState">error state, default set to error</param>
|
|
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
private static void ObjectsToProcessStates(IReadOnlyList<Object> objects, out ProcessState? successState,
|
|
out ProcessState errorState)
|
|
{
|
|
successState = DoNotChangeStateAfterCompletion;
|
|
errorState = ProcessState.Error;
|
|
|
|
// cast objects to process states sorted as success, error, break
|
|
if (objects.Count > 0 && objects[0] != null)
|
|
successState = (ProcessState)objects[0];
|
|
if (objects.Count > 1 && objects[1] != null)
|
|
errorState = (ProcessState)objects[1];
|
|
}
|
|
|
|
/// <summary>
|
|
/// New status for logging
|
|
/// </summary>
|
|
/// <param name="statusMessage"></param>
|
|
/// <param name="solidColorBrush">Brushes color to make the output colorful</param>
|
|
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
internal void LogNewStatusMsg(String statusMessage, SolidColorBrush solidColorBrush = null)
|
|
{
|
|
if (!IsSilentProcess)
|
|
OnProcessLogRequest?.Invoke(this, new ProcessLogArgs(statusMessage, solidColorBrush));
|
|
Meter?.WriteLog(statusMessage);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public virtual void ErrorMsgDispatcher(String errorMsg)
|
|
{
|
|
LogNewStatusMsg(errorMsg, Brushes.Red);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public virtual void WarningMsgDispatcher(String warningMsg)
|
|
{
|
|
LogNewStatusMsg(warningMsg, Brushes.Orange);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public virtual void SuccessMsgDispatcher(String successMsg)
|
|
{
|
|
LogNewStatusMsg(successMsg, Brushes.Green);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common routine to signal the successfully started running state and inform the UI.
|
|
/// </summary>
|
|
/// <param name="maxActualProcessProgressSteps"></param>
|
|
/// <remarks date="2023-Mar-15" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
protected void SignalRunningState(Int32 maxActualProcessProgressSteps = 1)
|
|
{
|
|
// set process state for this process and signal it to final controller
|
|
SetAndSignalProcessState(this, SingleProcessState.Running);
|
|
// set the progress bar on GUI
|
|
MaxActualProcessProgress = maxActualProcessProgressSteps;
|
|
ActualProcessProgress = 0;
|
|
|
|
// log and text to the GUI
|
|
LogNewStatusMsg($"{Resources.StrStartMsgProcess}: {ProcessName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process update event handler
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2025-Nov-17" author="Thomas Wiedebusch">
|
|
/// - StatusReturn.
|
|
/// </remarks>
|
|
protected void ProcessUpdate_Handler(Object sender, ProcessExecEventArgs e)
|
|
{
|
|
ActualProcessProgress++;
|
|
switch (e.StatusReturn)
|
|
{
|
|
case StatusReturn.Okay:
|
|
SuccessMsgDispatcher(e.ActualProcessMessage);
|
|
break;
|
|
case StatusReturn.Warning:
|
|
WarningMsgDispatcher(e.ActualProcessMessage);
|
|
break;
|
|
case StatusReturn.Failed:
|
|
ErrorMsgDispatcher(e.ActualProcessMessage);
|
|
break;
|
|
default:
|
|
LogNewStatusMsg(e.ActualProcessMessage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of user control getter in inherited class
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
protected virtual void InitUserControl()
|
|
{
|
|
}
|
|
|
|
#endregion --------------------------------------- Tools ------------------------------------------------------
|
|
}
|
|
}
|