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 { /// /// The base production process defines the tools needed to initiate, start and finalize a process. It holds the /// statistic counters. /// 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; } /// /// 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. /// public static readonly ProcessState? DoNotChangeStateAfterCompletion = null; /// /// Number of processing steps for this actual process progress /// private Int32 _maxActualProcessSteps; private Int32 MaxActualProcessProgress { get => _maxActualProcessSteps; set { _maxActualProcessSteps = value; OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxActualProcessSteps, ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps)); } } /// /// Progress of actual process /// 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)); } } /// public SingleProcessState SingleProcessState { get; private set; } /// /// 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. /// protected ProcessState? ForcedSuccessExitState { set; get; } /// public String ProcessName { get; } /// public Version Version => _version; /// public GenesisStatus ProductionStatus { set; protected get; } /// public CordonelRequirements ProductionRequirements { set; protected get; } /// public EOLProgressModel EolProgress { set; protected get; } /// public IGenesisMeter Meter { get; set; } /// public RegisterRestorer RegisterRestorer { protected get; set; } /// public List RadioEncryptionKey { protected get; set; } /// public MeterPwdDb PwdContainer { set; protected get; } /// /// Is for processes which do not want to log the execution or state change like ERROR process, /// protected Boolean IsSilentProcess { set; get; } // assembly version information private static readonly Version _version = Assembly.GetExecutingAssembly().GetName().Version; /// public CancellationToken CancellationToken { set; get; } /// public event EventHandler OnProcessStateChanged; /// public event EventHandler OnForceStateMachineStateChange; /// public event EventHandler OnProcessLogRequest; /// public event EventHandler OnProcessProgressChanged; /// public event EventHandler OnGuiMessageDispatcher; #endregion --------------------------------------- Variables -------------------------------------------------- #region ------------------------------------------ Init ------------------------------------------------------- /// /// Ctor: /// - Init. /// /// name for process to display a message /// /// - Initial /// protected BaseProductionProcess(String name) { ProcessName = name; SingleProcessState = SingleProcessState.Waiting; } #endregion --------------------------------------- Init ------------------------------------------------------- #region ------------------------------------------ Tools ------------------------------------------------------ /// /// Set overall process to 100 % /// protected void SetProcessProgressTo100Percent() { OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(Int32.MaxValue, ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps)); } /// 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; } /// /// Common routine for single register write, log of result and increase process counter. /// /// /// true if successfully /// /// - Initial /// /// /// - Inverse raw register bytes before conversion to text using the /// . /// 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; } /// /// Action for cancellation token to abort process /// /// /// - Initial /// protected StatusReturn CancellationProcedure() { AbortProcess(); return StatusReturn.Failed; } /// /// 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. /// /// /// /// /// /// - Initial /// /// /// - Set single process state for calling production process, /// - fire single process state event. /// /// /// - PreExecuteProcess. /// private void SetAndSignalProcessState(Object sender, SingleProcessState state) { SingleProcessState = state; OnProcessStateChanged?.Invoke(sender, new SingleProcessStateArgs(state)); } /// /// Fire event for GUI messages /// /// protected void DispatchGuiMessage(GuiMessageArgs args) { OnGuiMessageDispatcher?.Invoke(this, args); } /// 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); } /// public virtual void InitProcess() { // Set process state for this process and signal it to final controller SetAndSignalProcessState(this, SingleProcessState.Waiting); } /// public virtual void IdleProcess() { // set process state for this process and signal it to final controller SetAndSignalProcessState(this, SingleProcessState.Idle); } /// public virtual StatusReturn PreExecuteProcess() { return StatusReturn.Okay; } /// public virtual void AbortProcess() { // set process state for this process and signal it to final controller SetAndSignalProcessState(this, SingleProcessState.Abort); } /// public virtual void RetryProcess() { // set process state for this process and signal it to final controller SetAndSignalProcessState(this, SingleProcessState.Retry); } /// public abstract StatusReturn ExecuteProcess(); /// public virtual void FinalizeProcess(StatusReturn success, IReadOnlyList 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); } /// /// Common routine to convert objects to process states. /// Objects are sorted: /// 1. successState /// 2. errorState /// 3. breakState /// /// input of object array /// success state, default set to null meaning leave state as is /// error state, default set to error /// /// - Initial /// private static void ObjectsToProcessStates(IReadOnlyList 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]; } /// /// New status for logging /// /// /// Brushes color to make the output colorful /// /// - Initial /// internal void LogNewStatusMsg(String statusMessage, SolidColorBrush solidColorBrush = null) { if (!IsSilentProcess) OnProcessLogRequest?.Invoke(this, new ProcessLogArgs(statusMessage, solidColorBrush)); Meter?.WriteLog(statusMessage); } /// public virtual void ErrorMsgDispatcher(String errorMsg) { LogNewStatusMsg(errorMsg, Brushes.Red); } /// public virtual void WarningMsgDispatcher(String warningMsg) { LogNewStatusMsg(warningMsg, Brushes.Orange); } /// public virtual void SuccessMsgDispatcher(String successMsg) { LogNewStatusMsg(successMsg, Brushes.Green); } /// /// Common routine to signal the successfully started running state and inform the UI. /// /// /// /// - Initial /// 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}"); } /// /// Process update event handler /// /// /// /// /// - Initial /// /// /// - StatusReturn. /// 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; } } /// /// Implementation of user control getter in inherited class /// /// /// /// - Initial /// protected virtual void InitUserControl() { } #endregion --------------------------------------- Tools ------------------------------------------------------ } }