common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs
2026-04-23 17:50:07 +02:00

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 ------------------------------------------------------
}
}