From 1560282fad0d2c409a5561ef1962e65c850174ba Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 12:57:00 +0200 Subject: [PATCH 01/16] ProductionUiCordonel: - re-organized to support multiple production processes part 1 --- .../BaseProductionProcess.cs | 2 +- .../CheckOrderNumber.cs | 4 +-- .../CheckSerialNumber.cs | 4 +-- .../DetectCordonel.cs | 2 +- .../ErrorHandler.cs | 4 +-- .../EolProcesses/CheckAttachments.cs | 2 +- .../EolProcesses/CheckRadio.cs | 2 +- .../EolProcesses/ConnectCordonel.cs | 1 + .../EolProcesses/ExecPasswordFile.cs | 2 +- .../EolProcesses/GetFinalParametrization.cs | 1 + .../EolProcesses/PrepareShipping.cs | 2 +- .../EolProcesses/VisualInspection.cs | 2 +- .../ProductionProcesses/ProcessStateDef.cs | 1 + .../ProductionUiCordonel.csproj | 30 +++++++++---------- .../{FinalTest => }/IUserControl.cs | 2 +- .../{FinalTest => }/UcBarcodeInput.xaml | 2 +- .../{FinalTest => }/UcBarcodeInput.xaml.cs | 2 +- .../{FinalTest => }/UcDialog.xaml | 2 +- .../{FinalTest => }/UcDialog.xaml.cs | 2 +- .../{FinalTest => }/UcPasswordFile.xaml | 2 +- .../{FinalTest => }/UcPasswordFile.xaml.cs | 2 +- .../UserControls/{FinalTest => }/UcPlain.xaml | 2 +- .../{FinalTest => }/UcPlain.xaml.cs | 2 +- .../{FinalTest => }/UcRadioCheck.xaml | 2 +- .../{FinalTest => }/UcRadioCheck.xaml.cs | 2 +- 25 files changed, 42 insertions(+), 39 deletions(-) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/CheckOrderNumber.cs (96%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/CheckSerialNumber.cs (97%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/DetectCordonel.cs (98%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/ErrorHandler.cs (96%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/IUserControl.cs (93%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcBarcodeInput.xaml (94%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcBarcodeInput.xaml.cs (99%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcDialog.xaml (95%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcDialog.xaml.cs (99%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcPasswordFile.xaml (98%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcPasswordFile.xaml.cs (99%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcPlain.xaml (95%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcPlain.xaml.cs (98%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcRadioCheck.xaml (98%) rename Common/Production/ProductionUiCordonel/UserControls/{FinalTest => }/UcRadioCheck.xaml.cs (99%) diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs index 1eeae25d..741e8986 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs @@ -16,7 +16,7 @@ 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.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; using Xylem.Common.Utils.ProcessExec; using Xylem.Common.Utils.ProcessExec.EventArguments; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckOrderNumber.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs similarity index 96% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckOrderNumber.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs index 6cca551f..6eb900f3 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckOrderNumber.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs @@ -2,9 +2,9 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { public class CheckOrderNumber : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckSerialNumber.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs similarity index 97% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckSerialNumber.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs index f9fcf65e..586e1f5b 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckSerialNumber.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs @@ -2,9 +2,9 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { public class CheckSerialNumber : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/DetectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/DetectCordonel.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs index 79bbc83c..fef61032 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/DetectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs @@ -3,7 +3,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { /// /// Production process detects the serial number of the Cordonel. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ErrorHandler.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs similarity index 96% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ErrorHandler.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs index 12eaed5c..e333b9bd 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ErrorHandler.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { public class ErrorHandler : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckAttachments.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckAttachments.cs index 097ee268..5f95d63e 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckAttachments.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckAttachments.cs @@ -9,7 +9,7 @@ using Xylem.Common.Logic.ProductionOrderCore.Vako; using Xylem.Common.Logic.SoftwareAccessHelper; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs index 09ebe34b..ac3db873 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs @@ -14,7 +14,7 @@ using Xylem.Common.Logic.ProductionOrderCore; using Xylem.Common.Logic.SoftwareAccessHelper; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; using Button = System.Windows.Controls.Button; // ReSharper disable UnusedAutoPropertyAccessor.Local diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs index 56c83089..aa619be1 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs @@ -1,5 +1,6 @@ using System; using Xylem.Common.CommonCore.Consts; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs index e91e74fb..3d7f3bb4 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs @@ -10,7 +10,7 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.ProductionOrderCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; //using Xylem.Common.Ui.ProductionUiCordonel.UserControls.FinalTest; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs index 51f82cd0..9f72441f 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs @@ -7,6 +7,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.SoftwareAccessHelper; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/PrepareShipping.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/PrepareShipping.cs index 5ecdbfd7..d54f05e8 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/PrepareShipping.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/PrepareShipping.cs @@ -6,7 +6,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.Model; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/VisualInspection.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/VisualInspection.cs index b36634d4..feb6b362 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/VisualInspection.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/VisualInspection.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -using Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs index 592997c8..5cfecbdc 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 5737bff2..6f4ea12c 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -289,10 +289,10 @@ - + - + @@ -317,8 +317,8 @@ MultiModeChild.xaml - - + + @@ -347,20 +347,20 @@ UserAccessWindow.xaml - - + + UcBarcodeInput.xaml - + UcDialog.xaml - + UcRadioCheck.xaml - + UcPlain.xaml - + UcPasswordFile.xaml @@ -410,23 +410,23 @@ Designer MSBuild:Compile - + MSBuild:Compile Designer - + MSBuild:Compile Designer - + MSBuild:Compile Designer - + Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/IUserControl.cs b/Common/Production/ProductionUiCordonel/UserControls/IUserControl.cs similarity index 93% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/IUserControl.cs rename to Common/Production/ProductionUiCordonel/UserControls/IUserControl.cs index e902c173..2ee89196 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/IUserControl.cs +++ b/Common/Production/ProductionUiCordonel/UserControls/IUserControl.cs @@ -1,4 +1,4 @@ -namespace Xylem.Common.Production.ProductionUiCordonel.UserControls.FinalTest +namespace Xylem.Common.Production.ProductionUiCordonel.UserControls { public interface IUserControl { diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcBarcodeInput.xaml b/Common/Production/ProductionUiCordonel/UserControls/UcBarcodeInput.xaml similarity index 94% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcBarcodeInput.xaml rename to Common/Production/ProductionUiCordonel/UserControls/UcBarcodeInput.xaml index 5c762d95..ce5bb105 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcBarcodeInput.xaml +++ b/Common/Production/ProductionUiCordonel/UserControls/UcBarcodeInput.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for UcDialog.xaml diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcDialog.xaml b/Common/Production/ProductionUiCordonel/UserControls/UcDialog.xaml similarity index 95% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcDialog.xaml rename to Common/Production/ProductionUiCordonel/UserControls/UcDialog.xaml index e19bb492..6e1bc150 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcDialog.xaml +++ b/Common/Production/ProductionUiCordonel/UserControls/UcDialog.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for UcDialog.xaml diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPasswordFile.xaml b/Common/Production/ProductionUiCordonel/UserControls/UcPasswordFile.xaml similarity index 98% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPasswordFile.xaml rename to Common/Production/ProductionUiCordonel/UserControls/UcPasswordFile.xaml index 01a24fe8..c9c9e21f 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPasswordFile.xaml +++ b/Common/Production/ProductionUiCordonel/UserControls/UcPasswordFile.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for UcPasswordFile.xaml diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPlain.xaml b/Common/Production/ProductionUiCordonel/UserControls/UcPlain.xaml similarity index 95% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPlain.xaml rename to Common/Production/ProductionUiCordonel/UserControls/UcPlain.xaml index 14d5dba7..3eeccb38 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcPlain.xaml +++ b/Common/Production/ProductionUiCordonel/UserControls/UcPlain.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for UcPlain.xaml diff --git a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcRadioCheck.xaml b/Common/Production/ProductionUiCordonel/UserControls/UcRadioCheck.xaml similarity index 98% rename from Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcRadioCheck.xaml rename to Common/Production/ProductionUiCordonel/UserControls/UcRadioCheck.xaml index b5ae1c73..470d492f 100644 --- a/Common/Production/ProductionUiCordonel/UserControls/FinalTest/UcRadioCheck.xaml +++ b/Common/Production/ProductionUiCordonel/UserControls/UcRadioCheck.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for UcRadioCheck.xaml From 83fba2f1055af0b712d15255a71839460b3d250d Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 14:56:53 +0200 Subject: [PATCH 02/16] ProductionUiCordonel: - re-organized to support multiple production processes part 2 --- Common/.shared/SharedAssemblyInfo.cs | 2 +- .../ProductionUiCordonel/FinalTest.xaml.cs | 5 +- .../ProductionUiCordonel/Picking.xaml.cs | 5 +- ...Controller.cs => BaseProcessController.cs} | 356 +++--------------- .../ConnectCordonel.cs | 3 +- .../GetSerialNumberAndRadioAddress.cs | 2 +- .../EolProcesses/CheckProductionState.cs | 1 + .../EolProcesses/EolProcessController.cs | 314 +++++++++++++++ .../EolProcessStates.cs} | 9 +- .../EolProcesses/ExecPasswordFile.cs | 2 +- .../EolProcesses/GetFinalParametrization.cs | 2 +- .../ProductionProcesses/IProcessController.cs | 58 +++ .../ProductionUiCordonel.csproj | 10 +- 13 files changed, 450 insertions(+), 319 deletions(-) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{FinalProcessController.cs => BaseProcessController.cs} (62%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/ConnectCordonel.cs (94%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{EolProcesses => CommonProcesses}/GetSerialNumberAndRadioAddress.cs (99%) create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/{ProcessStateDef.cs => EolProcesses/EolProcessStates.cs} (98%) create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs diff --git a/Common/.shared/SharedAssemblyInfo.cs b/Common/.shared/SharedAssemblyInfo.cs index bcca328f..6c4ebd47 100644 --- a/Common/.shared/SharedAssemblyInfo.cs +++ b/Common/.shared/SharedAssemblyInfo.cs @@ -14,4 +14,4 @@ using System.Reflection; //[assembly: AssemblyVersion("1.2.*.0")] -[assembly: AssemblyVersion("2.6.16.*")] +[assembly: AssemblyVersion("2.7.0.*")] diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs index 96393683..4e1fae51 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs +++ b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs @@ -21,6 +21,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -53,7 +54,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private FinalProcessController _processCtrl; + private BaseProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -177,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new FinalProcessController(Slot); + _processCtrl = new EolProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/Picking.xaml.cs b/Common/Production/ProductionUiCordonel/Picking.xaml.cs index 5aee7568..72e42923 100644 --- a/Common/Production/ProductionUiCordonel/Picking.xaml.cs +++ b/Common/Production/ProductionUiCordonel/Picking.xaml.cs @@ -21,6 +21,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -53,7 +54,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private FinalProcessController _processCtrl; + private BaseProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -177,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new FinalProcessController(Slot); + _processCtrl = new EolProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs similarity index 62% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs index 408c53a3..8ddac59b 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/FinalProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs @@ -8,27 +8,20 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Timer = System.Threading.Timer; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { /// - /// Preparing the Cordonel for shipping: - /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, - /// - If radio is installed: radio test, - /// - Requiring radio key from LuDB, - /// - Requiring 7 passwords for the Cordonel access levels, - /// - Building the password file (Level 3 is the modified skeleton key), - /// - Store all configurations, - /// - Reboot the Cordonel to load all settings from FLASH to RAM, - /// - Configuration check of all parameters defined by VAKO and CSD. + /// Implementation for all methods needed to drive the state machine of the process controller /// - public sealed class FinalProcessController + public abstract class BaseProcessController : IProcessController { #region ------------------------------------------ Variables -------------------------------------------------- // actual genesis meter - private IGenesisMeter _genesisMeter; + protected IGenesisMeter _genesisMeter; // Cordonel requirements for production private CordonelRequirements _cordonelRequirements; // EOL progress states for production @@ -38,19 +31,20 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // batch just used for setup, as only one meter will be assigned private MeterBatch _meterBatch; // state of the state machine - private ProcessState _stateMachineState; + protected ProcessState _stateMachineState; // locker to avoid repeated state execution - private ProcessState _lockedProcessState; + protected ProcessState _lockedProcessState; // sender of state change event to evaluate in e.g. error process - private IProductionProcess _stateChangeRequestProcess; + protected IProductionProcess _stateChangeRequestProcess; // List of production processes public List Processes { - get; private set; + get; + private set; } // Container for production process states - private ProcessStateDef _processStateDef; + protected EolProcessStates _processStateDef; // Container for production status information, a list is used to keep the content over all classes of production // processes as a reference seems not to return their value (always reset to the initial value) @@ -69,24 +63,36 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // reminder for slot private readonly Int32 _slot; - // processing retries - private const Int32 MaxProcessRetries = 2; - //private static Int32 _processRetryCtr; - - //event for process list change or state of single process step change + /// + /// Change the overview table of the GUI to the state of the individual process + /// public event EventHandler OnSingleProcessStateChanged; + + /// + /// The overall process chain changed needed to signal to the GUI + /// - Can be forced by the state machine to invoke, + /// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next + /// required sate after break, error or completion to change the state of the state machine. + /// public event EventHandler OnOverallProcessStateChanged; + + /// + /// Request for logging to GUI output and log-file + /// public event EventHandler OnProcessLogRequest; - // progress bar and screen info of ongoing processes + + /// + /// Keep the progress bars of the GUI up to date + /// public event EventHandler OnProcessProgressChanged; // cancellation token - private CancellationTokenSource _cancellationTokenSource; - private CancellationToken _cancellationToken; + protected CancellationTokenSource _cancellationTokenSource; + protected CancellationToken _cancellationToken; // cyclic timer for state machine call private Timer _processCtrlTimer; - private const Int32 ProcessTimerCycleMs = 250; + protected const Int32 ProcessTimerCycleMs = 250; // defines for state machine /// @@ -111,7 +117,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Always RegisterWatchService on, /// - Always MinMaxCHeck on. /// - public FinalProcessController(Int32 slot) + protected BaseProcessController(Int32 slot) { _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); _slot = slot; @@ -248,6 +254,20 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses OnProcessLogRequest?.Invoke(sender, e); } + /// + /// Wrapper to fire this event from a derived class + /// + /// + /// + /// + /// - Initial + /// + protected void OverallProcessStateChanged(Object sender, ProcessStateArgs e) + { + // signal state change to logger + OnOverallProcessStateChanged?.Invoke(sender, e); + } + /// /// Handler to set a new state for the state machine regarding the entire final-process /// @@ -282,272 +302,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses #endregion --------------------------------------- Events ----------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- - /// - /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). - /// - /// - /// INSTRUCTION FOR NORMAL USAGE: - /// ----------------------------- - /// Call the SetupProcess() with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState set to the next required state after successfully execution. - /// That's it! The normal state change has always only one production process active and waits for completion - /// before changing to a new state and therefore a new production process. - /// - /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the - /// errorExitState is linked to the error state, the breakExitState to the stop state. - /// - /// - /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: - /// -------------------------------------------------------------------------- - /// Sometimes production processes may be able to be executed simultaneously as they are independent of each - /// other. One example is a database access for data collection from different tables. - /// - /// Call the SetupProcess with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState being set to (null). - /// This will lock the sate change after completion as multiple processes may be active and a state change after - /// completion may cause unpredictable operation, the state is already where it should be (manually changed), - /// - change the state after the SetupProcess() call manually. - /// - /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of - /// the actual new assigned process): - /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without - /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully - /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait - /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! - /// - /// - /// INSTRUCTION TO DELAY THE START OF A PROCESS: - /// -------------------------------------------- - /// Each new process execution can be delayed as it may need the result of another process which is active. - /// - /// Wait for a single process to complete: - /// - set the "waitForSingleProcess" to the process to wait for. - /// Wait for all preceding processes to complete: - /// - set the "waitForAllProcesses" to true. - /// - /// - /// - /// - Initial - /// - private void StateMachine() - { - // avoid repeated execution of the state machine on unchanged state - if (_stateMachineState == _lockedProcessState) - { - Thread.Sleep(ProcessTimerCycleMs >> 1); - } - else - { - try - { - // remind backup state to avoid repeated execution and side effects - _lockedProcessState = _stateMachineState; - // signal overall process progress to GUI based on ready processes - CalcAndPushOverAllProgress(); - - // signal state change to logger - OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); - - // get the actual required process from the state definition list - var process = _processStateDef?.GetProcessOfState(_stateMachineState); - - switch (_stateMachineState) - { - case ProcessState.Idle: - // This state needs an external state change like user input to go ahead - break; - - case ProcessState.DetectCordonel: - // Wait for cancellation - if (_cancellationToken.IsCancellationRequested) - { - Thread.Sleep(1000); - // Reset the cancellation request - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - _cancellationToken = _cancellationTokenSource.Token; - } - - // Set up the counter for maximum overall process progress bar in GUI - CalcAndPushMaxOverAllProcessSteps(); - - // Detection of Cordonel has to be completed before assigning next state, so leave to automatic - // to change the state after successfully execution. The detection is the base for all others. - StartProcess(process, ProcessState.CheckNewMeter); - break; - - case ProcessState.CheckNewMeter: - // Detection of Cordonel has to be completed before assigning next state. It has to be checked if - // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after - // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then - // enter the "ConnectCordonel"! - _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; - break; - - case ProcessState.ConnectCordonel: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; - break; - - case ProcessState.GetSerialNumberAndRadioAddress: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetProgrammingParameters; - break; - - case ProcessState.GetProgrammingParameters: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.CheckProductionState; - break; - - case ProcessState.CheckProductionState: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.ProcessPasswordFile; - break; - - case ProcessState.ProcessPasswordFile: - // Precondition: - The connection process has to be completed as a password change in the DB - // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side - // effects on login. Wait for all processes to complete. - // Leave state switch to automatic to avoid side effects on incomplete updated DB for next - // steps. - StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); - break; - - case ProcessState.ExecFinalParametrization: - // This writes all setups to the device, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.ExecStoreConfiguration); - break; - - case ProcessState.ExecStoreConfiguration: - // Precondition: - The final parametrization has to be finished as this shall be stored - // to non-volatile memory and checked if remaining valid after the reboot. - // Store configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.RebootCordonel); - break; - - case ProcessState.RebootCordonel: - // Precondition: - The store configuration has to be executed to safe all settings - // to non-volatile memory and checked if remaining valid after the reboot. - // Reboot has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckFinalParametrization); - break; - - case ProcessState.CheckFinalParametrization: - // Precondition: - The reboot process including the store all configurations has to be - // completed. - // Check final configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckRadio); - break; - - case ProcessState.CheckRadio: - // Precondition: - The final parametrization, reboot process including the store all - // configurations and the check of all setups survived the reboot have to be completed. - // Radio signal level check, skipped on NA region or if skipping is required by special - // approval - StartProcess(process, ProcessState.CheckOrderNumber); - break; - - case ProcessState.CheckOrderNumber: - // This scans the order number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); - break; - - case ProcessState.CheckSerialNumber: - // This scans the serial number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckAttachments); - break; - - case ProcessState.CheckAttachments: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ExecVisualInspection); - break; - - case ProcessState.ExecVisualInspection: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.PrepareShippingMode); - break; - - case ProcessState.PrepareShippingMode: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ReadyForShipping); - break; - - case ProcessState.ReadyForShipping: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.RepeatFailedTests: - _stateMachineState = RepeatFailedTests(); - break; - - case ProcessState.PrintReturnReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.PrintSuccessReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.AbortFinalTest: - AbortProcesses(); - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.Stop: - KillProcesses(); - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.Error: - // Common error handling routine - ProcessErrorHandler(_stateChangeRequestProcess); - // Start the error handler with its user feedback request. It has to be started - // always even if the abortion of all processes is required! The error handler - // will change the exit state based on the user input. - StartProcess(process, ProcessState.Idle, startAlways: true); - break; - - case ProcessState.StateListDelimiter: - default: - _stateMachineState = ProcessState.Idle; - break; - }// - } - catch (ThreadAbortException ex) - { - _genesisMeter?.WriteLog(ex.Message); - _stateMachineState = ProcessState.Idle; - } - catch (Exception ex) - { - _genesisMeter?.WriteLog(ex.Message); - _stateMachineState = ProcessState.Error; - } - } - }// state locked against repeated execution + public abstract void StateMachine(); #endregion --------------------------------------- State Machine ---------------------------------------------- #region ------------------------------------------ Tools ------------------------------------------------------ @@ -588,7 +344,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Handle genesis meter is null as new meter. /// - private Boolean CheckForNewMeter() + protected Boolean CheckForNewMeter() { if (_genesisMeter == null || (!string.IsNullOrEmpty(_lastPcbId) && @@ -626,7 +382,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Leave skipped processes as is. /// - private ProcessState RepeatFailedTests() + protected ProcessState RepeatFailedTests() { var state = ProcessState.Idle; @@ -668,7 +424,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Kick off exec parametrization if check after reboot failed. /// - private void ProcessErrorHandler(IProductionProcess process) + protected void ProcessErrorHandler(IProductionProcess process) { if (process == null) return; @@ -693,7 +449,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate the maximum of all processes expected to execute and signal to GUI. /// - private void CalcAndPushMaxOverAllProcessSteps() + protected void CalcAndPushMaxOverAllProcessSteps() { if (_processStateDef == null) return; @@ -707,7 +463,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate all processes which are completed or skipped and signal to GUI. /// - private void CalcAndPushOverAllProgress() + protected void CalcAndPushOverAllProgress() { if (Processes == null) return; @@ -736,7 +492,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses if (Processes == null) Processes = new List(); if (_processStateDef == null) - _processStateDef = new ProcessStateDef(); + _processStateDef = new EolProcessStates(); // get all processes relevant for EOL from table which has to be sorted var processes = _processStateDef.GetAllEolRelevantProcesses(); @@ -791,7 +547,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Changed setup process to start process being able to create a process list in advance. /// - private void StartProcess(IProductionProcess process, ProcessState? successExitState, + protected void StartProcess(IProductionProcess process, ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error, ProcessState breakExitState = ProcessState.Stop, IProductionProcess waitForSingleProcess = DoNotWaitForSingleProcessCompletion, Boolean waitForAllProcesses = false, Boolean startAlways = false) @@ -956,7 +712,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Cancellation token source. /// - private void KillProcesses() + protected void KillProcesses() { _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); @@ -978,7 +734,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - private void AbortProcesses() + protected void AbortProcesses() { if (Processes == null || Processes.Count == 0) return; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs similarity index 94% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs index aa619be1..a0fac18a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ConnectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs @@ -1,10 +1,9 @@ using System; using Xylem.Common.CommonCore.Consts; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { public class ConnectCordonel : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs index efe7c97d..562d993e 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetSerialNumberAndRadioAddress.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs @@ -8,7 +8,7 @@ using Xylem.Common.Logic.SoftwareAccessHelper; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses { /// /// Request the serial number and radio address from DB. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs index a5607356..72993b35 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs @@ -15,6 +15,7 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.ProductionOrderCore.OrderData; using Xylem.Common.Logic.ProductionOrderCore.TestResults; using Xylem.Common.Production.ProductionUiCordonel.Model; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs new file mode 100644 index 00000000..58041c78 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs @@ -0,0 +1,314 @@ +using System; +using System.Threading; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +{ + /// + /// Preparing the Cordonel for shipping: + /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, + /// - If radio is installed: radio test, + /// - Requiring radio key from LuDB, + /// - Requiring 7 passwords for the Cordonel access levels, + /// - Building the password file (Level 3 is the modified skeleton key), + /// - Store all configurations, + /// - Reboot the Cordonel to load all settings from FLASH to RAM, + /// - Configuration check of all parameters defined by VAKO and CSD. + /// + public sealed class EolProcessController : BaseProcessController, IProcessController + { + #region ------------------------------------------ Variables -------------------------------------------------- + // + #endregion --------------------------------------- Variables -------------------------------------------------- + #region ------------------------------------------ Class ------------------------------------------------------ + + /// + /// Ctor + /// + /// physical slot for this meter + /// + /// - Initial + /// + public EolProcessController(Int32 slot) : base(slot) + { + } + + #endregion --------------------------------------- Class ------------------------------------------------------ + #region ------------------------------------------ Events ----------------------------------------------------- + // + #endregion --------------------------------------- Events ----------------------------------------------------- + #region ------------------------------------------ State Machine ---------------------------------------------- + /// + /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// Call the SetupProcess() with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState set to the next required state after successfully execution. + /// That's it! The normal state change has always only one production process active and waits for completion + /// before changing to a new state and therefore a new production process. + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// errorExitState is linked to the error state, the breakExitState to the stop state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// Call the SetupProcess with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState being set to (null). + /// This will lock the sate change after completion as multiple processes may be active and a state change after + /// completion may cause unpredictable operation, the state is already where it should be (manually changed), + /// - change the state after the SetupProcess() call manually. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully + /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait + /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process to wait for. + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + /// + /// - Initial + /// + public override void StateMachine() + { + // avoid repeated execution of the state machine on unchanged state + if (_stateMachineState == _lockedProcessState) + { + Thread.Sleep(ProcessTimerCycleMs >> 1); + } + else + { + try + { + // remind backup state to avoid repeated execution and side effects + _lockedProcessState = _stateMachineState; + + // signal overall process progress to GUI based on ready processes + CalcAndPushOverAllProgress(); + + // signal state change to logger + OverallProcessStateChanged(this, new ProcessStateArgs(_stateMachineState)); + + // get the actual required process from the state definition list + var process = _processStateDef?.GetProcessOfState(_stateMachineState); + + switch (_stateMachineState) + { + case ProcessState.Idle: + // This state needs an external state change like user input to go ahead + break; + + case ProcessState.DetectCordonel: + // Wait for cancellation + if (_cancellationToken.IsCancellationRequested) + { + Thread.Sleep(1000); + // Reset the cancellation request + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + } + + // Set up the counter for maximum overall process progress bar in GUI + CalcAndPushMaxOverAllProcessSteps(); + + // Detection of Cordonel has to be completed before assigning next state, so leave to automatic + // to change the state after successfully execution. The detection is the base for all others. + StartProcess(process, ProcessState.CheckNewMeter); + break; + + case ProcessState.CheckNewMeter: + // Detection of Cordonel has to be completed before assigning next state. It has to be checked if + // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after + // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then + // enter the "ConnectCordonel"! + _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; + break; + + case ProcessState.ConnectCordonel: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; + break; + + case ProcessState.GetSerialNumberAndRadioAddress: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetProgrammingParameters; + break; + + case ProcessState.GetProgrammingParameters: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.CheckProductionState; + break; + + case ProcessState.CheckProductionState: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.ProcessPasswordFile; + break; + + case ProcessState.ProcessPasswordFile: + // Precondition: - The connection process has to be completed as a password change in the DB + // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side + // effects on login. Wait for all processes to complete. + // Leave state switch to automatic to avoid side effects on incomplete updated DB for next + // steps. + StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); + break; + + case ProcessState.ExecFinalParametrization: + // This writes all setups to the device, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.ExecStoreConfiguration); + break; + + case ProcessState.ExecStoreConfiguration: + // Precondition: - The final parametrization has to be finished as this shall be stored + // to non-volatile memory and checked if remaining valid after the reboot. + // Store configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.RebootCordonel); + break; + + case ProcessState.RebootCordonel: + // Precondition: - The store configuration has to be executed to safe all settings + // to non-volatile memory and checked if remaining valid after the reboot. + // Reboot has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckFinalParametrization); + break; + + case ProcessState.CheckFinalParametrization: + // Precondition: - The reboot process including the store all configurations has to be + // completed. + // Check final configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckRadio); + break; + + case ProcessState.CheckRadio: + // Precondition: - The final parametrization, reboot process including the store all + // configurations and the check of all setups survived the reboot have to be completed. + // Radio signal level check, skipped on NA region or if skipping is required by special + // approval + StartProcess(process, ProcessState.CheckOrderNumber); + break; + + case ProcessState.CheckOrderNumber: + // This scans the order number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); + break; + + case ProcessState.CheckSerialNumber: + // This scans the serial number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckAttachments); + break; + + case ProcessState.CheckAttachments: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ExecVisualInspection); + break; + + case ProcessState.ExecVisualInspection: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.PrepareShippingMode); + break; + + case ProcessState.PrepareShippingMode: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ReadyForShipping); + break; + + case ProcessState.ReadyForShipping: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.RepeatFailedTests: + _stateMachineState = RepeatFailedTests(); + break; + + case ProcessState.PrintReturnReport: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.PrintSuccessReport: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.AbortFinalTest: + AbortProcesses(); + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.Stop: + KillProcesses(); + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.Error: + // Common error handling routine + ProcessErrorHandler(_stateChangeRequestProcess); + // Start the error handler with its user feedback request. It has to be started + // always even if the abortion of all processes is required! The error handler + // will change the exit state based on the user input. + StartProcess(process, ProcessState.Idle, startAlways: true); + break; + + case ProcessState.StateListDelimiter: + default: + _stateMachineState = ProcessState.Idle; + break; + }// + } + catch (ThreadAbortException ex) + { + _genesisMeter?.WriteLog(ex.Message); + _stateMachineState = ProcessState.Idle; + } + catch (Exception ex) + { + _genesisMeter?.WriteLog(ex.Message); + _stateMachineState = ProcessState.Error; + } + } + }// state locked against repeated execution + + #endregion --------------------------------------- State Machine ---------------------------------------------- + #region ------------------------------------------ Tools ------------------------------------------------------ + // + #endregion --------------------------------------- Tools ------------------------------------------------------ + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index 5cfecbdc..b7c8b4c9 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Linq; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; -using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { /// /// Concatenation for all production process states: @@ -17,7 +16,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - public class ProcessStateDef + public class EolProcessStates { /// /// Collection of state information @@ -31,7 +30,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Sort the active steps from first til last step as this is the order as those will be sorted in the /// process list UI. /// - public ProcessStateDef() + public EolProcessStates() { _stateContainer = new List { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs index 3d7f3bb4..11d6bcf6 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs @@ -66,7 +66,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if passwords are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs index 9f72441f..2a322f9a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs @@ -39,7 +39,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if programming parameters are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs new file mode 100644 index 00000000..26615686 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs @@ -0,0 +1,58 @@ +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +{ + public interface IProcessController + { + /// + /// State machine of ProcessController which serves as template for all production processes. + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// Call the SetupProcess() with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState set to the next required state after successfully execution. + /// That's it! The normal state change has always only one production process active and waits for completion + /// before changing to a new state and therefore a new production process. + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// errorExitState is linked to the error state, the breakExitState to the stop state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// Call the SetupProcess with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState being set to (null). + /// This will lock the sate change after completion as multiple processes may be active and a state change after + /// completion may cause unpredictable operation, the state is already where it should be (manually changed), + /// - change the state after the SetupProcess() call manually. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully + /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait + /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process to wait for. + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + /// + /// - Initial + /// + void StateMachine(); + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 6f4ea12c..767a4554 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -291,7 +291,7 @@ - + @@ -299,7 +299,9 @@ - + + + @@ -325,12 +327,12 @@ - + - + From 8909602dac4d069e1dde8e1f646b568728252990 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 15:39:22 +0200 Subject: [PATCH 03/16] ProductionUiCordonel: - re-organized to support multiple production processes part 3 --- .../ProductionUiCordonel/FinalTest.xaml.cs | 2 +- .../ProductionUiCordonel/Picking.xaml.cs | 2 +- .../BaseProcessController.cs | 191 ++++++++++- .../BaseProductionProcess.cs | 2 +- .../ProductionProcesses/Enum/ProcessState.cs | 8 +- .../EolProcesses/CheckProductionState.cs | 2 +- .../EolProcesses/EolProcessController.cs | 314 ------------------ .../EolProcesses/EolProcessStates.cs | 6 +- .../EolProcesses/EolStateMachine.cs | 148 +++++++++ .../EolProcesses/GetFinalParametrization.cs | 2 +- .../CheckOrderNumber.cs | 2 +- .../CheckSerialNumber.cs | 2 +- .../ConnectCordonel.cs | 2 +- .../DetectCordonel.cs | 2 +- .../ErrorHandler.cs | 2 +- .../GetSerialNumberAndRadioAddress.cs | 2 +- .../ProductionProcesses/IProcessController.cs | 56 +--- .../ProductionUiCordonel.csproj | 14 +- 18 files changed, 367 insertions(+), 392 deletions(-) delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/CheckOrderNumber.cs (99%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/CheckSerialNumber.cs (99%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/ConnectCordonel.cs (98%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/DetectCordonel.cs (98%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/ErrorHandler.cs (99%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{CommonProcesses => GenericProcesses}/GetSerialNumberAndRadioAddress.cs (99%) diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs index 4e1fae51..6fecf460 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs +++ b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs @@ -178,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new EolProcessController(Slot); + _processCtrl = new EolStateMachine(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/Picking.xaml.cs b/Common/Production/ProductionUiCordonel/Picking.xaml.cs index 72e42923..7ec51aff 100644 --- a/Common/Production/ProductionUiCordonel/Picking.xaml.cs +++ b/Common/Production/ProductionUiCordonel/Picking.xaml.cs @@ -178,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new EolProcessController(Slot); + _processCtrl = new EolStateMachine(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs index 8ddac59b..c3783ef1 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs @@ -297,13 +297,200 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// private void TmrProcessCtrlCycle_Elapsed(Object state) { - StateMachine(); + GenericStateMachine(); } #endregion --------------------------------------- Events ----------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- - public abstract void StateMachine(); + /// + public abstract void IndividualStateMachine(IProductionProcess process); + + /// + /// State machine of ProcessController which serves as template for all production processes. + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// Call the SetupProcess() with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState set to the next required state after successfully execution. + /// That's it! The normal state change has always only one production process active and waits for completion + /// before changing to a new state and therefore a new production process. + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// errorExitState is linked to the error state, the breakExitState to the stop state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// Call the SetupProcess with the + /// - new process automatically collected from with new + /// _actualProcessState as input, + /// - successExitState being set to (null). + /// This will lock the sate change after completion as multiple processes may be active and a state change after + /// completion may cause unpredictable operation, the state is already where it should be (manually changed), + /// - change the state after the SetupProcess() call manually. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully + /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait + /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process to wait for. + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + /// + /// - Initial + /// + private void GenericStateMachine() + {// avoid repeated execution of the state machine on unchanged state + if (_stateMachineState == _lockedProcessState) + { + Thread.Sleep(ProcessTimerCycleMs >> 1); + } + else + { + try + { + // remind backup state to avoid repeated execution and side effects + _lockedProcessState = _stateMachineState; + + // signal overall process progress to GUI based on ready processes + CalcAndPushOverAllProgress(); + + // signal state change to logger + OverallProcessStateChanged(this, new ProcessStateArgs(_stateMachineState)); + + // get the actual required process from the state definition list + var process = _processStateDef?.GetProcessOfState(_stateMachineState); + + switch (_stateMachineState) + { + case ProcessState.Idle: + // This state needs an external state change like user input to go ahead + break; + + case ProcessState.DetectCordonel: + // Wait for cancellation + if (_cancellationToken.IsCancellationRequested) + { + Thread.Sleep(1000); + // Reset the cancellation request + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + } + + // Set up the counter for maximum overall process progress bar in GUI + CalcAndPushMaxOverAllProcessSteps(); + + // Detection of Cordonel has to be completed before assigning next state, so leave to automatic + // to change the state after successfully execution. The detection is the base for all others. + StartProcess(process, ProcessState.CheckNewMeter); + break; + + case ProcessState.CheckNewMeter: + // Detection of Cordonel has to be completed before assigning next state. It has to be checked if + // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after + // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then + // enter the "ConnectCordonel"! + _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; + break; + + case ProcessState.ConnectCordonel: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; + break; + + case ProcessState.GetSerialNumberAndRadioAddress: + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.GetProgrammingParameters; + break; + + case ProcessState.CheckOrderNumber: + // This scans the order number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); + break; + + case ProcessState.CheckSerialNumber: + // This scans the serial number, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckAttachments); + break; + + case ProcessState.RepeatFailedTests: + _stateMachineState = RepeatFailedTests(); + break; + + case ProcessState.PrintReturnReport: + _stateMachineState = ProcessState.Idle; + break; + + case ProcessState.PrintSuccessReport: + _stateMachineState = ProcessState.Idle; + 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(process, ProcessState.Idle, startAlways: true); + break; + + case ProcessState.StateListDelimiter: + _stateMachineState = ProcessState.Idle; + break; + + default: + IndividualStateMachine(process); + 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 ------------------------------------------------------ diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs index 741e8986..79360a99 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProductionProcess.cs @@ -292,7 +292,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// public void StartProcess(ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error, - ProcessState breakExitState = ProcessState.AbortFinalTest) + ProcessState breakExitState = ProcessState.AbortTest) { if (Meter == null) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs index fd68e028..b15cb83e 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs @@ -19,17 +19,17 @@ /// Error, /// - /// Abort final test execution + /// Abort test execution /// - AbortFinalTest, + AbortTest, /// - /// Repeat the entire final test + /// Repeat the entire test /// RepeatFailedTests, /// /// Final test failed /// - FinalTestFailed, + TestFailed, /// /// User stopped process /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs index 72993b35..bc596ccf 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs @@ -15,8 +15,8 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.ProductionOrderCore.OrderData; using Xylem.Common.Logic.ProductionOrderCore.TestResults; using Xylem.Common.Production.ProductionUiCordonel.Model; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs deleted file mode 100644 index 58041c78..00000000 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessController.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Threading; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; - -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses -{ - /// - /// Preparing the Cordonel for shipping: - /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, - /// - If radio is installed: radio test, - /// - Requiring radio key from LuDB, - /// - Requiring 7 passwords for the Cordonel access levels, - /// - Building the password file (Level 3 is the modified skeleton key), - /// - Store all configurations, - /// - Reboot the Cordonel to load all settings from FLASH to RAM, - /// - Configuration check of all parameters defined by VAKO and CSD. - /// - public sealed class EolProcessController : BaseProcessController, IProcessController - { - #region ------------------------------------------ Variables -------------------------------------------------- - // - #endregion --------------------------------------- Variables -------------------------------------------------- - #region ------------------------------------------ Class ------------------------------------------------------ - - /// - /// Ctor - /// - /// physical slot for this meter - /// - /// - Initial - /// - public EolProcessController(Int32 slot) : base(slot) - { - } - - #endregion --------------------------------------- Class ------------------------------------------------------ - #region ------------------------------------------ Events ----------------------------------------------------- - // - #endregion --------------------------------------- Events ----------------------------------------------------- - #region ------------------------------------------ State Machine ---------------------------------------------- - /// - /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). - /// - /// - /// INSTRUCTION FOR NORMAL USAGE: - /// ----------------------------- - /// Call the SetupProcess() with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState set to the next required state after successfully execution. - /// That's it! The normal state change has always only one production process active and waits for completion - /// before changing to a new state and therefore a new production process. - /// - /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the - /// errorExitState is linked to the error state, the breakExitState to the stop state. - /// - /// - /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: - /// -------------------------------------------------------------------------- - /// Sometimes production processes may be able to be executed simultaneously as they are independent of each - /// other. One example is a database access for data collection from different tables. - /// - /// Call the SetupProcess with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState being set to (null). - /// This will lock the sate change after completion as multiple processes may be active and a state change after - /// completion may cause unpredictable operation, the state is already where it should be (manually changed), - /// - change the state after the SetupProcess() call manually. - /// - /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of - /// the actual new assigned process): - /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without - /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully - /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait - /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! - /// - /// - /// INSTRUCTION TO DELAY THE START OF A PROCESS: - /// -------------------------------------------- - /// Each new process execution can be delayed as it may need the result of another process which is active. - /// - /// Wait for a single process to complete: - /// - set the "waitForSingleProcess" to the process to wait for. - /// Wait for all preceding processes to complete: - /// - set the "waitForAllProcesses" to true. - /// - /// - /// - /// - Initial - /// - public override void StateMachine() - { - // avoid repeated execution of the state machine on unchanged state - if (_stateMachineState == _lockedProcessState) - { - Thread.Sleep(ProcessTimerCycleMs >> 1); - } - else - { - try - { - // remind backup state to avoid repeated execution and side effects - _lockedProcessState = _stateMachineState; - - // signal overall process progress to GUI based on ready processes - CalcAndPushOverAllProgress(); - - // signal state change to logger - OverallProcessStateChanged(this, new ProcessStateArgs(_stateMachineState)); - - // get the actual required process from the state definition list - var process = _processStateDef?.GetProcessOfState(_stateMachineState); - - switch (_stateMachineState) - { - case ProcessState.Idle: - // This state needs an external state change like user input to go ahead - break; - - case ProcessState.DetectCordonel: - // Wait for cancellation - if (_cancellationToken.IsCancellationRequested) - { - Thread.Sleep(1000); - // Reset the cancellation request - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = new CancellationTokenSource(); - _cancellationToken = _cancellationTokenSource.Token; - } - - // Set up the counter for maximum overall process progress bar in GUI - CalcAndPushMaxOverAllProcessSteps(); - - // Detection of Cordonel has to be completed before assigning next state, so leave to automatic - // to change the state after successfully execution. The detection is the base for all others. - StartProcess(process, ProcessState.CheckNewMeter); - break; - - case ProcessState.CheckNewMeter: - // Detection of Cordonel has to be completed before assigning next state. It has to be checked if - // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after - // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then - // enter the "ConnectCordonel"! - _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; - break; - - case ProcessState.ConnectCordonel: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; - break; - - case ProcessState.GetSerialNumberAndRadioAddress: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetProgrammingParameters; - break; - - case ProcessState.GetProgrammingParameters: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.CheckProductionState; - break; - - case ProcessState.CheckProductionState: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.ProcessPasswordFile; - break; - - case ProcessState.ProcessPasswordFile: - // Precondition: - The connection process has to be completed as a password change in the DB - // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side - // effects on login. Wait for all processes to complete. - // Leave state switch to automatic to avoid side effects on incomplete updated DB for next - // steps. - StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); - break; - - case ProcessState.ExecFinalParametrization: - // This writes all setups to the device, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.ExecStoreConfiguration); - break; - - case ProcessState.ExecStoreConfiguration: - // Precondition: - The final parametrization has to be finished as this shall be stored - // to non-volatile memory and checked if remaining valid after the reboot. - // Store configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.RebootCordonel); - break; - - case ProcessState.RebootCordonel: - // Precondition: - The store configuration has to be executed to safe all settings - // to non-volatile memory and checked if remaining valid after the reboot. - // Reboot has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckFinalParametrization); - break; - - case ProcessState.CheckFinalParametrization: - // Precondition: - The reboot process including the store all configurations has to be - // completed. - // Check final configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckRadio); - break; - - case ProcessState.CheckRadio: - // Precondition: - The final parametrization, reboot process including the store all - // configurations and the check of all setups survived the reboot have to be completed. - // Radio signal level check, skipped on NA region or if skipping is required by special - // approval - StartProcess(process, ProcessState.CheckOrderNumber); - break; - - case ProcessState.CheckOrderNumber: - // This scans the order number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); - break; - - case ProcessState.CheckSerialNumber: - // This scans the serial number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckAttachments); - break; - - case ProcessState.CheckAttachments: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ExecVisualInspection); - break; - - case ProcessState.ExecVisualInspection: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.PrepareShippingMode); - break; - - case ProcessState.PrepareShippingMode: - // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ReadyForShipping); - break; - - case ProcessState.ReadyForShipping: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.RepeatFailedTests: - _stateMachineState = RepeatFailedTests(); - break; - - case ProcessState.PrintReturnReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.PrintSuccessReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.AbortFinalTest: - AbortProcesses(); - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.Stop: - KillProcesses(); - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.Error: - // Common error handling routine - ProcessErrorHandler(_stateChangeRequestProcess); - // Start the error handler with its user feedback request. It has to be started - // always even if the abortion of all processes is required! The error handler - // will change the exit state based on the user input. - StartProcess(process, ProcessState.Idle, startAlways: true); - break; - - case ProcessState.StateListDelimiter: - default: - _stateMachineState = ProcessState.Idle; - break; - }// - } - catch (ThreadAbortException ex) - { - _genesisMeter?.WriteLog(ex.Message); - _stateMachineState = ProcessState.Idle; - } - catch (Exception ex) - { - _genesisMeter?.WriteLog(ex.Message); - _stateMachineState = ProcessState.Error; - } - } - }// state locked against repeated execution - - #endregion --------------------------------------- State Machine ---------------------------------------------- - #region ------------------------------------------ Tools ------------------------------------------------------ - // - #endregion --------------------------------------- Tools ------------------------------------------------------ - } -} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index b7c8b4c9..5652ecf7 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses @@ -60,7 +60,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr new ProcessStateStruct( null, - ProcessState.AbortFinalTest, + ProcessState.AbortTest, Resources.StrStateSkipping, false), @@ -72,7 +72,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr new ProcessStateStruct( null, - ProcessState.FinalTestFailed, + ProcessState.TestFailed, Resources.StrStateFinalTestFailed, false), diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs new file mode 100644 index 00000000..62c3fcd1 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs @@ -0,0 +1,148 @@ +using System; +using System.Threading; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +{ + /// + /// Preparing the Cordonel for shipping: + /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, + /// - If radio is installed: radio test, + /// - Requiring radio key from LuDB, + /// - Requiring 7 passwords for the Cordonel access levels, + /// - Building the password file (Level 3 is the modified skeleton key), + /// - Store all configurations, + /// - Reboot the Cordonel to load all settings from FLASH to RAM, + /// - Configuration check of all parameters defined by VAKO and CSD. + /// + public sealed class EolStateMachine : BaseProcessController, IProcessController + { + #region ------------------------------------------ Variables -------------------------------------------------- + // + #endregion --------------------------------------- Variables -------------------------------------------------- + #region ------------------------------------------ Class ------------------------------------------------------ + + /// + /// Ctor + /// + /// physical slot for this meter + /// + /// - Initial + /// + public EolStateMachine(Int32 slot) : base(slot) + { + } + + #endregion --------------------------------------- Class ------------------------------------------------------ + #region ------------------------------------------ Events ----------------------------------------------------- + // + #endregion --------------------------------------- Events ----------------------------------------------------- + #region ------------------------------------------ State Machine ---------------------------------------------- + /// + /// + /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). + /// + /// + /// - Initial + /// + public override void IndividualStateMachine(IProductionProcess process) + { + switch (_stateMachineState) + { + case ProcessState.GetProgrammingParameters: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.CheckProductionState; + break; + + case ProcessState.CheckProductionState: + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, + waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + // Do not wait on completion as new parallel process can be started by manually state change. + _stateMachineState = ProcessState.ProcessPasswordFile; + break; + + case ProcessState.ProcessPasswordFile: + // Precondition: - The connection process has to be completed as a password change in the DB + // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side + // effects on login. Wait for all processes to complete. + // Leave state switch to automatic to avoid side effects on incomplete updated DB for next + // steps. + StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); + break; + + case ProcessState.ExecFinalParametrization: + // This writes all setups to the device, it has to be executed exclusively with automatic + StartProcess(process, ProcessState.ExecStoreConfiguration); + break; + + case ProcessState.ExecStoreConfiguration: + // Precondition: - The final parametrization has to be finished as this shall be stored + // to non-volatile memory and checked if remaining valid after the reboot. + // Store configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.RebootCordonel); + break; + + case ProcessState.RebootCordonel: + // Precondition: - The store configuration has to be executed to safe all settings + // to non-volatile memory and checked if remaining valid after the reboot. + // Reboot has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckFinalParametrization); + break; + + case ProcessState.CheckFinalParametrization: + // Precondition: - The reboot process including the store all configurations has to be + // completed. + // Check final configuration has to be executed exclusively with automatic + StartProcess(process, ProcessState.CheckRadio); + break; + + case ProcessState.CheckRadio: + // Precondition: - The final parametrization, reboot process including the store all + // configurations and the check of all setups survived the reboot have to be completed. + // Radio signal level check, skipped on NA region or if skipping is required by special + // approval + StartProcess(process, ProcessState.CheckOrderNumber); + break; + + case ProcessState.CheckAttachments: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ExecVisualInspection); + break; + + case ProcessState.ExecVisualInspection: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.PrepareShippingMode); + break; + + case ProcessState.PrepareShippingMode: + // Precondition: - All checks have to be executed with positive result + StartProcess(process, ProcessState.ReadyForShipping); + break; + + case ProcessState.ReadyForShipping: + _stateMachineState = ProcessState.Idle; + break; + + default: + _stateMachineState = ProcessState.Idle; + break; + } + } + + #endregion --------------------------------------- State Machine ---------------------------------------------- + #region ------------------------------------------ Tools ------------------------------------------------------ + // + #endregion --------------------------------------- Tools ------------------------------------------------------ + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs index 2a322f9a..aa67ea14 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs @@ -7,7 +7,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Logic.SoftwareAccessHelper; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckOrderNumber.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckOrderNumber.cs index 6eb900f3..3b4a7056 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckOrderNumber.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckOrderNumber.cs @@ -4,7 +4,7 @@ using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { public class CheckOrderNumber : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckSerialNumber.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckSerialNumber.cs index 586e1f5b..ba488770 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/CheckSerialNumber.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/CheckSerialNumber.cs @@ -4,7 +4,7 @@ using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { public class CheckSerialNumber : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ConnectCordonel.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ConnectCordonel.cs index a0fac18a..da46434a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ConnectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ConnectCordonel.cs @@ -3,7 +3,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { public class ConnectCordonel : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/DetectCordonel.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/DetectCordonel.cs index fef61032..0c2d9993 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/DetectCordonel.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/DetectCordonel.cs @@ -3,7 +3,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { /// /// Production process detects the serial number of the Cordonel. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ErrorHandler.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ErrorHandler.cs index e333b9bd..78f5a3eb 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/ErrorHandler.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/ErrorHandler.cs @@ -5,7 +5,7 @@ using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; using Xylem.Common.Production.ProductionUiCordonel.UserControls; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { public class ErrorHandler : BaseProductionProcess { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/GetSerialNumberAndRadioAddress.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/GetSerialNumberAndRadioAddress.cs index 562d993e..a912e5a7 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/CommonProcesses/GetSerialNumberAndRadioAddress.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericProcesses/GetSerialNumberAndRadioAddress.cs @@ -8,7 +8,7 @@ using Xylem.Common.Logic.SoftwareAccessHelper; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.Properties; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.CommonProcesses +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses { /// /// Request the serial number and radio address from DB. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs index 26615686..5a88c1ae 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs @@ -1,58 +1,12 @@ -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { public interface IProcessController { /// - /// State machine of ProcessController which serves as template for all production processes. - /// - /// - /// INSTRUCTION FOR NORMAL USAGE: - /// ----------------------------- - /// Call the SetupProcess() with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState set to the next required state after successfully execution. - /// That's it! The normal state change has always only one production process active and waits for completion - /// before changing to a new state and therefore a new production process. - /// - /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the - /// errorExitState is linked to the error state, the breakExitState to the stop state. - /// - /// - /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: - /// -------------------------------------------------------------------------- - /// Sometimes production processes may be able to be executed simultaneously as they are independent of each - /// other. One example is a database access for data collection from different tables. - /// - /// Call the SetupProcess with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState being set to (null). - /// This will lock the sate change after completion as multiple processes may be active and a state change after - /// completion may cause unpredictable operation, the state is already where it should be (manually changed), - /// - change the state after the SetupProcess() call manually. - /// - /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of - /// the actual new assigned process): - /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without - /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully - /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait - /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! - /// - /// - /// INSTRUCTION TO DELAY THE START OF A PROCESS: - /// -------------------------------------------- - /// Each new process execution can be delayed as it may need the result of another process which is active. - /// - /// Wait for a single process to complete: - /// - set the "waitForSingleProcess" to the process to wait for. - /// Wait for all preceding processes to complete: - /// - set the "waitForAllProcesses" to true. - /// + /// Individual states to fill the state machine. + /// ATTENTION: All states have to be part of the /// - /// - /// - Initial - /// - void StateMachine(); + void IndividualStateMachine(IProductionProcess process); } } diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 767a4554..945346a8 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -289,10 +289,10 @@ - + - - + + @@ -300,7 +300,7 @@ - + @@ -319,15 +319,15 @@ MultiModeChild.xaml - - + + - + From cf8a0b90b40e947d6fc9eb3ea99723533e8138ec Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 16:07:27 +0200 Subject: [PATCH 04/16] ProductionUiCordonel: - re-organized to support multiple production processes part 3 --- .../BaseProcessController.cs | 45 ++-- .../ProductionProcesses/BaseProcessStates.cs | 140 +++++++++++ .../EolProcesses/EolProcessStates.cs | 229 +++++------------- .../EolProcesses/EolStateMachine.cs | 2 - .../ProductionProcesses/IProcessStates.cs | 19 ++ .../ProductionUiCordonel.csproj | 2 + 6 files changed, 232 insertions(+), 205 deletions(-) create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs index c3783ef1..c332353f 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs @@ -21,7 +21,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { #region ------------------------------------------ Variables -------------------------------------------------- // actual genesis meter - protected IGenesisMeter _genesisMeter; + private IGenesisMeter _genesisMeter; // Cordonel requirements for production private CordonelRequirements _cordonelRequirements; // EOL progress states for production @@ -33,9 +33,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // state of the state machine protected ProcessState _stateMachineState; // locker to avoid repeated state execution - protected ProcessState _lockedProcessState; + private ProcessState _lockedProcessState; // sender of state change event to evaluate in e.g. error process - protected IProductionProcess _stateChangeRequestProcess; + private IProductionProcess _stateChangeRequestProcess; // List of production processes public List Processes { @@ -44,7 +44,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses } // Container for production process states - protected EolProcessStates _processStateDef; + protected BaseProcessStates _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) @@ -87,12 +87,12 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public event EventHandler OnProcessProgressChanged; // cancellation token - protected CancellationTokenSource _cancellationTokenSource; - protected CancellationToken _cancellationToken; + private CancellationTokenSource _cancellationTokenSource; + private CancellationToken _cancellationToken; // cyclic timer for state machine call private Timer _processCtrlTimer; - protected const Int32 ProcessTimerCycleMs = 250; + private const Int32 ProcessTimerCycleMs = 250; // defines for state machine /// @@ -253,21 +253,6 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { OnProcessLogRequest?.Invoke(sender, e); } - - /// - /// Wrapper to fire this event from a derived class - /// - /// - /// - /// - /// - Initial - /// - protected void OverallProcessStateChanged(Object sender, ProcessStateArgs e) - { - // signal state change to logger - OnOverallProcessStateChanged?.Invoke(sender, e); - } - /// /// Handler to set a new state for the state machine regarding the entire final-process /// @@ -374,7 +359,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses CalcAndPushOverAllProgress(); // signal state change to logger - OverallProcessStateChanged(this, new ProcessStateArgs(_stateMachineState)); + OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); // get the actual required process from the state definition list var process = _processStateDef?.GetProcessOfState(_stateMachineState); @@ -531,7 +516,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Handle genesis meter is null as new meter. /// - protected Boolean CheckForNewMeter() + private Boolean CheckForNewMeter() { if (_genesisMeter == null || (!string.IsNullOrEmpty(_lastPcbId) && @@ -569,7 +554,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Leave skipped processes as is. /// - protected ProcessState RepeatFailedTests() + private ProcessState RepeatFailedTests() { var state = ProcessState.Idle; @@ -611,7 +596,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Kick off exec parametrization if check after reboot failed. /// - protected void ProcessErrorHandler(IProductionProcess process) + private void ProcessErrorHandler(IProductionProcess process) { if (process == null) return; @@ -636,7 +621,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate the maximum of all processes expected to execute and signal to GUI. /// - protected void CalcAndPushMaxOverAllProcessSteps() + private void CalcAndPushMaxOverAllProcessSteps() { if (_processStateDef == null) return; @@ -650,7 +635,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Calculate all processes which are completed or skipped and signal to GUI. /// - protected void CalcAndPushOverAllProgress() + private void CalcAndPushOverAllProgress() { if (Processes == null) return; @@ -899,7 +884,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Cancellation token source. /// - protected void KillProcesses() + private void KillProcesses() { _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); @@ -921,7 +906,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - protected void AbortProcesses() + private void AbortProcesses() { if (Processes == null || Processes.Count == 0) return; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs new file mode 100644 index 00000000..e82b993a --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +{ + /// + /// Concatenation for all production process states: + /// + /// + /// - Initial + /// + public class BaseProcessStates : IProcessStates + { + /// + /// Collection of state information + /// + protected List StateContainer { set; get; } + + /// + /// Ctor + /// + protected BaseProcessStates() + { + } + + /// + /// Get the process linked to state of state machine + /// + /// state to search for information text + /// process attached to state + /// + /// - Initial + /// + public IProductionProcess GetProcessOfState(ProcessState stateMachineState) + { + return (from stateContainer in StateContainer + where stateContainer.State == stateMachineState + select stateContainer.Process).FirstOrDefault(); + } + + /// + /// Get the current information of the state machine state + /// + /// state to search for information text + /// information attached to state depending on region + /// + /// - Initial + /// + /// + /// - If state == null return error message. + /// + public String GetStateInfoOfState(ProcessState? stateMachineState) + { + if (stateMachineState == null) + stateMachineState = ProcessState.Error; + + return (from stateContainer in StateContainer + where stateContainer.State == stateMachineState + select stateContainer.Info).FirstOrDefault(); + } + + /// + /// Get the info text from production process + /// + /// process to look for state info text + /// information attached to process depending on region + /// + /// - Initial + /// + public String GetStateInfoOfProcess(IProductionProcess process) + { + return (from stateContainer in StateContainer + where stateContainer.Process == process + select stateContainer.Info).FirstOrDefault(); + } + + /// + /// Get the state machine state of the text + /// + /// information text to search if state is assigned + /// state attached to information text + /// + /// - Initial + /// + public ProcessState GetStateOfInfoText(String text) + { + return (from stateContainer in StateContainer + where stateContainer.Info == text + select stateContainer.State).FirstOrDefault(); + } + + /// + /// Get the state machine state from for the process + /// + /// process to look for state info text + /// state attached to process + /// + /// - Initial + /// + public ProcessState GetStateOfProcess(IProductionProcess process) + { + return (from stateContainer in StateContainer + where stateContainer.Process == process + select stateContainer.State).FirstOrDefault(); + } + + /// + /// Get number of EOL relevant processes for statistic e.g. progress bar + /// + /// number of initialized processes + /// + /// - Initial + /// + /// + /// - Used marker for production relevant processes + /// + public Int32 GetNumberOfInitializedProcesses() + { + return StateContainer.Count(stateContainer => stateContainer.ProductionRelevant ); + } + + /// + /// Get all EOL relevant states + /// + /// list of initialized processes + /// + /// - Initial + /// + /// + /// - Used marker for production relevant processes + /// + public List GetAllEolRelevantProcesses() + { + return (from state in StateContainer where state.ProductionRelevant + select state.Process).ToList(); + } + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index 5652ecf7..6cad814d 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -16,13 +16,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Initial /// - public class EolProcessStates + public class EolProcessStates : BaseProcessStates { - /// - /// Collection of state information - /// - private readonly List _stateContainer; - /// /// Ctor /// - Setup state and state informal text, @@ -32,154 +27,154 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// public EolProcessStates() { - _stateContainer = new List + StateContainer = new List { new ProcessStateStruct( - null, - ProcessState.Idle, + null, + ProcessState.Idle, Resources.StrStateIdle, false), - + new ProcessStateStruct( - null, - ProcessState.CheckNewMeter, + null, + ProcessState.CheckNewMeter, Resources.StrProcessStateCheckNewMeter, false), - + new ProcessStateStruct( - new ErrorHandler(null), - ProcessState.Error, + new ErrorHandler(null), + ProcessState.Error, Resources.StrProcessStateError, false), - + new ProcessStateStruct( - null, + null, ProcessState.RepeatFailedTests, Resources.StrStateRepeatFinalTest, false), - + new ProcessStateStruct( - null, - ProcessState.AbortTest, + null, + ProcessState.AbortTest, Resources.StrStateSkipping, false), - + new ProcessStateStruct( - null, - ProcessState.Stop, + null, + ProcessState.Stop, Resources.StrStateUserStop, false), - + new ProcessStateStruct( - null, - ProcessState.TestFailed, + null, + ProcessState.TestFailed, Resources.StrStateFinalTestFailed, false), - + new ProcessStateStruct( - null, - ProcessState.ReadyForShipping, + null, + ProcessState.ReadyForShipping, Resources.StrStateReadyForShipping, false), - + new ProcessStateStruct( - null, - ProcessState.PrintSuccessReport, + null, + ProcessState.PrintSuccessReport, Resources.StrStatePrintSuccessReport, false), - + new ProcessStateStruct( - null, - ProcessState.PrintReturnReport, + null, + ProcessState.PrintReturnReport, Resources.StrStatePrintReturnReport, false), - + new ProcessStateStruct( - new DetectCordonel(Resources.StrStateDetectCordonel), + new DetectCordonel(Resources.StrStateDetectCordonel), ProcessState.DetectCordonel, Resources.StrStateDetectCordonel, true), - + new ProcessStateStruct( - new ConnectCordonel(Resources.StrStateConnectCordonel), + new ConnectCordonel(Resources.StrStateConnectCordonel), ProcessState.ConnectCordonel, Resources.StrStateConnectCordonel, true), - + new ProcessStateStruct( - new GetSerialNumberAndRadioAddress(Resources.StrStateGetSerialAndRadioAddress), + new GetSerialNumberAndRadioAddress(Resources.StrStateGetSerialAndRadioAddress), ProcessState.GetSerialNumberAndRadioAddress, Resources.StrStateGetSerialAndRadioAddress, true), - + new ProcessStateStruct( new GetFinalParametrization(Resources.StrStateGetProgrammingParameters), - ProcessState.GetProgrammingParameters, + ProcessState.GetProgrammingParameters, Resources.StrStateGetProgrammingParameters, true), - + new ProcessStateStruct( new CheckProductionState(Resources.StrStateCheckProductionState), ProcessState.CheckProductionState, Resources.StrStateCheckProductionState, true), - + new ProcessStateStruct( new ExecPasswordFile(Resources.StrStatePasswordFile), ProcessState.ProcessPasswordFile, Resources.StrStatePasswordFile, true), - + new ProcessStateStruct( new ExecFinalParametrization(Resources.StrStateFinalParametrization), - ProcessState.ExecFinalParametrization, + ProcessState.ExecFinalParametrization, Resources.StrStateFinalParametrization, true), - + new ProcessStateStruct( new ExecStoreConfiguration(Resources.StrStateStoreConfiguration), - ProcessState.ExecStoreConfiguration, + ProcessState.ExecStoreConfiguration, Resources.StrStateStoreConfiguration, true), - + new ProcessStateStruct( new ExecRebootCordonel(Resources.StrStateRebootCordonel), - ProcessState.RebootCordonel, + ProcessState.RebootCordonel, Resources.StrStateRebootCordonel, true), - + new ProcessStateStruct( new CheckFinalParametrization(Resources.StrStateCheckParametrization), ProcessState.CheckFinalParametrization, Resources.StrStateCheckParametrization, true), - + new ProcessStateStruct( new CheckRadio(Resources.StrStateRadioCheck), ProcessState.CheckRadio, Resources.StrStateRadioCheck, true), - + new ProcessStateStruct( new CheckOrderNumber(Resources.StrStateCheckOrderNumber), - ProcessState.CheckOrderNumber, + ProcessState.CheckOrderNumber, Resources.StrStateCheckOrderNumber, true), - + new ProcessStateStruct( new CheckSerialNumber( Resources.StrStateCheckSerialNumber), ProcessState.CheckSerialNumber, Resources.StrStateCheckSerialNumber, true), - + new ProcessStateStruct( new CheckAttachments(Resources.StrStateCheckAttachments), - ProcessState.CheckAttachments, + ProcessState.CheckAttachments, Resources.StrStateCheckAttachments, true), - + new ProcessStateStruct( new VisualInspection(Resources.StrStateVisualInspection), - ProcessState.ExecVisualInspection, + ProcessState.ExecVisualInspection, Resources.StrStateVisualInspection, true), @@ -190,117 +185,5 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr true) }; } - - /// - /// Get the process linked to state of state machine - /// - /// state to search for information text - /// process attached to state - /// - /// - Initial - /// - public IProductionProcess GetProcessOfState(ProcessState stateMachineState) - { - return (from stateContainer in _stateContainer - where stateContainer.State == stateMachineState - select stateContainer.Process).FirstOrDefault(); - } - - /// - /// Get the current information of the state machine state - /// - /// state to search for information text - /// information attached to state depending on region - /// - /// - Initial - /// - /// - /// - If state == null return error message. - /// - public String GetStateInfoOfState(ProcessState? stateMachineState) - { - if (stateMachineState == null) - stateMachineState = ProcessState.Error; - - return (from stateContainer in _stateContainer - where stateContainer.State == stateMachineState - select stateContainer.Info).FirstOrDefault(); - } - - /// - /// Get the info text from production process - /// - /// process to look for state info text - /// information attached to process depending on region - /// - /// - Initial - /// - public String GetStateInfoOfProcess(IProductionProcess process) - { - return (from stateContainer in _stateContainer - where stateContainer.Process == process - select stateContainer.Info).FirstOrDefault(); - } - - /// - /// Get the state machine state of the text - /// - /// information text to search if state is assigned - /// state attached to information text - /// - /// - Initial - /// - public ProcessState GetStateOfInfoText(String text) - { - return (from stateContainer in _stateContainer - where stateContainer.Info == text - select stateContainer.State).FirstOrDefault(); - } - - /// - /// Get the state machine state from for the process - /// - /// process to look for state info text - /// state attached to process - /// - /// - Initial - /// - public ProcessState GetStateOfProcess(IProductionProcess process) - { - return (from stateContainer in _stateContainer - where stateContainer.Process == process - select stateContainer.State).FirstOrDefault(); - } - - /// - /// Get number of EOL relevant processes for statistic e.g. progress bar - /// - /// number of initialized processes - /// - /// - Initial - /// - /// - /// - Used marker for production relevant processes - /// - public Int32 GetNumberOfInitializedProcesses() - { - return _stateContainer.Count(stateContainer => stateContainer.ProductionRelevant ); - } - - /// - /// Get all EOL relevant states - /// - /// list of initialized processes - /// - /// - Initial - /// - /// - /// - Used marker for production relevant processes - /// - public List GetAllEolRelevantProcesses() - { - return (from state in _stateContainer where state.ProductionRelevant - select state.Process).ToList(); - } - } + } } diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs index 62c3fcd1..33f517e0 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs @@ -1,7 +1,5 @@ using System; -using System.Threading; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs new file mode 100644 index 00000000..8d99734e --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Windows.Controls; +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.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +{ + public interface IProcessStates + { + + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 945346a8..153efcea 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -289,6 +289,7 @@ + @@ -302,6 +303,7 @@ + From e707304f430ecdd8dd3d2714aec84ce3c8efe948 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 17:06:52 +0200 Subject: [PATCH 05/16] ProductionUiCordonel: - re-organized to support multiple production processes part 5 --- .../ProductionProcesses/BaseProcessStates.cs | 40 +++++++------ .../EolProcesses/EolProcessStates.cs | 56 +++++++++---------- .../ProductionProcesses/ProcessStateStruct.cs | 19 ++++--- 3 files changed, 61 insertions(+), 54 deletions(-) diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs index e82b993a..c3d6a409 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses @@ -11,7 +12,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// - public class BaseProcessStates : IProcessStates + public class BaseProcessStates { /// /// Collection of state information @@ -35,9 +36,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// public IProductionProcess GetProcessOfState(ProcessState stateMachineState) { - return (from stateContainer in StateContainer - where stateContainer.State == stateMachineState - select stateContainer.Process).FirstOrDefault(); + return (from stateContainer in StateContainer + where stateContainer.State == stateMachineState + select stateContainer.Process).FirstOrDefault(); } /// @@ -57,8 +58,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses stateMachineState = ProcessState.Error; return (from stateContainer in StateContainer - where stateContainer.State == stateMachineState - select stateContainer.Info).FirstOrDefault(); + where stateContainer.State == stateMachineState + select stateContainer.Info).FirstOrDefault(); } /// @@ -72,8 +73,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public String GetStateInfoOfProcess(IProductionProcess process) { return (from stateContainer in StateContainer - where stateContainer.Process == process - select stateContainer.Info).FirstOrDefault(); + where stateContainer.Process == process + select stateContainer.Info).FirstOrDefault(); } /// @@ -87,8 +88,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public ProcessState GetStateOfInfoText(String text) { return (from stateContainer in StateContainer - where stateContainer.Info == text - select stateContainer.State).FirstOrDefault(); + where stateContainer.Info == text + select stateContainer.State).FirstOrDefault(); } /// @@ -102,8 +103,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public ProcessState GetStateOfProcess(IProductionProcess process) { return (from stateContainer in StateContainer - where stateContainer.Process == process - select stateContainer.State).FirstOrDefault(); + where stateContainer.Process == process + select stateContainer.State).FirstOrDefault(); } /// @@ -118,9 +119,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// public Int32 GetNumberOfInitializedProcesses() { - return StateContainer.Count(stateContainer => stateContainer.ProductionRelevant ); + return StateContainer.Count(stateContainer => stateContainer.ProductionProcessNo != null); } - + /// /// Get all EOL relevant states /// @@ -131,10 +132,15 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Used marker for production relevant processes /// + /// + /// - Changed to sorted list by production process number + /// public List GetAllEolRelevantProcesses() { - return (from state in StateContainer where state.ProductionRelevant - select state.Process).ToList(); + var sortedList = StateContainer + .Where(y => y.ProductionProcessNo != null) + .OrderBy(x => x.ProductionProcessNo); + return (from state in sortedList select state.Process).ToList(); } - } + } } diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index 6cad814d..ad05f59f 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; using Xylem.Common.Production.ProductionUiCordonel.Properties; @@ -33,156 +31,156 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr null, ProcessState.Idle, Resources.StrStateIdle, - false), + null), new ProcessStateStruct( null, ProcessState.CheckNewMeter, Resources.StrProcessStateCheckNewMeter, - false), + null), new ProcessStateStruct( new ErrorHandler(null), ProcessState.Error, Resources.StrProcessStateError, - false), + null), new ProcessStateStruct( null, ProcessState.RepeatFailedTests, Resources.StrStateRepeatFinalTest, - false), + null), new ProcessStateStruct( null, ProcessState.AbortTest, Resources.StrStateSkipping, - false), + null), new ProcessStateStruct( null, ProcessState.Stop, Resources.StrStateUserStop, - false), + null), new ProcessStateStruct( null, ProcessState.TestFailed, Resources.StrStateFinalTestFailed, - false), + null), new ProcessStateStruct( null, ProcessState.ReadyForShipping, Resources.StrStateReadyForShipping, - false), + null), new ProcessStateStruct( null, ProcessState.PrintSuccessReport, Resources.StrStatePrintSuccessReport, - false), + null), new ProcessStateStruct( null, ProcessState.PrintReturnReport, Resources.StrStatePrintReturnReport, - false), + null), new ProcessStateStruct( new DetectCordonel(Resources.StrStateDetectCordonel), ProcessState.DetectCordonel, Resources.StrStateDetectCordonel, - true), + 0), new ProcessStateStruct( new ConnectCordonel(Resources.StrStateConnectCordonel), ProcessState.ConnectCordonel, Resources.StrStateConnectCordonel, - true), + 1), new ProcessStateStruct( new GetSerialNumberAndRadioAddress(Resources.StrStateGetSerialAndRadioAddress), ProcessState.GetSerialNumberAndRadioAddress, Resources.StrStateGetSerialAndRadioAddress, - true), + 2), new ProcessStateStruct( new GetFinalParametrization(Resources.StrStateGetProgrammingParameters), ProcessState.GetProgrammingParameters, Resources.StrStateGetProgrammingParameters, - true), + 3), new ProcessStateStruct( new CheckProductionState(Resources.StrStateCheckProductionState), ProcessState.CheckProductionState, Resources.StrStateCheckProductionState, - true), + 4), new ProcessStateStruct( new ExecPasswordFile(Resources.StrStatePasswordFile), ProcessState.ProcessPasswordFile, Resources.StrStatePasswordFile, - true), + 5), new ProcessStateStruct( new ExecFinalParametrization(Resources.StrStateFinalParametrization), ProcessState.ExecFinalParametrization, Resources.StrStateFinalParametrization, - true), + 6), new ProcessStateStruct( new ExecStoreConfiguration(Resources.StrStateStoreConfiguration), ProcessState.ExecStoreConfiguration, Resources.StrStateStoreConfiguration, - true), + 7), new ProcessStateStruct( new ExecRebootCordonel(Resources.StrStateRebootCordonel), ProcessState.RebootCordonel, Resources.StrStateRebootCordonel, - true), + 8), new ProcessStateStruct( new CheckFinalParametrization(Resources.StrStateCheckParametrization), ProcessState.CheckFinalParametrization, Resources.StrStateCheckParametrization, - true), + 9), new ProcessStateStruct( new CheckRadio(Resources.StrStateRadioCheck), ProcessState.CheckRadio, Resources.StrStateRadioCheck, - true), + 10), new ProcessStateStruct( new CheckOrderNumber(Resources.StrStateCheckOrderNumber), ProcessState.CheckOrderNumber, Resources.StrStateCheckOrderNumber, - true), + 11), new ProcessStateStruct( new CheckSerialNumber( Resources.StrStateCheckSerialNumber), ProcessState.CheckSerialNumber, Resources.StrStateCheckSerialNumber, - true), + 12), new ProcessStateStruct( new CheckAttachments(Resources.StrStateCheckAttachments), ProcessState.CheckAttachments, Resources.StrStateCheckAttachments, - true), + 13), new ProcessStateStruct( new VisualInspection(Resources.StrStateVisualInspection), ProcessState.ExecVisualInspection, Resources.StrStateVisualInspection, - true), + 14), new ProcessStateStruct( new PrepareShipping(Resources.StrStatePrepareShipping), ProcessState.PrepareShippingMode, Resources.StrStatePrepareShipping, - true) + 15) }; } } diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs index b28abf73..b635aa12 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs @@ -11,19 +11,21 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Assign process state information /// - /// production process - /// assigned state for call of this process - /// name for process to display a message - /// all processes which are relevant for the EOL production + /// production process, set to null for "invisible" states + /// assigned state for call of this process used by the state machine + /// name for process to display a message in the GUI + /// set a unique number to sort the table of production processes, + /// set to null for all hidden processes which are not a production process /// /// - Initial /// - public ProcessStateStruct(IProductionProcess process, ProcessState state, String info, Boolean productionRelevant) + public ProcessStateStruct(IProductionProcess process, ProcessState state, String info, + UInt32? productionProcessNo) { Process = process; State = state; Info = info; - ProductionRelevant = productionRelevant; + ProductionProcessNo = productionProcessNo; } /// /// The state of the final test @@ -38,8 +40,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// public String Info { get; } /// - /// Processes which are relevant for the EOL production + /// Processes which are relevant for the production have a number for the sequence they should be displayed + /// in the GUI, all hidden (non-)production processes are set to null /// - public Boolean ProductionRelevant { get; } + public UInt32? ProductionProcessNo { get; } } } From 5e6e76533a49716c6cae76cc25f40437b12018af Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 14 Apr 2025 17:27:38 +0200 Subject: [PATCH 06/16] ProductionUiCordonel: - re-organized to support multiple production processes part 6 --- .../ProductionProcesses/BaseProcessStates.cs | 66 ++++++++++++++++++- .../EolProcesses/EolProcessStates.cs | 57 +--------------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs index c3d6a409..20627021 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; + namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { @@ -17,13 +19,71 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Collection of state information /// - protected List StateContainer { set; get; } + protected List StateContainer { get; } /// - /// Ctor + /// Ctor: + /// Initialize all hidden standard generic states /// protected BaseProcessStates() { + StateContainer = new List + { + new ProcessStateStruct( + null, + ProcessState.Idle, + Resources.StrStateIdle, + null), + + new ProcessStateStruct( + null, + ProcessState.CheckNewMeter, + Resources.StrProcessStateCheckNewMeter, + null), + + new ProcessStateStruct( + new ErrorHandler(null), + ProcessState.Error, + Resources.StrProcessStateError, + null), + + new ProcessStateStruct( + null, + ProcessState.RepeatFailedTests, + Resources.StrStateRepeatFinalTest, + null), + + new ProcessStateStruct( + null, + ProcessState.AbortTest, + Resources.StrStateSkipping, + null), + + new ProcessStateStruct( + null, + ProcessState.Stop, + Resources.StrStateUserStop, + null), + + new ProcessStateStruct( + null, + ProcessState.TestFailed, + Resources.StrStateFinalTestFailed, + null), + + new ProcessStateStruct( + null, + ProcessState.PrintSuccessReport, + Resources.StrStatePrintSuccessReport, + null), + + new ProcessStateStruct( + null, + ProcessState.PrintReturnReport, + Resources.StrStatePrintReturnReport, + null), + + }; } /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs index ad05f59f..8d792cbf 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs @@ -25,68 +25,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// public EolProcessStates() { - StateContainer = new List + var stateContainer = new List { - new ProcessStateStruct( - null, - ProcessState.Idle, - Resources.StrStateIdle, - null), - - new ProcessStateStruct( - null, - ProcessState.CheckNewMeter, - Resources.StrProcessStateCheckNewMeter, - null), - - new ProcessStateStruct( - new ErrorHandler(null), - ProcessState.Error, - Resources.StrProcessStateError, - null), - - new ProcessStateStruct( - null, - ProcessState.RepeatFailedTests, - Resources.StrStateRepeatFinalTest, - null), - - new ProcessStateStruct( - null, - ProcessState.AbortTest, - Resources.StrStateSkipping, - null), - - new ProcessStateStruct( - null, - ProcessState.Stop, - Resources.StrStateUserStop, - null), - - new ProcessStateStruct( - null, - ProcessState.TestFailed, - Resources.StrStateFinalTestFailed, - null), - new ProcessStateStruct( null, ProcessState.ReadyForShipping, Resources.StrStateReadyForShipping, null), - new ProcessStateStruct( - null, - ProcessState.PrintSuccessReport, - Resources.StrStatePrintSuccessReport, - null), - - new ProcessStateStruct( - null, - ProcessState.PrintReturnReport, - Resources.StrStatePrintReturnReport, - null), - new ProcessStateStruct( new DetectCordonel(Resources.StrStateDetectCordonel), ProcessState.DetectCordonel, @@ -182,6 +128,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr Resources.StrStatePrepareShipping, 15) }; + StateContainer.AddRange(stateContainer); } } } From c57e7bf3770e11fc1b952c20149214c0699086a9 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Tue, 15 Apr 2025 11:40:31 +0200 Subject: [PATCH 07/16] ProductionUiCordonel: - re-organized to support multiple production processes part 7 --- .../BaseProcessController.cs | 105 +++---- .../ProductionProcesses/Enum/ProcessState.cs | 4 - .../EolProcesses/EolProcessStates.cs | 134 --------- .../EolProcesses/EolStateMachine.cs | 26 +- .../EolProcesses/EolStateMachineCtrl.cs | 271 ++++++++++++++++++ ...ssStates.cs => GenericStateMachineCtrl.cs} | 120 ++++---- .../ProductionProcesses/IProcessController.cs | 2 +- .../ProductionProcesses/ProcessStateStruct.cs | 86 +++++- .../ProductionUiCordonel.csproj | 4 +- 9 files changed, 480 insertions(+), 272 deletions(-) delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/{BaseProcessStates.cs => GenericStateMachineCtrl.cs} (59%) diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs index c332353f..08454429 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs @@ -94,11 +94,15 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses private Timer _processCtrlTimer; private const Int32 ProcessTimerCycleMs = 250; - // defines for state machine /// /// Avoid waiting for a single process completion. /// - private const IProductionProcess DoNotWaitForSingleProcessCompletion = null; + public const IProductionProcess DoNotWaitForSingleProcessCompletion = null; + + /// + /// Kick off a parallel execution of the state machine. + /// + public const Boolean ExecuteInParallel = true; #endregion --------------------------------------- Variables -------------------------------------------------- #region ------------------------------------------ Class ------------------------------------------------------ @@ -289,7 +293,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses #region ------------------------------------------ State Machine ---------------------------------------------- /// - public abstract void IndividualStateMachine(IProductionProcess process); + public abstract void IndividualStateMachine(ProcessStateStruct processSatProcessStateStruct); /// /// State machine of ProcessController which serves as template for all production processes. @@ -298,7 +302,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// INSTRUCTION FOR NORMAL USAGE: /// ----------------------------- /// Call the SetupProcess() with the - /// - new process automatically collected from with new + /// - new process automatically collected from with new /// _actualProcessState as input, /// - successExitState set to the next required state after successfully execution. /// That's it! The normal state change has always only one production process active and waits for completion @@ -314,7 +318,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// other. One example is a database access for data collection from different tables. /// /// Call the SetupProcess with the - /// - new process automatically collected from with new + /// - new process automatically collected from with new /// _actualProcessState as input, /// - successExitState being set to (null). /// This will lock the sate change after completion as multiple processes may be active and a state change after @@ -342,6 +346,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Initial /// + /// + /// - Generic implementation + /// private void GenericStateMachine() {// avoid repeated execution of the state machine on unchanged state if (_stateMachineState == _lockedProcessState) @@ -361,8 +368,11 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // signal state change to logger OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); + // get the processStateStruct of the actual set stateMachineSTate + var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState); + // get the actual required process from the state definition list - var process = _processStateDef?.GetProcessOfState(_stateMachineState); + //var process = _processStateDef?.GetProcessOfState(_stateMachineState); switch (_stateMachineState) { @@ -386,7 +396,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // Detection of Cordonel has to be completed before assigning next state, so leave to automatic // to change the state after successfully execution. The detection is the base for all others. - StartProcess(process, ProcessState.CheckNewMeter); + StartProcess(processStateStruct); break; case ProcessState.CheckNewMeter: @@ -400,7 +410,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses case ProcessState.ConnectCordonel: // Precondition: - The detection process has to be completed as the PcbId is needed. // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + StartProcess(processStateStruct); // Do not wait on completion as new parallel process can be started by manually state change. _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; break; @@ -408,19 +418,19 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses case ProcessState.GetSerialNumberAndRadioAddress: // Precondition: - The detection process has to be completed as the PcbId is needed. // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion); + StartProcess(processStateStruct); // Do not wait on completion as new parallel process can be started by manually state change. _stateMachineState = ProcessState.GetProgrammingParameters; break; case ProcessState.CheckOrderNumber: // This scans the order number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckSerialNumber, waitForAllProcesses: true); + StartProcess(processStateStruct); break; case ProcessState.CheckSerialNumber: // This scans the serial number, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckAttachments); + StartProcess(processStateStruct); break; case ProcessState.RepeatFailedTests: @@ -451,7 +461,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // Start the error handler with its user feedback request. It has to be started // always even if the abortion of all processes is required! The error handler // will change the exit state based on the user input. - StartProcess(process, ProcessState.Idle, startAlways: true); + StartProcess(processStateStruct); break; case ProcessState.StateListDelimiter: @@ -459,7 +469,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses break; default: - IndividualStateMachine(process); + // This is the call to the processes for a specific test e.g. EOL or Picking + IndividualStateMachine(processStateStruct); break; } } @@ -664,7 +675,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses if (Processes == null) Processes = new List(); if (_processStateDef == null) - _processStateDef = new EolProcessStates(); + _processStateDef = new EolStateMachineCtrl(); // get all processes relevant for EOL from table which has to be sorted var processes = _processStateDef.GetAllEolRelevantProcesses(); @@ -693,14 +704,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Adds event handlers for process information, /// - Starts process. /// - /// - /// - /// - /// - /// wait for single processes to complete before execution - /// wait for all processes to complete before execution - /// start even if abort is required - /// actual state + /// + /// actual state /// /// - Initial /// @@ -719,41 +724,42 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Changed setup process to start process being able to create a process list in advance. /// - protected void StartProcess(IProductionProcess process, ProcessState? successExitState, - ProcessState errorExitState = ProcessState.Error, ProcessState breakExitState = ProcessState.Stop, - IProductionProcess waitForSingleProcess = DoNotWaitForSingleProcessCompletion, - Boolean waitForAllProcesses = false, Boolean startAlways = false) + /// + /// - Feed with processStateStruct. + /// + protected void StartProcess(ProcessStateStruct processStateStruct) { // deny access if uninitialized - if (Processes == null || process == null) + if (Processes == null || processStateStruct.ProductionProcess == null) return; + // do not start a new process if any error or abort request is signaled - if (!startAlways && AnyProcessRequiresAbortion()) + if (AnyProcessRequiresAbortion() && !processStateStruct.StartAlways ) { return; } // process is waiting for execution after init, this overrides the eventually abort state - process.InitProcess(); + processStateStruct.ProductionProcess.InitProcess(); // wait for a single process to complete before start of new depending on process - if (waitForSingleProcess != DoNotWaitForSingleProcessCompletion) + if (processStateStruct.WaitForSingleProcess != null) { // each process will have its own timer until a timeout will change the state - while (Processes.Any(pp => pp.ProcessName == waitForSingleProcess.ProcessName - && WaitForProcess(pp) - && !AnyProcessRequiresAbortion())) + 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 (waitForAllProcesses) + 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 != process.ProcessName + while (Processes.Any(pp => pp.ProcessName != processStateStruct.ProductionProcess.ProcessName && WaitForProcess(pp) && !AnyProcessRequiresAbortion())) { @@ -762,28 +768,29 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses } // if any process exits meanwhile with an error or abort request avoid a start of this process - if ((!startAlways && AnyProcessRequiresAbortion()) - || process.SingleProcessState == SingleProcessState.Idle - || process.SingleProcessState == SingleProcessState.Abort) + if (( !processStateStruct.StartAlways && AnyProcessRequiresAbortion()) + || processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Idle + || processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Abort) { - process.AbortProcess(); + processStateStruct.ProductionProcess.AbortProcess(); return; } // References to commonly used production process objects - process.Meter = _genesisMeter; - process.RegisterRestorer = _registerRestorer; - process.RadioEncryptionKey = _radioEncryptionKey; - process.PwdContainer = _pwdContainer; - process.ProductionStatus = _productionStatus; - process.ProductionRequirements = _cordonelRequirements; - process.EolProgress = _cordonelEolProgress; - process.CancellationToken = _cancellationToken; + 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; + processStateStruct.ProductionProcess.CancellationToken = _cancellationToken; // kick off process execution, the breakExitState will not be used - process.StartProcess(successExitState, errorExitState, breakExitState); + processStateStruct.ProductionProcess.StartProcess(processStateStruct.NextStateOnSuccess, + processStateStruct.ErrorExitState, processStateStruct.BreakExitState); // connect process events to actual process - AddProcessEvents(process); + AddProcessEvents(processStateStruct.ProductionProcess); } /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs index b15cb83e..e59153b8 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/Enum/ProcessState.cs @@ -27,10 +27,6 @@ /// RepeatFailedTests, /// - /// Final test failed - /// - TestFailed, - /// /// User stopped process /// Stop, diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs deleted file mode 100644 index 8d792cbf..00000000 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStates.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Collections.Generic; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; -using Xylem.Common.Production.ProductionUiCordonel.Properties; - -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses -{ - /// - /// Concatenation for all production process states: - /// - Production process class (representing a production process step), - /// - attached state, - /// - state information to display a text message. - /// - /// - /// - Initial - /// - public class EolProcessStates : BaseProcessStates - { - /// - /// Ctor - /// - Setup state and state informal text, - /// - Setup ProductionProcessFunctions, if these are null, it is just a state without any predefined function - /// - Sort the active steps from first til last step as this is the order as those will be sorted in the - /// process list UI. - /// - public EolProcessStates() - { - var stateContainer = new List - { - new ProcessStateStruct( - null, - ProcessState.ReadyForShipping, - Resources.StrStateReadyForShipping, - null), - - new ProcessStateStruct( - new DetectCordonel(Resources.StrStateDetectCordonel), - ProcessState.DetectCordonel, - Resources.StrStateDetectCordonel, - 0), - - new ProcessStateStruct( - new ConnectCordonel(Resources.StrStateConnectCordonel), - ProcessState.ConnectCordonel, - Resources.StrStateConnectCordonel, - 1), - - new ProcessStateStruct( - new GetSerialNumberAndRadioAddress(Resources.StrStateGetSerialAndRadioAddress), - ProcessState.GetSerialNumberAndRadioAddress, - Resources.StrStateGetSerialAndRadioAddress, - 2), - - new ProcessStateStruct( - new GetFinalParametrization(Resources.StrStateGetProgrammingParameters), - ProcessState.GetProgrammingParameters, - Resources.StrStateGetProgrammingParameters, - 3), - - new ProcessStateStruct( - new CheckProductionState(Resources.StrStateCheckProductionState), - ProcessState.CheckProductionState, - Resources.StrStateCheckProductionState, - 4), - - new ProcessStateStruct( - new ExecPasswordFile(Resources.StrStatePasswordFile), - ProcessState.ProcessPasswordFile, - Resources.StrStatePasswordFile, - 5), - - new ProcessStateStruct( - new ExecFinalParametrization(Resources.StrStateFinalParametrization), - ProcessState.ExecFinalParametrization, - Resources.StrStateFinalParametrization, - 6), - - new ProcessStateStruct( - new ExecStoreConfiguration(Resources.StrStateStoreConfiguration), - ProcessState.ExecStoreConfiguration, - Resources.StrStateStoreConfiguration, - 7), - - new ProcessStateStruct( - new ExecRebootCordonel(Resources.StrStateRebootCordonel), - ProcessState.RebootCordonel, - Resources.StrStateRebootCordonel, - 8), - - new ProcessStateStruct( - new CheckFinalParametrization(Resources.StrStateCheckParametrization), - ProcessState.CheckFinalParametrization, - Resources.StrStateCheckParametrization, - 9), - - new ProcessStateStruct( - new CheckRadio(Resources.StrStateRadioCheck), - ProcessState.CheckRadio, Resources.StrStateRadioCheck, - 10), - - new ProcessStateStruct( - new CheckOrderNumber(Resources.StrStateCheckOrderNumber), - ProcessState.CheckOrderNumber, - Resources.StrStateCheckOrderNumber, - 11), - - new ProcessStateStruct( - new CheckSerialNumber( Resources.StrStateCheckSerialNumber), - ProcessState.CheckSerialNumber, - Resources.StrStateCheckSerialNumber, - 12), - - new ProcessStateStruct( - new CheckAttachments(Resources.StrStateCheckAttachments), - ProcessState.CheckAttachments, - Resources.StrStateCheckAttachments, - 13), - - new ProcessStateStruct( - new VisualInspection(Resources.StrStateVisualInspection), - ProcessState.ExecVisualInspection, - Resources.StrStateVisualInspection, - 14), - - new ProcessStateStruct( - new PrepareShipping(Resources.StrStatePrepareShipping), - ProcessState.PrepareShippingMode, - Resources.StrStatePrepareShipping, - 15) - }; - StateContainer.AddRange(stateContainer); - } - } -} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs index 33f517e0..9b478550 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs @@ -44,7 +44,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Initial /// - public override void IndividualStateMachine(IProductionProcess process) + public override void IndividualStateMachine(ProcessStateStruct processStateStruct) { switch (_stateMachineState) { @@ -53,8 +53,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // values from the Cordonel are needed to check the update capability: // - Wait on completed connection process before starting this process. // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + StartProcess(processStateStruct); // Do not wait on completion as new parallel process can be started by manually state change. _stateMachineState = ProcessState.CheckProductionState; break; @@ -64,8 +63,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // values from the Cordonel are needed to check the update capability: // - Wait on completed connection process before starting this process. // AVOID to change the process state on completion as parallel process is active! - StartProcess(process, BaseProductionProcess.DoNotChangeStateAfterCompletion, - waitForSingleProcess: _processStateDef?.GetProcessOfState(ProcessState.ConnectCordonel)); + StartProcess(processStateStruct); // Do not wait on completion as new parallel process can be started by manually state change. _stateMachineState = ProcessState.ProcessPasswordFile; break; @@ -76,33 +74,33 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // effects on login. Wait for all processes to complete. // Leave state switch to automatic to avoid side effects on incomplete updated DB for next // steps. - StartProcess(process, ProcessState.ExecFinalParametrization, waitForAllProcesses: true); + StartProcess(processStateStruct); break; case ProcessState.ExecFinalParametrization: // This writes all setups to the device, it has to be executed exclusively with automatic - StartProcess(process, ProcessState.ExecStoreConfiguration); + StartProcess(processStateStruct); break; case ProcessState.ExecStoreConfiguration: // Precondition: - The final parametrization has to be finished as this shall be stored // to non-volatile memory and checked if remaining valid after the reboot. // Store configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.RebootCordonel); + StartProcess(processStateStruct); break; case ProcessState.RebootCordonel: // Precondition: - The store configuration has to be executed to safe all settings // to non-volatile memory and checked if remaining valid after the reboot. // Reboot has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckFinalParametrization); + StartProcess(processStateStruct); break; case ProcessState.CheckFinalParametrization: // Precondition: - The reboot process including the store all configurations has to be // completed. // Check final configuration has to be executed exclusively with automatic - StartProcess(process, ProcessState.CheckRadio); + StartProcess(processStateStruct); break; case ProcessState.CheckRadio: @@ -110,22 +108,22 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // configurations and the check of all setups survived the reboot have to be completed. // Radio signal level check, skipped on NA region or if skipping is required by special // approval - StartProcess(process, ProcessState.CheckOrderNumber); + StartProcess(processStateStruct); break; case ProcessState.CheckAttachments: // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ExecVisualInspection); + StartProcess(processStateStruct); break; case ProcessState.ExecVisualInspection: // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.PrepareShippingMode); + StartProcess(processStateStruct); break; case ProcessState.PrepareShippingMode: // Precondition: - All checks have to be executed with positive result - StartProcess(process, ProcessState.ReadyForShipping); + StartProcess(processStateStruct); break; case ProcessState.ReadyForShipping: diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs new file mode 100644 index 00000000..54e63a8e --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs @@ -0,0 +1,271 @@ +using System.Collections.Generic; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +{ + /// + /// Concatenation for all production process states: + /// - Production process class (representing a production process step), + /// - attached state, + /// - state information to display a text message. + /// + /// + /// - Initial + /// + public class EolStateMachineCtrl : BaseProcessStates + { + /// + /// State-machine definitions of production-processes which feeds the state-machine. + /// + /// + /// INSTRUCTION FOR NORMAL USAGE: + /// ----------------------------- + /// + /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the + /// "errorExitState" is linked to the generic-error-state, the "breakExitState" to the abort-test-state. + /// + /// + /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: + /// -------------------------------------------------------------------------- + /// Sometimes production processes may be able to be executed simultaneously as they are independent of each + /// other. One example is a database access for data collection from different tables. + /// + /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of + /// the actual new assigned process): + /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without + /// waiting for the result, the "nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion" + /// has to be set to avoid a state change on successfully execution of the process. + /// Instead, the "kickOffParallelState" has to be defined to immediately start the next process. + /// + /// + /// INSTRUCTION TO DELAY THE START OF A PROCESS: + /// -------------------------------------------- + /// Each new process execution can be delayed as it may need the result of another process which is active. + /// + /// Wait for a single process to complete: + /// - set the "waitForSingleProcess" to the process-state to wait for. + /// + /// Wait for all preceding processes to complete: + /// - set the "waitForAllProcesses" to true. + /// + /// + public EolStateMachineCtrl() + { + var stateContainer = new List + { + #region ----------------------------------- GENERIC-STATES--------------------------------------------- + // Usage of the generic-state to detect the Cordonel on the communication line (request protocol) + // Detection of Cordonel has to be completed before assigning next state, so leave to automatic + // to change the state after successfully execution. The detection is the base for all others. + new ProcessStateStruct( + productionProcess: new DetectCordonel(Resources.StrStateDetectCordonel), + processState: ProcessState.DetectCordonel, + processInfo: Resources.StrStateDetectCordonel, + productionProcessNo: 0, + nextStateOnSuccess: ProcessState.CheckNewMeter), + + // Usage of the generic-state to check for new meter + // 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"! + new ProcessStateStruct( + productionProcess: null, + processState: ProcessState.CheckNewMeter, + processInfo: Resources.StrProcessStateCheckNewMeter, + productionProcessNo: null, + nextStateOnSuccess: ProcessState.DetectCordonel, + errorExitState: ProcessState.ConnectCordonel), + + // Usage of the generic-state to connect the Cordonel + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + new ProcessStateStruct( + productionProcess: new ConnectCordonel(Resources.StrStateConnectCordonel), + processState: ProcessState.ConnectCordonel, + processInfo: Resources.StrStateConnectCordonel, + productionProcessNo: 1, + // Avoid automatic change after completion as parallel process is active + nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion, + // Kick off parallel process execution + kickOffParallelState: ProcessState.GetSerialNumberAndRadioAddress), + + // Usage of the generic-state to get the serial-number and radio-address + // Precondition: - The detection process has to be completed as the PcbId is needed. + // AVOID to change the process state on completion as parallel process is active! + new ProcessStateStruct( + productionProcess: new GetSerialNumberAndRadioAddress(Resources.StrStateGetSerialAndRadioAddress), + processState:ProcessState.GetSerialNumberAndRadioAddress, + processInfo:Resources.StrStateGetSerialAndRadioAddress, + productionProcessNo: 2, + // Avoid automatic change after completion as parallel process is active + nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion, + // Kick off parallel process execution + kickOffParallelState:ProcessState.GetProgrammingParameters), + + #endregion -------------------------------- GENERIC-STATES -------------------------------------------- + + #region ----------------------------------- EOL-STATES ------------------------------------------------ + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + new ProcessStateStruct( + productionProcess: new GetFinalParametrization(Resources.StrStateGetProgrammingParameters), + processState: ProcessState.GetProgrammingParameters, + processInfo: Resources.StrStateGetProgrammingParameters, + productionProcessNo: 3, + // Avoid automatic change after completion as parallel process is active + nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion, + // Kick off parallel process execution + kickOffParallelState: ProcessState.CheckProductionState, + // Wait until this process has been successfully completed + waitForSingleProcess: ProcessState.ConnectCordonel), + + // EOL-state of the EOL software + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + // AVOID to change the process state on completion as parallel process is active! + new ProcessStateStruct( + productionProcess: new CheckProductionState(Resources.StrStateCheckProductionState), + processState: ProcessState.CheckProductionState, + processInfo: Resources.StrStateCheckProductionState, + productionProcessNo: 4, + // Avoid automatic change after completion as parallel process is active + nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion, + // Kick off parallel process execution + kickOffParallelState: ProcessState.ProcessPasswordFile, + // Wait until this process has been successfully completed + waitForSingleProcess: ProcessState.ConnectCordonel), + + + // EOL-state of the EOL software + // Precondition: - The connection process has to be completed as a password change in the DB + // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side + // effects on login. Wait for all processes to complete. + // Leave state switch to automatic to avoid side effects on incomplete updated DB for next + // steps. + new ProcessStateStruct( + productionProcess: new ExecPasswordFile(Resources.StrStatePasswordFile), + processState: ProcessState.ProcessPasswordFile, + processInfo: Resources.StrStatePasswordFile, + productionProcessNo: 5, + nextStateOnSuccess: ProcessState.ExecFinalParametrization, + waitForAllProcesses: true), + + // EOL-state of the EOL software + // This writes all setups to the device, it has to be executed exclusively with automatic + new ProcessStateStruct( + productionProcess: new ExecFinalParametrization(Resources.StrStateFinalParametrization), + processState: ProcessState.ExecFinalParametrization, + processInfo: Resources.StrStateFinalParametrization, + productionProcessNo: 6, + nextStateOnSuccess: ProcessState.ExecStoreConfiguration), + + // EOL-state of the EOL software + // Precondition: - The final parametrization has to be finished as this shall be stored + // to non-volatile memory and checked if remaining valid after the reboot. + // Store configuration has to be executed exclusively with automatic + new ProcessStateStruct( + productionProcess: new ExecStoreConfiguration(Resources.StrStateStoreConfiguration), + processState: ProcessState.ExecStoreConfiguration, + processInfo: Resources.StrStateStoreConfiguration, + productionProcessNo: 7, + nextStateOnSuccess: ProcessState.RebootCordonel), + + // EOL-state of the EOL software + // Precondition: - The store configuration has to be executed to safe all settings + // to non-volatile memory and checked if remaining valid after the reboot. + // Reboot has to be executed exclusively with automatic + new ProcessStateStruct( + productionProcess: new ExecRebootCordonel(Resources.StrStateRebootCordonel), + processState: ProcessState.RebootCordonel, + processInfo: Resources.StrStateRebootCordonel, + productionProcessNo: 8, + nextStateOnSuccess: ProcessState.CheckFinalParametrization), + + // EOL-state of the EOL software + // Precondition: - The reboot process including the store all configurations has to be + // completed. + // Check final configuration has to be executed exclusively with automatic + new ProcessStateStruct( + new CheckFinalParametrization(Resources.StrStateCheckParametrization), + ProcessState.CheckFinalParametrization, + Resources.StrStateCheckParametrization, + 9, + ProcessState.CheckRadio), + + // EOL-state of the EOL software + // Precondition: - The final parametrization, reboot process including the store all + // configurations and the check of all setups survived the reboot have to be completed. + // Radio signal level check, skipped on NA region or if skipping is required by special + // approval + new ProcessStateStruct( + new CheckRadio(Resources.StrStateRadioCheck), + ProcessState.CheckRadio, + Resources.StrStateRadioCheck, + 10, + ProcessState.CheckOrderNumber), + + // EOL-state of the EOL software + // This scans the order number, it has to be executed exclusively with automatic + new ProcessStateStruct( + new CheckOrderNumber(Resources.StrStateCheckOrderNumber), + ProcessState.CheckOrderNumber, + Resources.StrStateCheckOrderNumber, + 11, + ProcessState.CheckSerialNumber), + + // EOL-state of the EOL software + // This scans the serial number, it has to be executed exclusively with automatic + new ProcessStateStruct( + new CheckSerialNumber( Resources.StrStateCheckSerialNumber), + ProcessState.CheckSerialNumber, + Resources.StrStateCheckSerialNumber, + 12, + ProcessState.CheckAttachments), + + // EOL-state of the EOL software + // Precondition: - All checks have to be executed with positive result + new ProcessStateStruct( + new CheckAttachments(Resources.StrStateCheckAttachments), + ProcessState.CheckAttachments, + Resources.StrStateCheckAttachments, + 13, + ProcessState.ExecVisualInspection), + + // EOL-state of the EOL software + // Precondition: - All checks have to be executed with positive result + new ProcessStateStruct( + new VisualInspection(Resources.StrStateVisualInspection), + ProcessState.ExecVisualInspection, + Resources.StrStateVisualInspection, + 14, + ProcessState.PrepareShippingMode), + + // EOL-state of the EOL software + // Precondition: - All checks have to be executed with positive result + new ProcessStateStruct( + new PrepareShipping(Resources.StrStatePrepareShipping), + ProcessState.PrepareShippingMode, + Resources.StrStatePrepareShipping, + 15, + ProcessState.ReadyForShipping), + + // EOL-state of the EOL software + new ProcessStateStruct( + productionProcess: null, + processState:ProcessState.ReadyForShipping, + processInfo:Resources.StrStateReadyForShipping, + productionProcessNo:null, + nextStateOnSuccess:ProcessState.Idle), + #endregion -------------------------------- EOL-STATES ------------------------------------------------ + + }; + StateContainer.AddRange(stateContainer); + } + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineCtrl.cs similarity index 59% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineCtrl.cs index 20627021..dc167274 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessStates.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineCtrl.cs @@ -27,78 +27,88 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// protected BaseProcessStates() { - StateContainer = new List + StateContainer = new List { new ProcessStateStruct( - null, - ProcessState.Idle, - Resources.StrStateIdle, - null), + productionProcess: null, + processState: ProcessState.Idle, + processInfo: Resources.StrStateIdle, + productionProcessNo: null, + nextStateOnSuccess: ProcessState.Idle), new ProcessStateStruct( - null, - ProcessState.CheckNewMeter, - Resources.StrProcessStateCheckNewMeter, - null), + productionProcess: new ErrorHandler(null), + processState: ProcessState.Error, + processInfo: Resources.StrProcessStateError, + productionProcessNo: null, + nextStateOnSuccess: ProcessState.Idle, + startAlways: true), new ProcessStateStruct( - new ErrorHandler(null), - ProcessState.Error, - Resources.StrProcessStateError, - null), + productionProcess: null, + processState: ProcessState.RepeatFailedTests, + processInfo: Resources.StrStateRepeatFinalTest, + productionProcessNo: null, + nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion), new ProcessStateStruct( - null, - ProcessState.RepeatFailedTests, - Resources.StrStateRepeatFinalTest, - null), + productionProcess: null, + processState:ProcessState.AbortTest, + processInfo:Resources.StrStateSkipping, + productionProcessNo:null, + nextStateOnSuccess:ProcessState.Idle), new ProcessStateStruct( - null, - ProcessState.AbortTest, - Resources.StrStateSkipping, - null), + productionProcess: null, + processState:ProcessState.Stop, + processInfo:Resources.StrStateUserStop, + productionProcessNo:null, + nextStateOnSuccess:ProcessState.Idle), new ProcessStateStruct( - null, - ProcessState.Stop, - Resources.StrStateUserStop, - null), + productionProcess: null, + processState:ProcessState.PrintSuccessReport, + processInfo:Resources.StrStatePrintSuccessReport, + productionProcessNo:null, + nextStateOnSuccess:ProcessState.Idle), new ProcessStateStruct( - null, - ProcessState.TestFailed, - Resources.StrStateFinalTestFailed, - null), - - new ProcessStateStruct( - null, - ProcessState.PrintSuccessReport, - Resources.StrStatePrintSuccessReport, - null), - - new ProcessStateStruct( - null, - ProcessState.PrintReturnReport, - Resources.StrStatePrintReturnReport, - null), - + productionProcess: null, + processState:ProcessState.PrintReturnReport, + processInfo:Resources.StrStatePrintReturnReport, + productionProcessNo:null, + nextStateOnSuccess:ProcessState.Idle) }; } + /// + /// Get the process start struct linked to state of state machine + /// + /// state to search for information text + /// process state struct of the attached state + /// + /// - Initial + /// + public ProcessStateStruct GetProcessStateStructOfState(ProcessState stateMachineState) + { + return (from stateContainer in StateContainer + where stateContainer.ProcessState == stateMachineState + select stateContainer).FirstOrDefault(); + } + /// /// Get the process linked to state of state machine /// /// state to search for information text - /// process attached to state + /// process attached to the state /// /// - Initial /// public IProductionProcess GetProcessOfState(ProcessState stateMachineState) { return (from stateContainer in StateContainer - where stateContainer.State == stateMachineState - select stateContainer.Process).FirstOrDefault(); + where stateContainer.ProcessState == stateMachineState + select stateContainer.ProductionProcess).FirstOrDefault(); } /// @@ -118,8 +128,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses stateMachineState = ProcessState.Error; return (from stateContainer in StateContainer - where stateContainer.State == stateMachineState - select stateContainer.Info).FirstOrDefault(); + where stateContainer.ProcessState == stateMachineState + select stateContainer.ProcessInfo).FirstOrDefault(); } /// @@ -133,8 +143,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public String GetStateInfoOfProcess(IProductionProcess process) { return (from stateContainer in StateContainer - where stateContainer.Process == process - select stateContainer.Info).FirstOrDefault(); + where stateContainer.ProductionProcess == process + select stateContainer.ProcessInfo).FirstOrDefault(); } /// @@ -148,8 +158,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public ProcessState GetStateOfInfoText(String text) { return (from stateContainer in StateContainer - where stateContainer.Info == text - select stateContainer.State).FirstOrDefault(); + where stateContainer.ProcessInfo == text + select stateContainer.ProcessState).FirstOrDefault(); } /// @@ -163,8 +173,8 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses public ProcessState GetStateOfProcess(IProductionProcess process) { return (from stateContainer in StateContainer - where stateContainer.Process == process - select stateContainer.State).FirstOrDefault(); + where stateContainer.ProductionProcess == process + select stateContainer.ProcessState).FirstOrDefault(); } /// @@ -183,7 +193,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses } /// - /// Get all EOL relevant states + /// Get a sorted list of all production processes in order of the execution sequence /// /// list of initialized processes /// @@ -200,7 +210,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses var sortedList = StateContainer .Where(y => y.ProductionProcessNo != null) .OrderBy(x => x.ProductionProcessNo); - return (from state in sortedList select state.Process).ToList(); + return (from state in sortedList select state.ProductionProcess).ToList(); } } } diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs index 5a88c1ae..60dac181 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs @@ -7,6 +7,6 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// Individual states to fill the state machine. /// ATTENTION: All states have to be part of the /// - void IndividualStateMachine(IProductionProcess process); + void IndividualStateMachine(ProcessStateStruct processStateStruct); } } diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs index b635aa12..e8cc7deb 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessStateStruct.cs @@ -4,45 +4,105 @@ using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { /// - /// Concatenation between final test state and text + /// Concatenation between test state and text feeding the state machine with next state. /// public struct ProcessStateStruct { /// /// Assign process state information /// - /// production process, set to null for "invisible" states - /// assigned state for call of this process used by the state machine - /// name for process to display a message in the GUI + /// production process, set to null for "invisible" states + /// assigned state for call of this process used by the state machine + /// name for process to display a message in the GUI /// set a unique number to sort the table of production processes, /// set to null for all hidden processes which are not a production process + /// BaseProductionProcess.DoNotChangeStateAfterCompletion will not + /// change the state automatically + /// + /// + /// start a parallel process + /// wait for single processes to complete before execution + /// wait for all processes to complete before execution + /// start even if abort is required /// /// - Initial /// - public ProcessStateStruct(IProductionProcess process, ProcessState state, String info, - UInt32? productionProcessNo) + /// + /// - Extended to steer state machine with this struct. + /// + public ProcessStateStruct(IProductionProcess productionProcess, ProcessState processState, String processInfo, + UInt32? productionProcessNo, ProcessState? nextStateOnSuccess, ProcessState errorExitState = ProcessState.Error, + ProcessState breakExitState = ProcessState.Stop, + ProcessState? kickOffParallelState = null, + ProcessState? waitForSingleProcess = null, + Boolean waitForAllProcesses = false, Boolean startAlways = false) { - Process = process; - State = state; - Info = info; + ProductionProcess = productionProcess; + ProcessState = processState; + ProcessInfo = processInfo; ProductionProcessNo = productionProcessNo; + NextStateOnSuccess = nextStateOnSuccess; + ErrorExitState = errorExitState; + BreakExitState = breakExitState; + KickOffParallelState = kickOffParallelState; + WaitForSingleProcess = waitForSingleProcess; + WaitForAllProcesses = waitForAllProcesses; + StartAlways = startAlways; } + + /// + /// Start a parallel process + /// + public ProcessState? KickOffParallelState { get; } + /// /// The state of the final test /// - public IProductionProcess Process { get; } + public IProductionProcess ProductionProcess { get; } + /// /// The state of the final test /// - public ProcessState State { get; } + public ProcessState ProcessState { get; } + /// /// The info message of the state /// - public String Info { get; } + public String ProcessInfo { get; } + /// /// Processes which are relevant for the production have a number for the sequence they should be displayed /// in the GUI, all hidden (non-)production processes are set to null /// - public UInt32? ProductionProcessNo { get; } + public UInt32? ProductionProcessNo { get; } + + /// + /// Next state required after successfully execution + /// + public ProcessState? NextStateOnSuccess { get; } + + /// + /// Special state used for error or for the second output of the process + /// + public ProcessState ErrorExitState { get; } + + /// + /// Special state required on user break + /// + public ProcessState BreakExitState { get; } + + /// + /// Wait for a single process named here to take the result into account + /// + public ProcessState? WaitForSingleProcess { get; } + + /// + /// Wait until all preceding processes are finished with success + /// + public Boolean WaitForAllProcesses { get; } + /// + /// Sate which has to be active always even if a predecessor failed (e.g. error state) + /// + public Boolean StartAlways { get; } } } diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 153efcea..668cc06c 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -289,7 +289,7 @@ - + @@ -300,7 +300,7 @@ - + From 30c056f12855eac971749ad46b2435bf0659a153 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Tue, 15 Apr 2025 12:22:20 +0200 Subject: [PATCH 08/16] ProductionUiCordonel: - re-organized to support multiple production processes part 8 --- .../ProductionUiCordonel/FinalTest.xaml.cs | 5 +- .../ProductionUiCordonel/Picking.xaml.cs | 4 +- .../EolProcesses/EolStateMachine.cs | 144 ------------------ ...teMachineCtrl.cs => EolStateMachineDef.cs} | 4 +- .../EolProcesses/ExecPasswordFile.cs | 2 +- .../EolProcesses/GetFinalParametrization.cs | 2 +- ...chineCtrl.cs => GenericStateMachineDef.cs} | 0 .../ProductionProcesses/IProcessController.cs | 12 -- .../ProductionProcesses/IProcessStates.cs | 19 --- ...cessController.cs => ProcessController.cs} | 114 +++----------- .../ProductionUiCordonel.csproj | 9 +- 11 files changed, 30 insertions(+), 285 deletions(-) delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/{EolStateMachineCtrl.cs => EolStateMachineDef.cs} (99%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/{GenericStateMachineCtrl.cs => GenericStateMachineDef.cs} (100%) delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/{BaseProcessController.cs => ProcessController.cs} (86%) diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs index 6fecf460..c68dc4fa 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs +++ b/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs @@ -21,7 +21,6 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -54,7 +53,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private BaseProcessController _processCtrl; + private ProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -178,7 +177,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new EolStateMachine(Slot); + _processCtrl = new ProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/Picking.xaml.cs b/Common/Production/ProductionUiCordonel/Picking.xaml.cs index 7ec51aff..98a50c86 100644 --- a/Common/Production/ProductionUiCordonel/Picking.xaml.cs +++ b/Common/Production/ProductionUiCordonel/Picking.xaml.cs @@ -54,7 +54,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel // maximum processing steps for calculation of progress bar private Int32 _maxOverallProcessSteps; private Int32 _maxSingleProcessSteps; - private BaseProcessController _processCtrl; + private ProcessController _processCtrl; private DateTimeOffset _startTime; private DispatcherTimer _timer; private String _lastLoggingTextToAvoidRepetition; @@ -178,7 +178,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new EolStateMachine(Slot); + _processCtrl = new ProcessController(Slot); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs deleted file mode 100644 index 9b478550..00000000 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachine.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; - -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses -{ - /// - /// Preparing the Cordonel for shipping: - /// - Setup device with standard parameters covered by the VAKO or customer specific CSD, - /// - If radio is installed: radio test, - /// - Requiring radio key from LuDB, - /// - Requiring 7 passwords for the Cordonel access levels, - /// - Building the password file (Level 3 is the modified skeleton key), - /// - Store all configurations, - /// - Reboot the Cordonel to load all settings from FLASH to RAM, - /// - Configuration check of all parameters defined by VAKO and CSD. - /// - public sealed class EolStateMachine : BaseProcessController, IProcessController - { - #region ------------------------------------------ Variables -------------------------------------------------- - // - #endregion --------------------------------------- Variables -------------------------------------------------- - #region ------------------------------------------ Class ------------------------------------------------------ - - /// - /// Ctor - /// - /// physical slot for this meter - /// - /// - Initial - /// - public EolStateMachine(Int32 slot) : base(slot) - { - } - - #endregion --------------------------------------- Class ------------------------------------------------------ - #region ------------------------------------------ Events ----------------------------------------------------- - // - #endregion --------------------------------------- Events ----------------------------------------------------- - #region ------------------------------------------ State Machine ---------------------------------------------- - /// - /// - /// State machine of FinalProcessController which serves as preparation for SHIPPING named EOL (End-Of-Line). - /// - /// - /// - Initial - /// - public override void IndividualStateMachine(ProcessStateStruct processStateStruct) - { - switch (_stateMachineState) - { - case ProcessState.GetProgrammingParameters: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(processStateStruct); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.CheckProductionState; - break; - - case ProcessState.CheckProductionState: - // Precondition: - The connection process has to be completed as a readout of some register - // values from the Cordonel are needed to check the update capability: - // - Wait on completed connection process before starting this process. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(processStateStruct); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.ProcessPasswordFile; - break; - - case ProcessState.ProcessPasswordFile: - // Precondition: - The connection process has to be completed as a password change in the DB - // will take place: Skeleton to Password Level 8. Not waiting for completion may cause side - // effects on login. Wait for all processes to complete. - // Leave state switch to automatic to avoid side effects on incomplete updated DB for next - // steps. - StartProcess(processStateStruct); - break; - - case ProcessState.ExecFinalParametrization: - // This writes all setups to the device, it has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - - case ProcessState.ExecStoreConfiguration: - // Precondition: - The final parametrization has to be finished as this shall be stored - // to non-volatile memory and checked if remaining valid after the reboot. - // Store configuration has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - - case ProcessState.RebootCordonel: - // Precondition: - The store configuration has to be executed to safe all settings - // to non-volatile memory and checked if remaining valid after the reboot. - // Reboot has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - - case ProcessState.CheckFinalParametrization: - // Precondition: - The reboot process including the store all configurations has to be - // completed. - // Check final configuration has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - - case ProcessState.CheckRadio: - // Precondition: - The final parametrization, reboot process including the store all - // configurations and the check of all setups survived the reboot have to be completed. - // Radio signal level check, skipped on NA region or if skipping is required by special - // approval - StartProcess(processStateStruct); - break; - - case ProcessState.CheckAttachments: - // Precondition: - All checks have to be executed with positive result - StartProcess(processStateStruct); - break; - - case ProcessState.ExecVisualInspection: - // Precondition: - All checks have to be executed with positive result - StartProcess(processStateStruct); - break; - - case ProcessState.PrepareShippingMode: - // Precondition: - All checks have to be executed with positive result - StartProcess(processStateStruct); - break; - - case ProcessState.ReadyForShipping: - _stateMachineState = ProcessState.Idle; - break; - - default: - _stateMachineState = ProcessState.Idle; - break; - } - } - - #endregion --------------------------------------- State Machine ---------------------------------------------- - #region ------------------------------------------ Tools ------------------------------------------------------ - // - #endregion --------------------------------------- Tools ------------------------------------------------------ - } -} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineDef.cs similarity index 99% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineDef.cs index 54e63a8e..0be950b2 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineCtrl.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolStateMachineDef.cs @@ -14,7 +14,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Initial /// - public class EolStateMachineCtrl : BaseProcessStates + public class EolStateMachineDef : BaseProcessStates { /// /// State-machine definitions of production-processes which feeds the state-machine. @@ -51,7 +51,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// - set the "waitForAllProcesses" to true. /// /// - public EolStateMachineCtrl() + public EolStateMachineDef() { var stateContainer = new List { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs index 11d6bcf6..eba4c62d 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ExecPasswordFile.cs @@ -66,7 +66,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if passwords are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs index aa67ea14..e08cafe3 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/GetFinalParametrization.cs @@ -39,7 +39,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - Speed up to quick return if programming parameters are already set. /// - /// ATTENTION: This requires to remove those on new PcbId + /// ATTENTION: This requires to remove those on new PcbId /// for CheckMeterChange! /// /// diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineCtrl.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineDef.cs similarity index 100% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineCtrl.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/GenericStateMachineDef.cs diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs deleted file mode 100644 index 60dac181..00000000 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessController.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses -{ - public interface IProcessController - { - /// - /// Individual states to fill the state machine. - /// ATTENTION: All states have to be part of the - /// - void IndividualStateMachine(ProcessStateStruct processStateStruct); - } -} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs deleted file mode 100644 index 8d99734e..00000000 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStates.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Windows.Controls; -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.Production.ProductionUiCordonel.ProductionProcesses.Enum; -using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; - -namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses -{ - public interface IProcessStates - { - - } -} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs similarity index 86% rename from Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs rename to Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs index 08454429..eb2ba8bc 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/BaseProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs @@ -17,7 +17,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// Implementation for all methods needed to drive the state machine of the process controller /// - public abstract class BaseProcessController : IProcessController + public class ProcessController { #region ------------------------------------------ Variables -------------------------------------------------- // actual genesis meter @@ -31,7 +31,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // batch just used for setup, as only one meter will be assigned private MeterBatch _meterBatch; // state of the state machine - protected ProcessState _stateMachineState; + private ProcessState _stateMachineState; // locker to avoid repeated state execution private ProcessState _lockedProcessState; // sender of state change event to evaluate in e.g. error process @@ -44,7 +44,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses } // Container for production process states - protected BaseProcessStates _processStateDef; + private BaseProcessStates _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) @@ -121,7 +121,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Always RegisterWatchService on, /// - Always MinMaxCHeck on. /// - protected BaseProcessController(Int32 slot) + public ProcessController(Int32 slot) { _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); _slot = slot; @@ -286,62 +286,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// private void TmrProcessCtrlCycle_Elapsed(Object state) { - GenericStateMachine(); + StateMachine(); } #endregion --------------------------------------- Events ----------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- - /// - public abstract void IndividualStateMachine(ProcessStateStruct processSatProcessStateStruct); - /// - /// State machine of ProcessController which serves as template for all production processes. + /// 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". /// - /// - /// INSTRUCTION FOR NORMAL USAGE: - /// ----------------------------- - /// Call the SetupProcess() with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState set to the next required state after successfully execution. - /// That's it! The normal state change has always only one production process active and waits for completion - /// before changing to a new state and therefore a new production process. - /// - /// If a special state is needed on errorExitState or breakExitState these can be assigned. Otherwise, the - /// errorExitState is linked to the error state, the breakExitState to the stop state. - /// - /// - /// INSTRUCTION FOR NON-BLOCKING MULTIPLE PROCESSES SIMULTANEOUSLY ACTIVITIES: - /// -------------------------------------------------------------------------- - /// Sometimes production processes may be able to be executed simultaneously as they are independent of each - /// other. One example is a database access for data collection from different tables. - /// - /// Call the SetupProcess with the - /// - new process automatically collected from with new - /// _actualProcessState as input, - /// - successExitState being set to (null). - /// This will lock the sate change after completion as multiple processes may be active and a state change after - /// completion may cause unpredictable operation, the state is already where it should be (manually changed), - /// - change the state after the SetupProcess() call manually. - /// - /// This is needed for non-blocking immediately new state change (not waiting for the successful execution of - /// the actual new assigned process): - /// E.g. all DB accesses need a certain time to get data, kicking off (fire and forget) the process without - /// waiting for the result, the exitSuccessState has to be set to null to avoid a state change on successfully - /// execution of the process. Instead, change the state here to go ahead with the next state. This forces a wait - /// on the state who needs the data from the "fire and forget" states, SEE next instruction how to proceed! - /// - /// - /// INSTRUCTION TO DELAY THE START OF A PROCESS: - /// -------------------------------------------- - /// Each new process execution can be delayed as it may need the result of another process which is active. - /// - /// Wait for a single process to complete: - /// - set the "waitForSingleProcess" to the process to wait for. - /// Wait for all preceding processes to complete: - /// - set the "waitForAllProcesses" to true. - /// + /// A few states with special functions will be caught in advance in this StateMachine /// /// /// - Initial @@ -349,7 +305,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Generic implementation /// - private void GenericStateMachine() + private void StateMachine() {// avoid repeated execution of the state machine on unchanged state if (_stateMachineState == _lockedProcessState) { @@ -371,9 +327,6 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // get the processStateStruct of the actual set stateMachineSTate var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState); - // get the actual required process from the state definition list - //var process = _processStateDef?.GetProcessOfState(_stateMachineState); - switch (_stateMachineState) { case ProcessState.Idle: @@ -384,7 +337,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses // Wait for cancellation if (_cancellationToken.IsCancellationRequested) { - Thread.Sleep(1000); + Thread.Sleep(500); // Reset the cancellation request _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); @@ -407,44 +360,11 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; break; - case ProcessState.ConnectCordonel: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(processStateStruct); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetSerialNumberAndRadioAddress; - break; - - case ProcessState.GetSerialNumberAndRadioAddress: - // Precondition: - The detection process has to be completed as the PcbId is needed. - // AVOID to change the process state on completion as parallel process is active! - StartProcess(processStateStruct); - // Do not wait on completion as new parallel process can be started by manually state change. - _stateMachineState = ProcessState.GetProgrammingParameters; - break; - - case ProcessState.CheckOrderNumber: - // This scans the order number, it has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - - case ProcessState.CheckSerialNumber: - // This scans the serial number, it has to be executed exclusively with automatic - StartProcess(processStateStruct); - break; - + case ProcessState.RepeatFailedTests: _stateMachineState = RepeatFailedTests(); break; - case ProcessState.PrintReturnReport: - _stateMachineState = ProcessState.Idle; - break; - - case ProcessState.PrintSuccessReport: - _stateMachineState = ProcessState.Idle; - break; - case ProcessState.AbortTest: AbortProcesses(); _stateMachineState = ProcessState.Idle; @@ -469,8 +389,12 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses break; default: - // This is the call to the processes for a specific test e.g. EOL or Picking - IndividualStateMachine(processStateStruct); + // This is the call to the processes for a specific test + StartProcess(processStateStruct); + if (processStateStruct.KickOffParallelState != null) + { + _stateMachineState = (ProcessState)processStateStruct.KickOffParallelState; + } break; } } @@ -675,7 +599,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses if (Processes == null) Processes = new List(); if (_processStateDef == null) - _processStateDef = new EolStateMachineCtrl(); + _processStateDef = new EolStateMachineDef(); // get all processes relevant for EOL from table which has to be sorted var processes = _processStateDef.GetAllEolRelevantProcesses(); diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 668cc06c..7a1c73cd 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -289,7 +289,7 @@ - + @@ -300,10 +300,7 @@ - - - - + @@ -334,7 +331,7 @@ - + From 66f457dc0b1e0fb236c606247be7fd571f55fa0c Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Tue, 15 Apr 2025 15:58:21 +0200 Subject: [PATCH 09/16] ProductionUiCordonel: - re-organized to support multiple production processes - final - picking can be implemented --- .../ProductionUiCordonel/App.xaml.cs | 25 +- .../ProductionUiCordonel/MainWindow.xaml | 78 -- .../ProductionUiCordonel/MainWindow.xaml.cs | 451 --------- .../ProductionUiCordonel/MultiMode.xaml | 152 --- .../ProductionUiCordonel/MultiMode.xaml.cs | 206 ---- .../ProductionUiCordonel/MultiModeChild.xaml | 84 -- .../MultiModeChild.xaml.cs | 135 --- .../ProductionUiCordonel/Picking.xaml | 155 --- .../ProductionUiCordonel/Picking.xaml.cs | 928 ------------------ ...eMachineDef.cs => BaseProcessStatesDef.cs} | 88 +- .../EolProcesses/CheckProductionState.cs | 2 +- ...teMachineDef.cs => EolProcessStatesDef.cs} | 187 ++-- .../EolProcesses/ExecPasswordFile.cs | 13 +- .../ProductionCheckShippingState.cs | 192 ---- .../EolProcesses/ProductionProcessDone.cs | 143 --- .../ProductionProcessFinalParameterization.cs | 364 ------- ...roductionProcessFinalParametrizationFix.cs | 355 ------- ...ctionProcessFinalParametrizationVakoCsd.cs | 273 ------ .../ProductionProcessFullReadOut.cs | 107 -- ...onProcessGetFinalParametrizationVakoCsd.cs | 86 -- .../ProductionProcessPasswordFile.cs | 155 --- .../ProductionProcessRadioCheck.cs | 473 --------- .../ProductionProcessRegisterWatch.cs | 72 -- .../ProductionProcessShippingMode.cs | 258 ----- .../ProductionProcessSpecialConfig.cs | 162 --- .../ProductionProcesses/IProcessStateDef.cs | 104 ++ .../PickingProcessStatesDef.cs | 168 ++++ .../ProductionProcesses/ProcessController.cs | 10 +- .../ProductionUiCordonel.csproj | 42 +- .../{FinalTest.xaml => ProductionWindow.xaml} | 2 +- ...lTest.xaml.cs => ProductionWindow.xaml.cs} | 14 +- .../Properties/Resources.Designer.cs | 29 +- .../Properties/Resources.de.resx | 5 +- .../Properties/Resources.resx | 5 +- 34 files changed, 467 insertions(+), 5056 deletions(-) delete mode 100644 Common/Production/ProductionUiCordonel/MainWindow.xaml delete mode 100644 Common/Production/ProductionUiCordonel/MainWindow.xaml.cs delete mode 100644 Common/Production/ProductionUiCordonel/MultiMode.xaml delete mode 100644 Common/Production/ProductionUiCordonel/MultiMode.xaml.cs delete mode 100644 Common/Production/ProductionUiCordonel/MultiModeChild.xaml delete mode 100644 Common/Production/ProductionUiCordonel/MultiModeChild.xaml.cs delete mode 100644 Common/Production/ProductionUiCordonel/Picking.xaml delete mode 100644 Common/Production/ProductionUiCordonel/Picking.xaml.cs rename Common/Production/ProductionUiCordonel/ProductionProcesses/{GenericStateMachineDef.cs => BaseProcessStatesDef.cs} (60%) rename Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/{EolStateMachineDef.cs => EolProcessStatesDef.cs} (68%) delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionCheckShippingState.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessDone.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessFinalParameterization.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessFinalParametrizationFix.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessFinalParametrizationVakoCsd.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessFullReadOut.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessGetFinalParametrizationVakoCsd.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessPasswordFile.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessRadioCheck.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessRegisterWatch.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessShippingMode.cs delete mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/ProductionProcessSpecialConfig.cs create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/IProcessStateDef.cs create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs rename Common/Production/ProductionUiCordonel/{FinalTest.xaml => ProductionWindow.xaml} (99%) rename Common/Production/ProductionUiCordonel/{FinalTest.xaml.cs => ProductionWindow.xaml.cs} (98%) diff --git a/Common/Production/ProductionUiCordonel/App.xaml.cs b/Common/Production/ProductionUiCordonel/App.xaml.cs index d2e9d9e7..5909ed80 100644 --- a/Common/Production/ProductionUiCordonel/App.xaml.cs +++ b/Common/Production/ProductionUiCordonel/App.xaml.cs @@ -1,6 +1,8 @@ using System; using System.Configuration; using System.Windows; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.PickingProcesses; namespace Xylem.Common.Production.ProductionUiCordonel { @@ -11,24 +13,27 @@ namespace Xylem.Common.Production.ProductionUiCordonel { private void Application_Startup(Object sender, StartupEventArgs e) { - switch (ConfigurationManager.AppSettings[$"Mode"]) + switch (ConfigurationManager.AppSettings["Mode"]) { case "1": - - var mainWindow = new FinalTest(); - mainWindow.Show(); + var ProductionWindow = new ProductionWindow( + ProductionUiCordonel.Properties.Resources.StrEolSoftwareNameVersion, + "ProductionUiCordonel", new EolProcessStateDef()); + ProductionWindow.Show(); break; case "2": - var Picking = new Picking(); - Picking.Show(); + ProductionWindow = new ProductionWindow( + ProductionUiCordonel.Properties.Resources.StrPickingSoftwareNameVersion, + "PickingUiCordonel", new PickingProcessStateDef()); + ProductionWindow.Show(); break; default: - var mainWindowDef = new FinalTest(); - mainWindowDef.Show(); + ProductionWindow = new ProductionWindow( + ProductionUiCordonel.Properties.Resources.StrEolSoftwareNameVersion, + "ProductionUiCordonel", new EolProcessStateDef()); + ProductionWindow.Show(); break; } - - } } } diff --git a/Common/Production/ProductionUiCordonel/MainWindow.xaml b/Common/Production/ProductionUiCordonel/MainWindow.xaml deleted file mode 100644 index e878071f..00000000 --- a/Common/Production/ProductionUiCordonel/MainWindow.xaml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Common/Production/ProductionUiCordonel/MainWindow.xaml.cs b/Common/Production/ProductionUiCordonel/MainWindow.xaml.cs deleted file mode 100644 index a96dcba9..00000000 --- a/Common/Production/ProductionUiCordonel/MainWindow.xaml.cs +++ /dev/null @@ -1,451 +0,0 @@ -using System; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using System.Timers; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Threading; -using Newtonsoft.Json; -using Xylem.Common.CommonCore.Configuration; -using Xylem.Common.CommonCore.Consts; -using Xylem.Common.Hardware.Interfaces.Ports.PortCore; -using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; -using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; -using Xylem.Common.Logic.SoftwareAccessHelper; -using Xylem.Common.Production.ProductionUiCordonel.Model; - -namespace Xylem.Common.Production.ProductionUiCordonel -{ - //public class QuantityToBackgroundConverter : IValueConverter - //{ - // public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - // { - // var dc = DisplayCodes.None; - - // if (DisplayCodes.TryParse(value.ToString(), out dc)) - // { - // string hex = ""; - // int intValue; - // if (int.TryParse(value.ToString(), out intValue)) - // { - // hex = intValue.ToString("X"); - // } - // return $"{dc} ({hex})"; - // } - // else - // { - // return "?"; - // } - - - // } - - // public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - // { - // throw new NotImplementedException(); - // } - //} - - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow - { - private Timer tmrAutoDetect; - - public EventHandler OnPcbIdDetect; - - private Int32 intervalWithoutDetect = 1000; - private Int32 intervalWithDetect = 10000; - private String _currentPcbID; - - - public PickingViewModel PickingData = new PickingViewModel(); - - public String CurrentPcbID - { - set - { - if (_currentPcbID != value && !(string.IsNullOrEmpty(_currentPcbID) && value == null)) - { - - if (_currentPcbID != value) - { - - if (!string.IsNullOrEmpty(_currentPcbID) && string.IsNullOrEmpty(value)) - { - _currentPcbID = value; - updateUi(lblPcbID, _currentPcbID); - - - } - else - { - _currentPcbID = value; - updateUi(lblPcbID, _currentPcbID); - - if (!string.IsNullOrEmpty(_currentPcbID)) - { - GetPcbMapping(_currentPcbID); - } - } - } - OnPcbIdDetect?.Invoke(CurrentPcbID, EventArgs.Empty); - } - - } - get => _currentPcbID; - } - - public enum uiState - { - not_connected, - connected - } - - private uiState _currentState; - public uiState CurrentState - { - set - { - _currentState = value; - updateUi(lblState, _currentState.ToString()); - } - get => _currentState; - } - - private String _lastMsg; - public String LastMsg - { - set - { - _lastMsg = value; - updateUi(lblMessage, _lastMsg); - } - get => _lastMsg; - } - - public MainWindow() - { - try - { - if (true) - { - - } - InitializeComponent(); - - } - catch (Exception ex) - { - - MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace); - } - - } - public ApplicationSettings Settings { get; set; } - private void Window_Initialized(Object sender, EventArgs e) - { - var configfile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - nameof(Hardware.WaterMeter.Genesis), ProgramConfig.SerialConfigFileName); - - if (!File.Exists(configfile)) - { - throw new ApplicationException($"Configuration file {configfile} not found "); - } - var tr = new StreamReader(configfile); - var meterConfigList = JsonConvert.DeserializeObject(tr.ReadToEnd()); - - cbxSlotBox.Items.Clear(); - foreach (var item in meterConfigList.OrderBy(s => s.Slot)) - { - cbxSlotBox.Items.Add(item.Slot); - } - - cbxSlotBox.SelectedIndex = 0; - tmrAutoDetect = new Timer(intervalWithoutDetect); - tmrAutoDetect.Elapsed += TmrAutoDetect_Elapsed; - tmrAutoDetect.Enabled = false; - - - Settings = new ApplicationSettings("ProductionUiConfig.json"); - - - useSetting(true); - } - - private void TmrAutoDetect_Elapsed(Object sender, ElapsedEventArgs e) - { - tmrAutoDetect.Enabled = false; - var hasDetect = Detect(true, 1); - if (hasDetect) - { - tmrAutoDetect.Interval = intervalWithDetect; - } - else - { - tmrAutoDetect.Interval = intervalWithoutDetect; - } - tmrAutoDetect.Enabled = Settings.AutoDetect; - } - - - private void BtnConnect_Click(Object sender, RoutedEventArgs e) - { - var hasDetect = Detect(false, (Int32)cbxSlotBox.SelectedValue); - - Settings.Slot = (Int32)cbxSlotBox.SelectedValue; - storeSetting(); - } - - private void updateUi(ContentControl c, String content) - { - c.Dispatcher.Invoke(DispatcherPriority.Normal, - new Action(() => { c.Content = content; })); - } - private void updateUi(TextBox c, String content) - { - c.Dispatcher.Invoke(DispatcherPriority.Normal, - new Action(() => { c.Text = content; })); - } - private void updateUi(TextBlock c, String content) - { - c.Dispatcher.Invoke(DispatcherPriority.Normal, - new Action(() => { c.Text = content; })); - } - - private MeterBatch CurrentBatch; - private IGenesisMeter _currentMeter; - private Boolean detectIsLocked; - - private IGenesisMeter CurrentMeter - { - get => _currentMeter; - set - { - _currentMeter = value; - if (ucFinalRadioTest != null) - { - if (_currentMeter?.PcbId != null) - { - // ucFinalRadioTest.GenesisMeter = value; - } - else - { - // ucFinalRadioTest.GenesisMeter = null; - } - - } - } - } - - private Boolean Detect(Boolean ignoreErrors, Int32 slot) - { - if (!detectIsLocked) - { - LastMsg = "Start Detect"; - Boolean hasDetect = false; - if (string.IsNullOrEmpty(CurrentPcbID) || CurrentMeter == null) - { - var mb = new MeterBatch(); - var tryMeter = new GenesisMeter(); - try - { - tryMeter.SetupFromConfigFile(slot); - mb.AddMeter(tryMeter); - tryMeter.Logout(); - LastMsg = "Try to get PcbID"; - CurrentPcbID = tryMeter.GetPcbId(); - - hasDetect = !string.IsNullOrEmpty(CurrentPcbID); - CurrentBatch = mb; - CurrentMeter = tryMeter; - if (hasDetect) - { - LastMsg = $"New PcbID detected {CurrentPcbID}"; - - CurrentMeter.Login(); - } - else - { - LastMsg = "No Meter found"; - DisposeAllConnections(); - } - - } - catch (Exception e) - { - if (!ignoreErrors) - { - MessageBox.Show($"Error occur.{ Environment.NewLine} {e.Message}"); - } - - } - - } - else - { - LastMsg = "Check if Meter is still connected"; - var ret = CurrentMeter.GetPcbId(); - if (string.IsNullOrEmpty(ret) || ret != CurrentPcbID) - { - LastMsg = "Meter connection lost"; - hasDetect = false; - DisposeAllConnections(); - - CurrentPcbID = ""; - } - else - { - LastMsg = "Meter is still connected"; - hasDetect = true; - } - } - return hasDetect; - } - - LastMsg = "Meter is still connected (locked)"; - return true; - } - - - - private void Window_Closing(Object sender, CancelEventArgs e) - { - DisposeAllConnections(); - } - - private void DisposeAllConnections() - { - - if (CurrentBatch != null) - { - CurrentBatch.RemoveAllMeters(); - if (CurrentMeter != null) - { - CurrentMeter.Dispose(); - } - CurrentBatch.Dispose(); - } - PickingData.Empty(); - - } - - - - - private void GetPcbMapping(String pcbID) - { - if (string.IsNullOrEmpty(pcbID)) - { - return; - } - var url = $"{ServiceUrls.MarriageServiceUrl()}GetSerialNumberAndOrderNumber?PcbId={pcbID}&withRadioAdress=true"; - - Task.Run(() => - { - try - { - var responseJson = LocalWebRequest.GetRequest(url); - var a = JsonConvert.DeserializeObject(responseJson); - - - if (ucFinalRadioTest != null) - { - var tmpInt = 0; - int.TryParse(a[1], out tmpInt); - // ucFinalRadioTest.OrderNumber = tmpInt; - - tmpInt = 0; - if (a.Length > 2) - { - int.TryParse(a[2], out tmpInt); - - } - - //ucFinalRadioTest.RadioAddress = tmpInt; - // ucFinalRadioTest.SerialNumber = a[0] + "/" + a[3]; - - } - - - } - catch (Exception ex) - { - if (ex is WebException) - { - MessageBox.Show($"Error on Store: {Environment.NewLine} { ((WebException)ex).Message}"); - } - - MessageBox.Show(ex.Message); - } - }); - } - - - - - - - - private void Button_Click(Object sender, RoutedEventArgs e) - { - - } - - private void UcFinalRadioTest_OnLockMeter(Object sender, EventArgs e) - { - detectIsLocked = true; - - } - - private void UcFinalRadioTestOnOnReleaseMeter(Object sender, EventArgs e) - { - detectIsLocked = false; - //ucFinalRadioTest.SetUpBlank(false); - } - - - private void BtnSwitchAutoDetect_Click(Object sender, RoutedEventArgs e) - { - Settings.AutoDetect = !Settings.AutoDetect; - storeSetting(); - useSetting(); - } - - private void CbxSlotBox_SelectionChanged(Object sender, SelectionChangedEventArgs e) - { - - } - private void storeSetting() - { - Settings.Update("ProductionUiConfig.json"); - } - - - - private void useSetting(Boolean setSlot = false) - { - tmrAutoDetect.Enabled = Settings.AutoDetect; - tblkAutodetectState.Text = "Off"; - if (Settings.AutoDetect) - { - tblkAutodetectState.Text = "On"; - } - if (setSlot) - { - cbxSlotBox.SelectedValue = Settings.Slot; - } - } - - private void StackPanel_SizeChanged(Object sender, SizeChangedEventArgs e) - { - ucFinalRadioTest.Height = e.NewSize.Height; - - } - - } -} diff --git a/Common/Production/ProductionUiCordonel/MultiMode.xaml b/Common/Production/ProductionUiCordonel/MultiMode.xaml deleted file mode 100644 index 3e3fd0a6..00000000 --- a/Common/Production/ProductionUiCordonel/MultiMode.xaml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/Common/Production/ProductionUiCordonel/MultiMode.xaml.cs b/Common/Production/ProductionUiCordonel/MultiMode.xaml.cs deleted file mode 100644 index 8a7b6e0d..00000000 --- a/Common/Production/ProductionUiCordonel/MultiMode.xaml.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using Xylem.Common.Production.ProductionUiCordonel.Model; - -namespace Xylem.Common.Production.ProductionUiCordonel -{ - /// - /// Interaktionslogik für MultiMode.xaml - /// - public partial class MultiMode : Window - { - //#region Raw Input API - - //[DllImport("User32.dll")] - //extern static uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize); - - //[DllImport("User32.dll")] - //extern static uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize); - - //[DllImport("User32.dll")] - //extern static bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize); - - //[DllImport("User32.dll")] - //extern static uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader); - - //#endregion - - MultiModeMasterViewModel multiModeMasterViewModel = new MultiModeMasterViewModel(); - - private List Childs = new List(); - - public ApplicationSettings AppSettings { get; private set; } - //class RawInputDevice : InputDevice - //{ - // public override IInputElement Target => throw new NotImplementedException(); - - // public override PresentationSource ActiveSource => throw new NotImplementedException(); - //} - public MultiMode() - { - - DataContext = multiModeMasterViewModel; - - InitializeComponent(); - Topmost = false; - } - private void keyUp(Object sender, KeyEventArgs e) - { - - } - public void SetBaseFocus() - { - //foreach (var item in Childs) - //{ - // item.Key.Activate(); - //} - //this.Activate(); - //txtBarcodeInput.Focus(); - - } - - - private MultiModeChild addChild(IMultiModeChildViewModel model) - { - var a = new MultiModeChild(model); - - a.Show(); - return a; - } - CancellationTokenSource cts; - - private void Window_Loaded(Object sender, RoutedEventArgs e) - { - cts = new CancellationTokenSource(); - AppSettings = new ApplicationSettings("MultiProductionUiConfig.json"); - multiModeMasterViewModel.StartPicking(cts); - //var trigger = new List(); - - //if (AppSettings.ChildForms == null || !AppSettings.ChildForms.Any()) - //{ - // AppSettings.ChildForms = new List(); - - // AppSettings.ChildForms.Add(new ApplicationSettings.MultiFormChilds() { Name = "Picking", Slot = 1, ViewModelType = typeof(PickingMultiModeViewModel), StartPos = null, Visisble = true, TextPort = "COM11" }); - // AppSettings.ChildForms.Add(new ApplicationSettings.MultiFormChilds() { Name = "PressureHE", Slot = 3, ViewModelType = typeof(PressureMultiModeViewModel), StartPos = null, Visisble = true, TextPort = null }); - // AppSettings.ChildForms.Add(new ApplicationSettings.MultiFormChilds() { Name = "PressureHY", Slot = 1, ViewModelType = typeof(PressureMultiModeViewModel), StartPos = null, Visisble = true, TextPort = null }); - // AppSettings.ChildForms.Add(new ApplicationSettings.MultiFormChilds() { Name = "Marriage", Slot = 4, ViewModelType = typeof(MarriageMultiModeViewModel), StartPos = null, Visisble = true, TextPort = "COM1" }); - // AppSettings.Update("MultiProductionUiConfig.json"); - //} - - - ////AppSettings.ChildForms.ForEach(c => trigger.Add(c.TextPort.ToString())); - - //cts = new CancellationTokenSource(); - - //foreach (var item in AppSettings.ChildForms) - //{ - // if (item.Visisble) - // { - - - // IMultiModeChildViewModel mdl = null; - // if (item.ViewModelType == typeof(PickingMultiModeViewModel)) - // { - // mdl = new PickingMultiModeViewModel(item.Name, item.Slot, trigger, AppSettings.StationId); - // } - // else if (item.ViewModelType == typeof(PressureMultiModeViewModel)) - // { - // mdl = new PressureMultiModeViewModel(item.Name, item.Slot, trigger, AppSettings.StationId); - // } - // else if (item.ViewModelType == typeof(MarriageMultiModeViewModel)) - // { - // mdl = new MarriageMultiModeViewModel(item.Name, item.Slot, trigger, AppSettings.StationId); - // } - // if (mdl == null) - // { - // throw new ApplicationException("Bad configuration or old software"); - // } - // var child = addChild(mdl); - - // child.StartTextInputListener(item.TextPort, cts.Token); - - // if (item.StartPos != null) - // { - // child.WindowStartupLocation = WindowStartupLocation.Manual; - // child.Top = item.StartPos.Top; - // child.Left = item.StartPos.Left; - // child.Height = item.StartPos.Height; - // child.Width = item.StartPos.Width; - // child.UseDefaultPos = false; - // } - // child.PosChanged += delegate (object val, EventArgs a) - // { - // if (val is MultiModeChild mmc) - // { - // if (mmc.ViewModel is BaseMultiModeChildViewModel baseChild) - // { - // var aasd = AppSettings.ChildForms.FirstOrDefault(c => c.Name == baseChild.ChildName); - // aasd.StartPos = new ApplicationSettings.Pos() { Height = mmc.Height, Left = mmc.Left, Top = mmc.Top, Width = mmc.Width }; - - // AppSettings.Update("MultiProductionUiConfig.json"); - // } - // } - - // }; - - - // child.Show(); - - // Childs.Add(child); - - // } - - //} - - - - //SetBaseFocus(); - } - - private void delegeChild_PosChange(Object sender, EventArgs e) - { - throw new NotImplementedException(); - } - - private void TxtBarcodeInput_TextChanged(Object sender, TextChangedEventArgs e) - { - //if (sender is TextBox text) - //{ - // if (text.Text.ToLower().Contains("clear")) - // { - // text.Text = string.Empty; - // } - // else if (text.Text.Contains(System.Environment.NewLine)) - // { - // foreach (var dispatchChild in Childs.Where(x => x.Value == text.Text[0])) - // { - // dispatchChild.Key.InputText(text.Text.Substring(1, text.Text.Length - 1).Trim()); - // } - - // text.Text = ""; - // SetBaseFocus(); - // } - - //} - - } - - private void Window_Closed(Object sender, EventArgs e) - { - - cts.Cancel(); - - - foreach (var item in Childs) - { - item.Dispose(); - - item.Close(); - } - } - } -} diff --git a/Common/Production/ProductionUiCordonel/MultiModeChild.xaml b/Common/Production/ProductionUiCordonel/MultiModeChild.xaml deleted file mode 100644 index 5b560b2f..00000000 --- a/Common/Production/ProductionUiCordonel/MultiModeChild.xaml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - /// physical slot for this meter + /// /// /// - Initial /// @@ -121,12 +122,13 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// - Always RegisterWatchService on, /// - Always MinMaxCHeck on. /// - public ProcessController(Int32 slot) + public ProcessController(Int32 slot, IProcessStateDef processSateDef) { _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); _slot = slot; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; + _processStateDef = processSateDef; InitNewMeter(); InitProcesses(); // program start with idle process @@ -598,8 +600,6 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses { if (Processes == null) Processes = new List(); - if (_processStateDef == null) - _processStateDef = new EolStateMachineDef(); // get all processes relevant for EOL from table which has to be sorted var processes = _processStateDef.GetAllEolRelevantProcesses(); diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 7a1c73cd..4565ffaf 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -283,13 +283,11 @@ ctlMultiModePressure.xaml - - Picking.xaml - - + + @@ -300,7 +298,8 @@ - + + @@ -311,12 +310,6 @@ - - MultiMode.xaml - - - MultiModeChild.xaml - @@ -376,37 +369,17 @@ Designer MSBuild:Compile - - MSBuild:Compile - Designer - - + Designer MSBuild:Compile - - MSBuild:Compile - Designer - App.xaml Code - - FinalTest.xaml + + ProductionWindow.xaml - - MainWindow.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -620,7 +593,6 @@ - diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml similarity index 99% rename from Common/Production/ProductionUiCordonel/FinalTest.xaml rename to Common/Production/ProductionUiCordonel/ProductionWindow.xaml index c0a1e5f3..fe88c0f9 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml +++ b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - x:Class="Xylem.Common.Production.ProductionUiCordonel.FinalTest" + x:Class="Xylem.Common.Production.ProductionUiCordonel.ProductionWindow" mc:Ignorable="d" Title="FinalTest" MinHeight="730" MinWidth="820" Height="730" Width="820" Initialized="Window_Initialized" Closing="Window_Closing" ResizeMode="CanMinimize" HorizontalAlignment="Right" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded"> diff --git a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs similarity index 98% rename from Common/Production/ProductionUiCordonel/FinalTest.xaml.cs rename to Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs index c68dc4fa..81001603 100644 --- a/Common/Production/ProductionUiCordonel/FinalTest.xaml.cs +++ b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs @@ -21,6 +21,7 @@ using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; using Xylem.Common.Utils.Logging; using Xylem.Common.Utils.UserAccessCtrl; @@ -33,7 +34,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel /// /// Interaction logic for FinalTest.xaml /// - public sealed partial class FinalTest + public sealed partial class ProductionWindow { #region variables and properties private readonly ILogger _logger; @@ -59,13 +60,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel private String _lastLoggingTextToAvoidRepetition; private CordonelRequirements _productionRequirements; private UserAccessWindow _userAccessWindow; + private IProcessStateDef _processStateDef; #endregion - public FinalTest() + public ProductionWindow(String softwareName, String projectName, IProcessStateDef processStateDef) { - _logger = NLogHelper.CreateOrGetLogger("ProductionUiCordonel"); - + _logger = NLogHelper.CreateOrGetLogger(projectName); + _processStateDef = processStateDef; String msg; try { @@ -93,7 +95,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel InitializeComponent(); // Log version to files for debug purposes - _strSoftwareNameVersion = $"{Properties.Resources.StrSoftwareNameVersion} {_version}"; + _strSoftwareNameVersion = $"{softwareName} {_version}"; _logger.Info(_strSoftwareNameVersion); Title = _strSoftwareNameVersion; @@ -177,7 +179,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel try { - _processCtrl = new ProcessController(Slot); + _processCtrl = new ProcessController(Slot, _processStateDef); } catch (Exception ex) { diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs index 20a3d181..6c2b205e 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -285,6 +285,15 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { } } + /// + /// Looks up a localized string similar to Cordonel - Shipping Preparation - Version. + /// + internal static string StrEolSoftwareNameVersion { + get { + return ResourceManager.GetString("StrEolSoftwareNameVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to ERROR. /// @@ -1392,6 +1401,15 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { } } + /// + /// Looks up a localized string similar to Cordonel - Picking - Version. + /// + internal static string StrPickingSoftwareNameVersion { + get { + return ResourceManager.GetString("StrPickingSoftwareNameVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to Production Order Number . /// @@ -1518,15 +1536,6 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { } } - /// - /// Looks up a localized string similar to Cordonel - Shipping Preparation - Version. - /// - internal static string StrSoftwareNameVersion { - get { - return ResourceManager.GetString("StrSoftwareNameVersion", resourceCulture); - } - } - /// /// Looks up a localized string similar to New Cordonel EOL Software available! The software is going to exit! This version:. /// diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx index 2920feec..6ed0cd2f 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx @@ -733,9 +733,12 @@ Jahre - + Cordonel - Versandfertigmachen - Version + + Cordonel - Montage - Version + Deckel scannen diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.resx index f0ddd96c..2810476c 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.resx @@ -733,9 +733,12 @@ years - + Cordonel - Shipping Preparation - Version + + Cordonel - Picking - Version + Scan housing From c0cf3e1908301d5b813eec9ffddec5146e415958 Mon Sep 17 00:00:00 2001 From: Stoyan Zlatev Date: Tue, 29 Apr 2025 10:26:11 +0200 Subject: [PATCH 10/16] temporary implemented new battery calculations and requirements checks --- .../CommonCore.Configuration/ServiceUrls.cs | 13 ++ .../CordonelAssemblyLine.csproj | 4 + .../Model/PickingModel.cs | 177 ++++++++++++++---- .../GenesisStatus/GenesisStatusHandler.cs | 38 ++++ ...erRegisters.csproj.CoreCompileInputs.cache | 2 +- Common/LaaPackages | 2 +- Common/Ui/GenesisToolBox/FrmQsTool.cs | 145 +++++++++++--- .../Ui/GenesisToolBox/GenesisToolBox.csproj | 4 + 8 files changed, 322 insertions(+), 63 deletions(-) diff --git a/Common/CommonCore.Configuration/ServiceUrls.cs b/Common/CommonCore.Configuration/ServiceUrls.cs index 1cee286c..3abb956c 100644 --- a/Common/CommonCore.Configuration/ServiceUrls.cs +++ b/Common/CommonCore.Configuration/ServiceUrls.cs @@ -420,6 +420,19 @@ namespace Xylem.Common.CommonCore.Configuration DefaultGetCordonelRequirementInfoUrl; } + /// + /// Get all production infos for skip process checks or limits for production like: + /// - Battery drained percentage upper limit, + /// - Production due date for a special register, + /// - Required life time for assembly line, + /// - Required life time for shipping line. + /// + /// + public static String GetCordonelRequirementByFANrInfoServiceUrl(string ponr) + { + return $"http://sla12iis01.emea.sensus.net/LaaProductionWeb/api/cordonel/requirements/pono/{ponr}"; + } + /// /// Get all applications versions installed to a specific Cordonel. /// diff --git a/Common/CordonelAssemblyLine/CordonelAssemblyLine.csproj b/Common/CordonelAssemblyLine/CordonelAssemblyLine.csproj index 5aa52c31..4fade0db 100644 --- a/Common/CordonelAssemblyLine/CordonelAssemblyLine.csproj +++ b/Common/CordonelAssemblyLine/CordonelAssemblyLine.csproj @@ -230,6 +230,10 @@ {26AFCF1E-1287-48B8-A506-32B1E8A0DD0E} WaterMeterRegisters + + {8BEFD3AC-BB21-4B62-8D9A-CF8CBFD439EB} + LaaPackages.Features.Cordonel + {D0C859D9-D55E-4E85-AC50-0FF31B1CE50D} Logic.ProductionToProductMapper diff --git a/Common/CordonelAssemblyLine/Model/PickingModel.cs b/Common/CordonelAssemblyLine/Model/PickingModel.cs index 731c6b28..d909d012 100644 --- a/Common/CordonelAssemblyLine/Model/PickingModel.cs +++ b/Common/CordonelAssemblyLine/Model/PickingModel.cs @@ -1,4 +1,5 @@ -using Logic.ProductionToProductMapper.Cordonel; +using LaaPackages.Features.Cordonel.Models; +using Logic.ProductionToProductMapper.Cordonel; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -569,10 +570,10 @@ namespace CordonelAssemblyLine.Model meter.SetProcessState(DisplayCodes.PickingStarted); var ret = RunCheck(checkAppsIdVersion, frequency, meterSize, pressurePresent, meter); lblS08Color = _progressWaiting; + var sbMSG = new StringBuilder(); + if (ret) { - - if (!meter.WriteRegister("POWERMON_BatteryQuantity", 2, true, true)) { lblS08Text = "1"; @@ -582,55 +583,157 @@ namespace CordonelAssemblyLine.Model meter.WriteRegister("POWERMON_StoreConfiguration", 1, true); - - - var genesisStatus = new GenesisStatus(); + var requirements = new CordonelRequirements(); - // this calculates all life time values - if (!GenesisStatusHandler.BuildLifeTimeInformation(meter, genesisStatus, out _)) + if (GenesisStatusHandler.GetCordonelRequirementInfoFromDbByFANr(_order, requirements, out var error)) { - meter.WriteLog("Batterie nicht lesbar"); - lblS08Text = "Batterie nicht lesbar"; - lblS08Color = _progressError; - ret = false; - } - else - { - var sbMSG = new StringBuilder(); - sbMSG.AppendLine($"Pcb = {meter.PcbId}"); + genesisStatus.QuiescentCurrent_uA = requirements.QuiescentCurrent_uA; + genesisStatus.EstimatedProductionBatteryLoadPercent = requirements.EstimatedProductionBatteryLoadPercent; - sbMSG.AppendLine($"POWERMON_TotalUsedSeconds = {genesisStatus.ExceededLifeTime_s}"); - sbMSG.AppendLine($"POWERMON_TotalUsedCharge = {genesisStatus.DrainedBatteryLoad_uAs}"); - sbMSG.AppendLine($"POWERMON_BatteryQuantity = {genesisStatus.BatteryQuantity}"); - sbMSG.AppendLine($"POWERMON_BatteryMilliAHrRating = {genesisStatus.InitialBatteryLoad_mAh}"); - sbMSG.AppendLine(""); - sbMSG.AppendLine($"Remaining life time in years {genesisStatus.RemainingLifeTimeYears:F2}"); - sbMSG.AppendLine($"Totally drained battery load in % {genesisStatus.DrainedBatteryLoadPercent:F2}"); - if (genesisStatus.RemainingLifeTimeYears >= 21 || genesisStatus.DrainedBatteryLoadPercent <= 1) + if (GenesisStatusHandler.BuildLifeTimeInformation(meter, genesisStatus, out _)) { - lblS08Text = $"{genesisStatus.RemainingLifeTimeYears} Jahre"; + sbMSG.AppendLine($"Pcb = {meter.PcbId}"); + + sbMSG.AppendLine($"POWERMON_TotalUsedSeconds = {genesisStatus.ExceededLifeTime_s}"); + sbMSG.AppendLine($"POWERMON_TotalUsedCharge = {genesisStatus.DrainedBatteryLoad_uAs}"); + sbMSG.AppendLine($"POWERMON_BatteryQuantity = {genesisStatus.BatteryQuantity}"); + sbMSG.AppendLine($"POWERMON_BatteryMilliAHrRating = {genesisStatus.InitialBatteryLoad_mAh}"); + sbMSG.AppendLine(""); + sbMSG.AppendLine($"Remaining life time in years {genesisStatus.RemainingLifeTimeYears:F2}"); + sbMSG.AppendLine($"Totally drained battery load in % {genesisStatus.DrainedBatteryLoadPercent:F2}"); + + //restlaufzeit monate -RequiredLifeTimeYearsAssembly + + + var hasFehlern = false; + + // restlaufzeit monate - RequiredLifeTimeYearsAssembly + if (genesisStatus.RemainingLifeTimeYears < requirements.RequiredLifeTimeYearsAssembly) + { + lblS08Text = $"{nameof(genesisStatus.RemainingLifeTimeYears)}: " + + $"{genesisStatus.RemainingLifeTimeYears} < " + + $"{requirements.RequiredLifeTimeYearsAssembly}"; + lblS08Color = _progressError; + ret = false; + sbMSG.AppendLine($"{nameof(genesisStatus.RemainingLifeTimeYears)}: " + + $"{genesisStatus.RemainingLifeTimeYears} < " + + $"{requirements.RequiredLifeTimeYearsAssembly}"); + } + else sbMSG.AppendLine($"Restlaufzeit i.O."); + + + + + if (genesisStatus.AlternativeRequiredBatteryLoadPercent != null && + genesisStatus.AlternativeRequiredBatteryLoadPercent > requirements.MaxDrainedBatteryLoadPercentAssembly) + { + sbMSG.AppendLine($"{nameof(genesisStatus.AlternativeRequiredBatteryLoadPercent)}: " + + $"{genesisStatus.AlternativeRequiredBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"); + ret = false; + } + else sbMSG.AppendLine($"Alternative Batterieverbrauch Montage i.O."); + // max bat verbrauch - MaxDrainedBatteryLoadPercentAssembly + + if (genesisStatus.DrainedBatteryLoadPercent > requirements.MaxDrainedBatteryLoadPercentAssembly) + { + lblS08Text = $"{nameof(genesisStatus.DrainedBatteryLoadPercent)}: " + + $"{genesisStatus.DrainedBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"; + lblS08Color = _progressError; + ret = false; + sbMSG.AppendLine($"{nameof(genesisStatus.DrainedBatteryLoadPercent)}: " + + $"{genesisStatus.DrainedBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"); + } + else sbMSG.AppendLine($"Maximaler Batterieverbrauch Montage i.O."); + // lagerzeit - MaxStorageMonths + if (genesisStatus.StorageMonths > requirements.MaxStorageMonths) + { + lblS08Text = $"{nameof(genesisStatus.DrainedBatteryLoadPercent)}: " + + $"{genesisStatus.DrainedBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"; + lblS08Color = _progressError; + ret = false; + sbMSG.AppendLine($"{nameof(genesisStatus.StorageMonths)}: " + + $"{genesisStatus.StorageMonths} > " + + $"{requirements.MaxStorageMonths}"); + } + + else sbMSG.AppendLine($"Maximallagerzeit i.O."); + meter.WriteLog(sbMSG.ToString()); } + + + + + + + + + + + + + + + + + + + + + + + + + else { - lblS08Text = $"{genesisStatus.RemainingLifeTimeYears} Jahre"; + meter.WriteLog("Batterie nicht lesbar"); + lblS08Text = "Batterie nicht lesbar"; lblS08Color = _progressError; ret = false; } - if (_orderDetails.FrequencyIndicator == 0) - { - if ((342000000 + (genesisStatus.ExceededLifeTime_s * 77.7)) <= genesisStatus.DrainedBatteryLoad_uAs) - { - lblS08Text = $"UNT Limit überschritten"; - lblS08Color = _progressError; - ret = false; - } - } - meter.WriteLog(sbMSG.ToString()); + } + else + { + if (string.IsNullOrWhiteSpace(error)) error = "Fehler beim laden von Cordonel Freigaben"; + + + meter.WriteLog(error); + lblS08Text = error; + + + + + + + + + + + + + + + + + lblS08Color = _progressError; + ret = false; + } + + // this calculates all life time values } + if (!ret) + sbMSG.AppendLine($"Battery lifetime limits sind Übreschritten"); + else + sbMSG.AppendLine($"Battery lifetime limits sind OK"); + if (_skipProcessSteps == null || !_skipProcessSteps.SkipLUT) { diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs b/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs index 6490f1f8..59fdd835 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs @@ -101,6 +101,44 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus return false; } + public static Boolean GetCordonelRequirementInfoFromDbByFANr(String fanr, CordonelRequirements prodRequirements, out string errorMsg) + { + errorMsg = ""; + if (null == prodRequirements || string.IsNullOrEmpty(fanr)) + { + return false; + } + + try + { + var url = ServiceUrls.GetCordonelRequirementByFANrInfoServiceUrl(fanr); + var response = LocalWebRequest.GetRequest(url, 30000, out var httpStatus); + if (httpStatus == HttpStatusCode.OK && !string.IsNullOrEmpty(response)) + { + var requirements = JsonConvert.DeserializeObject(response); + var type = typeof(CordonelRequirements); + foreach (var prop in type.GetProperties()) + { + if (prop.CanWrite) + prop.SetValue(prodRequirements, prop.GetValue(requirements, null), null); + } + + // For standard requirements the order number has to be set as those are neither PcbId based + // nor order number based + if (prodRequirements.ProductionOrderNr == null) + prodRequirements.ProductionOrderNr = prodRequirements.OuterProductionOrderNr; + return true; + } + } + catch (Exception e) + { + errorMsg = e.Message; + return false; + } + + return false; + } + /// /// Get the calibration results for a meter from a test-bench calibration. /// diff --git a/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache b/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache index 4163759b..8865d283 100644 --- a/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache +++ b/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -8cb55c312de586b683d72cf58c13e2801d10e14b767777d98338d3e1473a1213 +89102b0142e574de38a9f47d370f3334e5e6c268 diff --git a/Common/LaaPackages b/Common/LaaPackages index e7aa3e27..ce1dc260 160000 --- a/Common/LaaPackages +++ b/Common/LaaPackages @@ -1 +1 @@ -Subproject commit e7aa3e2723a435f0a714df661dc5d86dfd45a71c +Subproject commit ce1dc26047374fd7a9328914a6c2bd7eb591f012 diff --git a/Common/Ui/GenesisToolBox/FrmQsTool.cs b/Common/Ui/GenesisToolBox/FrmQsTool.cs index 60d13991..da16f69c 100644 --- a/Common/Ui/GenesisToolBox/FrmQsTool.cs +++ b/Common/Ui/GenesisToolBox/FrmQsTool.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using LaaPackages.Features.Cordonel.Models; +using Newtonsoft.Json; using NLog; using System; using System.Collections.Generic; @@ -108,8 +109,17 @@ namespace Xylem.Common.Ui.GenesisToolBox _currentGenesis = ((GenesisMeter)me); var genesisStatus = new GenesisStatus(); + var requirements = new CordonelRequirements(); + + + + if (GenesisStatusHandler.GetCordonelRequirementInfoFromDb($"{_currentGenesis.PcbId}", requirements, out var error)) + { + genesisStatus.QuiescentCurrent_uA = requirements.QuiescentCurrent_uA; + genesisStatus.EstimatedProductionBatteryLoadPercent = requirements.EstimatedProductionBatteryLoadPercent; + } //TODO Roland: Check this implementation --- START - if (_currentGenesis.Region.Equals("EMEA")) + else if (_currentGenesis.Region.Equals("EMEA")) { genesisStatus.QuiescentCurrent_uA = 119.8; genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; @@ -119,8 +129,11 @@ namespace Xylem.Common.Ui.GenesisToolBox genesisStatus.QuiescentCurrent_uA = 77.7; genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; } - if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, genesisStatus, out _)) + + if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, genesisStatus, out var message)) { + if (!string.IsNullOrWhiteSpace(message)) + MessageBox.Show(message); var sbMSG = new StringBuilder(); sbMSG.AppendLine($"Pcb = {_currentGenesis.PcbId}"); @@ -135,33 +148,117 @@ namespace Xylem.Common.Ui.GenesisToolBox sbMSG.AppendLine($"Totally drained battery load in %: {genesisStatus.DrainedBatteryLoadPercent:F2}"); sbMSG.AppendLine(""); + var hasFehlern = false; + + + // + RemainingLifeTimeYears + + // restlaufzeit monate - RequiredLifeTimeYearsAssembly + if (genesisStatus.RemainingLifeTimeYears < requirements.RequiredLifeTimeYearsAssembly) + { + hasFehlern = true; + sbMSG.AppendLine($"{nameof(genesisStatus.RemainingLifeTimeYears)}: " + + $"{genesisStatus.RemainingLifeTimeYears} < " + + $"{requirements.RequiredLifeTimeYearsAssembly}"); + } + else sbMSG.AppendLine($"Restlaufzeit i.O."); + + + + + // + AlternativeRequiredBatteryLoadPercent + if (genesisStatus.AlternativeRequiredBatteryLoadPercent != null && + genesisStatus.AlternativeRequiredBatteryLoadPercent > requirements.MaxDrainedBatteryLoadPercentAssembly) + { + sbMSG.AppendLine($"{nameof(genesisStatus.AlternativeRequiredBatteryLoadPercent)}: " + + $"{genesisStatus.AlternativeRequiredBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"); + hasFehlern = true; + } + else sbMSG.AppendLine($"Alternative Batterieverbrauch Montage i.O."); + // max bat verbrauch - MaxDrainedBatteryLoadPercentAssembly + + // + DrainedBatteryLoadPercent + if (genesisStatus.DrainedBatteryLoadPercent > requirements.MaxDrainedBatteryLoadPercentAssembly) + { + sbMSG.AppendLine($"{nameof(genesisStatus.DrainedBatteryLoadPercent)}: " + + $"{genesisStatus.DrainedBatteryLoadPercent} > " + + $"{requirements.MaxDrainedBatteryLoadPercentAssembly}"); + hasFehlern = true; + } + else sbMSG.AppendLine($"Maximaler Batterieverbrauch Montage i.O."); + + + // + StorageMonths + // lagerzeit - MaxStorageMonths + if (genesisStatus.StorageMonths > requirements.MaxStorageMonths) + { + hasFehlern = true; + sbMSG.AppendLine($"{nameof(genesisStatus.StorageMonths)}: " + + $"{genesisStatus.StorageMonths} > " + + $"{requirements.MaxStorageMonths}"); + } + + else sbMSG.AppendLine($"Maximallagerzeit i.O."); + + //// strom verbrauch - QuiescentCurrent_uA + //if (genesisStatus.QuiescentCurrent_uA > requirements.QuiescentCurrent_uA) + //{ + // hasFehlern = true; + // sbMSG.AppendLine($"{nameof(genesisStatus.QuiescentCurrent_uA)}: " + + // $"{genesisStatus.QuiescentCurrent_uA} > " + + // $"{requirements.QuiescentCurrent_uA}"); + //} // max bat verbrauch prod - EstimatedProductionBatteryLoadPercent + //else sbMSG.AppendLine($"Stromverbrauch i.O."); + //if (genesisStatus.EstimatedProductionBatteryLoadPercent > requirements.EstimatedProductionBatteryLoadPercent) + //{ + // sbMSG.AppendLine($"{nameof(genesisStatus.EstimatedProductionBatteryLoadPercent)}: " + + // $"{genesisStatus.EstimatedProductionBatteryLoadPercent} > " + + // $"{requirements.EstimatedProductionBatteryLoadPercent}"); + // // TODO error + // hasFehlern = true; + //} + + + + + + //else sbMSG.AppendLine($"Maximaler Batterieverbrauch Produktion i.O."); + //if (_currentGenesis.Region == "NA") //{ //if (genesisStatus.UNTStatus) // This is the limit given by the "AlternativeRequiredBatteryLoadPercent" - if (genesisStatus.AlternativeRequiredBatteryLoadPercent != null && - genesisStatus.DrainedBatteryLoadPercent < genesisStatus.AlternativeRequiredBatteryLoadPercent) - { - sbMSG.AppendLine($"{_currentGenesis.Region} limit OK "); - } - else - { - sbMSG.AppendLine($"{_currentGenesis.Region} limit Failed "); - } - //sbMSG.AppendLine($"UNT Limit data Limit {genesisStatus.UNTLimit} Meter has {genesisStatus.DrainedBatteryLoad_uAs}"); - sbMSG.AppendLine($"Maximum drained battery load: {genesisStatus.AlternativeRequiredBatteryLoadPercent} %"); - sbMSG.AppendLine($"Meter reports drained battery load: {genesisStatus.DrainedBatteryLoadPercent} %"); + //if (genesisStatus.AlternativeRequiredBatteryLoadPercent != null && + // genesisStatus.DrainedBatteryLoadPercent < requirements.MaxDrainedBatteryLoadPercentAssembly) + //{ + // sbMSG.AppendLine($"{_currentGenesis.Region} limit OK "); + //} + //else + //{ + // sbMSG.AppendLine($"{_currentGenesis.Region} limit Failed "); + //} + ////sbMSG.AppendLine($"UNT Limit data Limit {genesisStatus.UNTLimit} Meter has {genesisStatus.DrainedBatteryLoad_uAs}"); + //sbMSG.AppendLine($"Maximum drained battery load: {genesisStatus.AlternativeRequiredBatteryLoadPercent} %"); + //sbMSG.AppendLine($"Meter reports drained battery load: {genesisStatus.DrainedBatteryLoadPercent} %"); - var preproductionLimit = genesisStatus.AlternativeRequiredBatteryLoadPercent - - genesisStatus.EstimatedProductionBatteryLoadPercent; - if (genesisStatus.DrainedBatteryLoadPercent < preproductionLimit) - { - sbMSG.AppendLine($"{_currentGenesis.Region} preproduction limit OK "); - } + //var preproductionLimit = genesisStatus.AlternativeRequiredBatteryLoadPercent - + // genesisStatus.EstimatedProductionBatteryLoadPercent; + //if (genesisStatus.DrainedBatteryLoadPercent < preproductionLimit) + //{ + // sbMSG.AppendLine($"{_currentGenesis.Region} preproduction limit OK "); + //} + //else + //{ + // sbMSG.AppendLine($"{_currentGenesis.Region} preproduction limit Failed "); + //} + if (hasFehlern) + sbMSG.AppendLine($"Battery lifetime limits sind Übreschritten"); else - { - sbMSG.AppendLine($"{_currentGenesis.Region} preproduction limit Failed "); - } + sbMSG.AppendLine($"Battery lifetime limits sind OK"); + + sbMSG.AppendLine( + ); sbMSG.AppendLine($"Preproduction max. drained battery load: {genesisStatus.AlternativeRequiredBatteryLoadPercent} %"); sbMSG.AppendLine($"Meter reports drained battery load: {genesisStatus.DrainedBatteryLoadPercent} %"); diff --git a/Common/Ui/GenesisToolBox/GenesisToolBox.csproj b/Common/Ui/GenesisToolBox/GenesisToolBox.csproj index 16faa439..78ef1599 100644 --- a/Common/Ui/GenesisToolBox/GenesisToolBox.csproj +++ b/Common/Ui/GenesisToolBox/GenesisToolBox.csproj @@ -728,6 +728,10 @@ {26afcf1e-1287-48b8-a506-32b1e8a0dd0e} WaterMeterRegisters + + {8BEFD3AC-BB21-4B62-8D9A-CF8CBFD439EB} + LaaPackages.Features.Cordonel + {D0C859D9-D55E-4E85-AC50-0FF31B1CE50D} Logic.ProductionToProductMapper From 4aea8ac872408ff72410509de2356fb55a60d303 Mon Sep 17 00:00:00 2001 From: Stoyan Zlatev Date: Tue, 29 Apr 2025 12:37:24 +0200 Subject: [PATCH 11/16] Requirements by internal meter size --- .../CommonCore.Configuration/ServiceUrls.cs | 13 ++++ .../GenesisStatus/GenesisStatusHandler.cs | 34 ++++++++++ Common/Ui/GenesisToolBox/FrmQsTool.cs | 66 ++++++++++++++----- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/Common/CommonCore.Configuration/ServiceUrls.cs b/Common/CommonCore.Configuration/ServiceUrls.cs index 3abb956c..952a32e7 100644 --- a/Common/CommonCore.Configuration/ServiceUrls.cs +++ b/Common/CommonCore.Configuration/ServiceUrls.cs @@ -433,6 +433,19 @@ namespace Xylem.Common.CommonCore.Configuration return $"http://sla12iis01.emea.sensus.net/LaaProductionWeb/api/cordonel/requirements/pono/{ponr}"; } + /// + /// Get all production infos for skip process checks or limits for production like: + /// - Battery drained percentage upper limit, + /// - Production due date for a special register, + /// - Required life time for assembly line, + /// - Required life time for shipping line. + /// + /// + public static string GetCordonelRequirementByMeterSizeInfoServiceUrl(int size) + { + return $"http://sla12iis01.emea.sensus.net/LaaProductionWeb/api/cordonel/requirements/isize/{size}"; + } + /// /// Get all applications versions installed to a specific Cordonel. /// diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs b/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs index 59fdd835..c56ba52b 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisStatus/GenesisStatusHandler.cs @@ -139,6 +139,40 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus return false; } + public static Boolean GetCordonelRequirementInfoFromDbByMeterSize(int size, CordonelRequirements prodRequirements, out string errorMsg) + { + errorMsg = ""; + + try + { + var url = ServiceUrls.GetCordonelRequirementByMeterSizeInfoServiceUrl(size); + var response = LocalWebRequest.GetRequest(url, 30000, out var httpStatus); + if (httpStatus == HttpStatusCode.OK && !string.IsNullOrEmpty(response)) + { + var requirements = JsonConvert.DeserializeObject(response); + var type = typeof(CordonelRequirements); + foreach (var prop in type.GetProperties()) + { + if (prop.CanWrite) + prop.SetValue(prodRequirements, prop.GetValue(requirements, null), null); + } + + // For standard requirements the order number has to be set as those are neither PcbId based + // nor order number based + if (prodRequirements.ProductionOrderNr == null) + prodRequirements.ProductionOrderNr = prodRequirements.OuterProductionOrderNr; + return true; + } + } + catch (Exception e) + { + errorMsg = e.Message; + return false; + } + + return false; + } + /// /// Get the calibration results for a meter from a test-bench calibration. /// diff --git a/Common/Ui/GenesisToolBox/FrmQsTool.cs b/Common/Ui/GenesisToolBox/FrmQsTool.cs index da16f69c..46a6a9a5 100644 --- a/Common/Ui/GenesisToolBox/FrmQsTool.cs +++ b/Common/Ui/GenesisToolBox/FrmQsTool.cs @@ -110,32 +110,62 @@ namespace Xylem.Common.Ui.GenesisToolBox var genesisStatus = new GenesisStatus(); var requirements = new CordonelRequirements(); + var sbMSG = new StringBuilder(); - - - if (GenesisStatusHandler.GetCordonelRequirementInfoFromDb($"{_currentGenesis.PcbId}", requirements, out var error)) + if (!GenesisStatusHandler.GetCordonelRequirementInfoFromDb($"{_currentGenesis.PcbId}", requirements, out var error)) { - genesisStatus.QuiescentCurrent_uA = requirements.QuiescentCurrent_uA; + if (!string.IsNullOrWhiteSpace(error)) + { + sbMSG.AppendLine(error); + } + + var size = RegisterConverter.ByteArrayToValue( + _currentGenesis.ReadRegister(Register.Genesisflow.MeterSize)); + + if (!GenesisStatusHandler.GetCordonelRequirementInfoFromDbByMeterSize(size, requirements, out error)) + { + if (!string.IsNullOrWhiteSpace(error)) + { + sbMSG.AppendLine(error); + } + + requirements = new CordonelRequirements + { + QuiescentCurrent_uA = 119.8, + EstimatedProductionBatteryLoadPercent = 1, + RequiredLifeTimeYearsAssembly = 22, + MaxDrainedBatteryLoadPercentAssembly = 4, + MaxStorageMonths = 5 + }; + } + } + + sbMSG.AppendLine($"Standard requirement: {requirements.StandardId}.{requirements.StandardVersion}"); + sbMSG.AppendLine($"Special requirement: {requirements.SpecialId}.{requirements.SpecialVersion}"); + sbMSG.AppendLine(); + + //if (GenesisStatusHandler.GetCordonelRequirementInfoFromDb($"{_currentGenesis.PcbId}", requirements, out var error)) + //{ + genesisStatus.QuiescentCurrent_uA = requirements.QuiescentCurrent_uA; genesisStatus.EstimatedProductionBatteryLoadPercent = requirements.EstimatedProductionBatteryLoadPercent; - } - //TODO Roland: Check this implementation --- START - else if (_currentGenesis.Region.Equals("EMEA")) - { - genesisStatus.QuiescentCurrent_uA = 119.8; - genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; - } - else - { - genesisStatus.QuiescentCurrent_uA = 77.7; - genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; - } + //} + ////TODO Roland: Check this implementation --- START + //else if (_currentGenesis.Region.Equals("EMEA")) + //{ + // genesisStatus.QuiescentCurrent_uA = 119.8; + // genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; + //} + //else + //{ + // genesisStatus.QuiescentCurrent_uA = 77.7; + // genesisStatus.EstimatedProductionBatteryLoadPercent = 1.0; + //} if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, genesisStatus, out var message)) { if (!string.IsNullOrWhiteSpace(message)) MessageBox.Show(message); - - var sbMSG = new StringBuilder(); + sbMSG.AppendLine($"Pcb = {_currentGenesis.PcbId}"); sbMSG.AppendLine($"POWERMON_TotalUsedSeconds = {genesisStatus.ExceededLifeTime_s}"); From 861f29f632e898e4e2b23d49f338c6e15361438c Mon Sep 17 00:00:00 2001 From: Stoyan Zlatev Date: Tue, 29 Apr 2025 13:31:25 +0200 Subject: [PATCH 12/16] passende icons to the QS tools read lifetime MessageBox --- Common/Ui/GenesisToolBox/FrmQsTool.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Common/Ui/GenesisToolBox/FrmQsTool.cs b/Common/Ui/GenesisToolBox/FrmQsTool.cs index 46a6a9a5..ed0c2792 100644 --- a/Common/Ui/GenesisToolBox/FrmQsTool.cs +++ b/Common/Ui/GenesisToolBox/FrmQsTool.cs @@ -333,12 +333,22 @@ namespace Xylem.Common.Ui.GenesisToolBox //ExceededLifeTime_s * 77.7) + if (hasFehlern) + { + + MessageBox.Show(sbMSG.ToString(), $"Battery for {_currentGenesis.PcbId} has remaining life " + - $"time of {genesisStatus.RemainingLifeTimeYears:F2} years"); + $"time of {genesisStatus.RemainingLifeTimeYears:F2} years", buttons:MessageBoxButtons.OK,icon:MessageBoxIcon.Error); + } else + { + MessageBox.Show(sbMSG.ToString(), $"Battery for {_currentGenesis.PcbId} has remaining life " + + $"time of {genesisStatus.RemainingLifeTimeYears:F2} years", buttons:MessageBoxButtons.OK,icon:MessageBoxIcon.Information); + } + } else { - MessageBox.Show("Failed to get life time information"); + MessageBox.Show("Failed to get life time information",caption:null,buttons:MessageBoxButtons.OK,icon:MessageBoxIcon.Error); } } } @@ -346,10 +356,8 @@ namespace Xylem.Common.Ui.GenesisToolBox } catch (Exception ex) { - MessageBox.Show(ex.ToString()); + MessageBox.Show(text:ex.ToString(),caption:null,buttons:MessageBoxButtons.OK,icon:MessageBoxIcon.Error); } - - } From a5eabc2322cf9493add55ddad571f18cb4bc8e08 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Thu, 15 May 2025 15:43:07 +0200 Subject: [PATCH 13/16] ProductionUiCordonel: - manually merged some modules from main EOL_2.6.20 --- Common/.vs/config/applicationhost.config | 2 +- .../Ports/SerialPorts/BaseSerialPort.cs | 70 +- .../Ports/SerialPorts/BaseSerialPort.cs.bak | 693 +++++++++++++ .../Genesis/GenesisCore/RegisterRestorer.cs | 9 + .../GenesisCore/RegisterRestorer.cs.bak | 104 +- ...erRegisters.csproj.CoreCompileInputs.cache | 2 +- .../ProductionUiCordonel/App.config | 10 +- .../EolProcesses/CheckRadio.cs | 18 +- .../EolProcesses/CheckRadio.cs.bak | 829 +++++++++++++++ .../ProductionProcesses/ProcessController.cs | 5 +- .../ProcessController.cs.bak | 857 ++++++++++++++++ .../ProductionWindow.xaml.cs | 14 + .../ProductionWindow.xaml.cs.bak | 963 ++++++++++++++++++ 13 files changed, 3496 insertions(+), 80 deletions(-) create mode 100644 Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs.bak create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs.bak create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs.bak create mode 100644 Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs.bak diff --git a/Common/.vs/config/applicationhost.config b/Common/.vs/config/applicationhost.config index a894c3cf..883679a3 100644 --- a/Common/.vs/config/applicationhost.config +++ b/Common/.vs/config/applicationhost.config @@ -155,7 +155,7 @@ - + diff --git a/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs index 13ae7987..6185afae 100644 --- a/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs +++ b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs @@ -27,7 +27,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts /// /// The port is not assigned. /// - public const String PortNotAssigned = "NA"; + public const String PortNotAssigned = "NA"; /// public virtual event EventHandler OnRawRecordSendOut; @@ -193,6 +193,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts /// /// Dispose serial port /// + /// + /// - Dispose procedure changed. + /// public void Dispose() { try @@ -206,6 +209,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts //Run thread again to notice CancellationToken has changed _onSyncReceiveThread.Set(); + + // dispose directly called from here to overcome glitches caused by USB to serial interface + _serialPort.Dispose(); } catch (Exception ex) @@ -419,6 +425,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts /// The IrDA sniffer showed these on the communication line, but they are incompletely received. /// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped. /// + /// + /// - Dispose procedure changed. + /// private void ReadingThreadLoop() { //put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on @@ -448,8 +457,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun) { OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord, - _receiveTimeStampPc, dataSyncMarkThisRun)); + _receiveTimeStampPc, dataSyncMarkThisRun)); } + if (RecordStreamingRawData) { AsciiDataLogger.Trace($"{rxStringRecord}"); @@ -503,12 +513,14 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts if (PortSettingsForTransmitProtocol.DoubleSyncByte) { if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) && - rxByteRecord[rxByteRecord.Count - 1].Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) + rxByteRecord[rxByteRecord.Count - 1] + .Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) { //skips this byte and read the next one rxByte = (Byte)_serialPort.ReadByte(); } } + //add byte to receive result buffer rxByteRecord.Add(rxByte); @@ -552,7 +564,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts } } catch (ThreadAbortException) - { throw; } + { + _byteDataLogger.Info($"{Ident} Thread abort exception fired!"); + } catch (TimeoutException) { //ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming @@ -575,12 +589,12 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts if (_receiveThread.ThreadState == ThreadState.Aborted || _receiveThread.ThreadState == ThreadState.AbortRequested) { - _byteDataLogger.Warn(ex, $"{ Ident} ThreadState is Aborted or AbortRequested but an " + - "error occurred while reading records from serial port"); + _byteDataLogger.Warn(ex, $"{Ident} ThreadState is Aborted or AbortRequested but an " + + "error occurred while reading records from serial port"); } else { - _byteDataLogger.Error(ex, $"{ Ident} Error while reading records from serial port"); + _byteDataLogger.Error(ex, $"{Ident} Error while reading records from serial port"); } } finally @@ -598,31 +612,33 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts } } } - }//while (!_tokenReadData.IsCancellationRequested) - }// over hole reading thread loop + } //while (!_tokenReadData.IsCancellationRequested) + } // over hole reading thread loop catch (ThreadAbortException) - { } + { + _byteDataLogger.Info($"{Ident} Thread abort exception fired!"); + } catch (Exception ex) { _byteDataLogger.Error($"{Ident} {ex.Message}"); } - finally - { - if (_serialPort.IsOpen) - { - _serialPort.Close(); - if (_serialPort.IsOpen) - { - _byteDataLogger.Info($"{Ident} Port close delay!"); - Thread.Sleep(50); - _serialPort.Close(); - } - _serialPort.Dispose(); - _byteDataLogger.Info($"{Ident} Port is closed"); - - - } - } + //finally + //{ + // //var retries = 5; + // //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0) + // //{ + // // _serialPort.Close(); + // // if (_serialPort.IsOpen) + // // { + // // _byteDataLogger.Info($"{Ident} Port closing delay!"); + // // Thread.Sleep(500); + // // } + // //} + // //if (_serialPort != null && _serialPort.IsOpen) + // // _byteDataLogger.Info($"{Ident} Port unable to close"); + // //else + // // _byteDataLogger.Info($"{Ident} Port is closed"); + //} } /// diff --git a/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs.bak b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs.bak new file mode 100644 index 00000000..13ae7987 --- /dev/null +++ b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs.bak @@ -0,0 +1,693 @@ +using NLog; +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Threading; +using Xylem.Common.CommonCore.Consts; +using Xylem.Common.CommonCore.ThreadWatcher; +using Xylem.Common.Hardware.Interfaces.Ports.PortCore; +using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments; +using Xylem.Common.Utils.Logging; + +namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts +{ + /// + /// All Ports must use BasePort as an base class + /// its support some wrapping event/function handling + /// + public abstract class BaseSerialPort : IPort, IDisposable + { + + private ILogger _byteDataLogger; + internal ILogger AsciiDataLogger; + + /// + public virtual event EventHandler OnRawRecordReceived; + + /// + /// The port is not assigned. + /// + public const String PortNotAssigned = "NA"; + + /// + public virtual event EventHandler OnRawRecordSendOut; + + //initially do not signal event + private readonly AutoResetEvent _onSyncReceiveThread = new AutoResetEvent(false); + private readonly Thread _receiveThread; + private readonly CancellationTokenSource _receiveToken = new CancellationTokenSource(); + + /// + /// Delay between bytes if receiving has started in milliseconds + /// + private const Int32 InterByteReadDelayMs = 50; + + /// + /// internal SerialPort class + /// + private readonly SerialPort _serialPort; + + + /// + /// Activate raw record recording + /// + public Boolean RecordStreamingRawData = false; + //public Boolean RecordStreamingRawData + //{ + // get => _recordStreamingRawData; + // set => _recordStreamingRawData = value; + //} + + /// + /// date and time of incoming first date interrupt on serial IO to set the + /// PC time-stamp to the record + /// + private DateTimeOffset _receiveTimeStampPc; + + /// + /// Individual port settings depending on transmit protocol + /// + protected TransmitPortSettings PortSettingsForTransmitProtocol; + + /// + public String GetPortName() + { + return _serialPort.PortName; + } + + /// + /// store a identification of a port, this contains the Slot, Port, Protocol and Type + /// e.g. "Slot:1, Port:COM4, Protocol:Request, Type:IrDA -" + /// + public String Ident; + + /// + /// ctor for SerialComPort with and full constructed properties + /// + /// set port as unique + /// Class for communication, all parameters for serial communication needs to be set + /// Class for store all parameters for serial communication needs to be set came from transmit protocol + protected BaseSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) + { + Ident = ident; + + //communication port settings + _serialPort = serialPort; + _serialPort.BaudRate = (Int32)setting.BaudRate; + + PortSettingsForTransmitProtocol = setting; + + //assign thread to loop + _receiveThread = new Thread(ReadingThreadLoop) { Name = $"{Ident} Reading thread" }; + + AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase"); + _byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort"); + } + + + public void RefreshLogger() + { + + AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase"); + _byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort"); + + + } + + + + /// + /// received bytes in buffer of SerialComPort, calls base function + /// + /// number of bytes in Rx buffer + protected Int32 BytesToRead() + { + try + { + return _serialPort.BytesToRead; + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + return 0; + + } + + } + + /// + /// read single byte from Rx buffer of SerialComPort, calls base function + /// + /// + protected Int32 ReadByte() + { + + try + { + return _serialPort.ReadByte(); + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + return 0; + + } + } + + /// + public void PortWrite(Byte[] record) + { + try + { + SpecificPortWrite(record); + } + catch (Exception ex) + { + throw new SystemException($"{Ident} Communication error while sending data to port", ex); + } + } + + /// + /// + /// flush Rx and Tx buffer of SerialComPort, calls base functions + /// + public void Clear() + { + try + { + if (!_serialPort.IsOpen) + { + return; + } + + _serialPort.DiscardInBuffer(); + _serialPort.DiscardOutBuffer(); + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + + } + } + + /// + /// Dispose serial port + /// + public void Dispose() + { + try + { + + //remove registration for receive delegate + _serialPort.DataReceived -= PhysicalDataReceived; + + //Cancel send and receive tokens + _receiveToken.Cancel(); + + //Run thread again to notice CancellationToken has changed + _onSyncReceiveThread.Set(); + + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + + } + + } + + + /// + public void Open() + { + try + { + + //set receive interrupt threshold for immediate execution + _serialPort.ReceivedBytesThreshold = 1; + _serialPort.DataReceived += PhysicalDataReceived; + + //start reading thread, will be immediately put to waitSleepJoin in ReadingThreadLoop + //to avoid side effects with RFID communication + + ThreadWatcher.Instance.Start(_receiveThread); + + if (!_serialPort.IsOpen) + { + _serialPort.Open(); + if (!_serialPort.IsOpen) + { + _byteDataLogger.Error($"{Ident} Port cannot be opened"); + throw new ApplicationException($"{Ident} Port cannot be opened"); + } + _byteDataLogger.Info($"{Ident} Port is opened"); + } + else + { + _byteDataLogger.Warn($"{Ident} Port was already opened"); + } + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + throw new ApplicationException($"{Ident} {ex.Message}"); + } + } + /// + /// Getting the base port + /// + protected SerialPort GetBaseComport() + { + return _serialPort; + } + + /// + /// Fill FIFO with received data + /// + /// + /// - Initial + /// + /// + /// - Time stamp added + /// + /// + /// - Timeout moved from to PhysicalDataReceived to avoid + /// of serial port while waiting on first incoming + /// data. + /// + /// + /// - Inter byte read delay used instead of response delay!!! + /// + + /// + /// + public virtual void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e) + { + if (!_receiveToken.IsCancellationRequested) + { + //remove event delegate to avoid repeated execution during one record + _serialPort.DataReceived -= PhysicalDataReceived; + + //put the actual time stamp to this record being able to assign it correctly + //even if the decoding is delayed. This time stamp will be used to do the first + //synchronization at start and stop of the measurement. Therefore the serial buffer + //has to be flushed in advance to avoid wrong time stamp to "old" records + _receiveTimeStampPc = DateTimeOffset.UtcNow; + + //start timeout for hanging read communication called inter byte delay + _serialPort.ReadTimeout = InterByteReadDelayMs; + // _serialPort.ReadTimeout = PortSettingsForTransmitProtocol.ResponseTimeoutMs; + + //wake up the reading threat from JoinWaitSleep + _onSyncReceiveThread.Set(); + } + } + + /// + /// + /// + private SyncMarkRecord _syncMarkRecord; + + /// + public void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord) + { + try + { + + //not in test bench situation only for temp logging + if (syncMarkRecord == SyncMarkRecord.FlushBuffer) + { + FlushBuffer(); + return; + } + //DecodeEveryPackage is ongoing and SkipDecoding is requested = do nothing + if (_syncMarkRecord == SyncMarkRecord.DecodeEveryPackage && + syncMarkRecord == SyncMarkRecord.SkipDecoding) + { + return; + } + //deny intermediate record when a start or end record is requested + if (syncMarkRecord == SyncMarkRecord.DecodeIntermediate && + (_syncMarkRecord == SyncMarkRecord.SyncEnd || + _syncMarkRecord == SyncMarkRecord.SyncStart)) + { + return; + } + //being able to detect the first incoming record after a sync is requested, the buffer is going to be flushed + //if the measurement will be started or stopped + if (syncMarkRecord == SyncMarkRecord.SyncEnd || + syncMarkRecord == SyncMarkRecord.SyncStart || + syncMarkRecord == SyncMarkRecord.DecodeIntermediate) + { + FlushBuffer(); + } + + _byteDataLogger.Info($"{Ident} Mark next incoming record as {syncMarkRecord} (was {_syncMarkRecord})"); + _syncMarkRecord = syncMarkRecord; + + + + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + } + + private void FlushBuffer() + { + //check if flushing is allowed + if (null != PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold) + { + //flush buffer above threshold to get an accurate actual record with next record coming in + if (_serialPort.IsOpen && _serialPort.BytesToRead > PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold) + { + _byteDataLogger.Warn($"{Ident} Flushed receive buffer({_serialPort.BytesToRead}Byte)"); + _serialPort.DiscardInBuffer(); + } + } + } + + /// + /// Reading thread loop getting data from serial port + /// + /// + /// - Initial + /// + /// + /// - Reading will be executed and repeated until buffer is empty or timeout + /// + /// + /// - Receive byte protocol more dynamically on position of length information in record + /// and additional length, + /// - decoding of data controlled by dataSyncMarker!= SyncMarkRecord.SkipDecoding to speed up recording + /// + /// + /// - Directly invoked onRawRecord Received event, + /// - Doubling of sync byte implemented + /// + /// + /// - Timeout moved from ReadingThreadLoop to to avoid + /// of serial port while waiting on first incoming + /// data and flush receive buffer at timeout to force task to enter JoinWaitSleep state. + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Raw record recording for missing sync-byte issues. + /// + /// + /// - Try to recover raw record on missing SYNC byte by adding the SYNC upfront and sending + /// the record to the decoding thread which will detect if just the SYNC byte had been + /// missed, then the CRC will match and the record can be decoded. + /// + /// + /// - Improved recovering of data sets at byte records with preceding SYNC byte: + /// - used payload length == 0 to skip decoding, it makes no sense to decode something where + /// the payload is empty, + /// - wait a certain time to complete incoming data. + /// + /// + /// - Inter byte read delay used instead of response delay. + /// + /// + /// - Flush buffer if received length is 0. + /// + /// + /// - Flush buffer (DiscardInBuffer) removed as some bytes are missing from time to time. + /// The IrDA sniffer showed these on the communication line, but they are incompletely received. + /// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped. + /// + private void ReadingThreadLoop() + { + //put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on + //RFID communication, because RFID will handle the physical receive by itself + //here the assignment of the _synReadingThreadEvent to the reading thread is being done + _onSyncReceiveThread.WaitOne(); + + try + { + while (!_receiveToken.IsCancellationRequested) + { + try + { + //remind data marker for this Thread execution time slice + var dataSyncMarkThisRun = _syncMarkRecord; + + //read line of ASCII indicated by syncByte == null + if (null == PortSettingsForTransmitProtocol.ProtSyncByte) + { + //exit is timeout from serialPort or received line + var rxStringRecord = _serialPort.ReadLine(); + + if (!string.IsNullOrEmpty(rxStringRecord)) + { + //decode optional at synchronized data SyncStart, SyncEnd or DecodeIntermediate + //todo remove after air problem + if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun) + { + OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord, + _receiveTimeStampPc, dataSyncMarkThisRun)); + } + if (RecordStreamingRawData) + { + AsciiDataLogger.Trace($"{rxStringRecord}"); + } + } + } + //byte record with preceding SYNC byte + else + { + //normally decoding is required + dataSyncMarkThisRun = SyncMarkRecord.DecodeEveryPackage; + + //this is the record for decoding + var rxByteRecord = new List(); + + //this is the informational record for logging on undetected SYNC byte + var rxRawRecord = new List(); + + //each byte protocol has to start with a syncByte, this has to be detected first + Byte rxByte; + do + { + rxByte = (Byte)_serialPort.ReadByte(); + //record raw data stream for output on missing sync-byte to investigate this issue + rxRawRecord.Add(rxByte); + } while (_serialPort.BytesToRead > 0 && PortSettingsForTransmitProtocol.ProtSyncByte != rxByte); + + // if syncByte has not been detected skip read loop and wait for next incoming record + if (PortSettingsForTransmitProtocol.ProtSyncByte == rxByte) + { + //save received SYNC byte + rxByteRecord.Add(rxByte); + + //to assemble the length it has to be extracted first from the record at given index, + //the payload length index cannot be 0 because at this position is always the SYNC byte, + //the overall length includes the SYNC byte! + var length = PortSettingsForTransmitProtocol.ProtLengthIndex + 1; + + //if the protLengthIndex is null the protAddLength equals the entire record length + if (null == PortSettingsForTransmitProtocol.ProtLengthIndex) + { + length = PortSettingsForTransmitProtocol.ProtAddLength; + } + + //read until length index to extract the length from the record + //the length index cannot be at position 0, because this is always the syncByte + do + { + rxByte = (Byte)_serialPort.ReadByte(); + //read the next byte if doubled sync byte detected and required by transmit protocol settings + if (PortSettingsForTransmitProtocol.DoubleSyncByte) + { + if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) && + rxByteRecord[rxByteRecord.Count - 1].Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) + { + //skips this byte and read the next one + rxByte = (Byte)_serialPort.ReadByte(); + } + } + //add byte to receive result buffer + rxByteRecord.Add(rxByte); + + //capture the length at given index and readjust the record length + if (null != PortSettingsForTransmitProtocol.ProtLengthIndex + && rxByteRecord.Count - 1 == PortSettingsForTransmitProtocol.ProtLengthIndex) + { + //if the received length indicates empty payload, then nothing is to decode + if (rxByte == 0) + { + //exit this loop + dataSyncMarkThisRun = SyncMarkRecord.SkipDecoding; + FlushBuffer(); + } + else + { + //build the new length to continue this receive loop + length = (UInt16)(PortSettingsForTransmitProtocol.ProtAddLength + rxByte); + } + } + + //wait a certain time to let the data stream coming in + if (length - rxByteRecord.Count - 1 > _serialPort.BytesToRead) + { + Thread.Sleep(2); + } + + } while (rxByteRecord.Count < length && dataSyncMarkThisRun != SyncMarkRecord.SkipDecoding); + + //all data received or skip decoding marked, skip decoding will clear the response timeout + //these protocols have always to be decoded because the base is a request protocol + OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(rxByteRecord, + _receiveTimeStampPc, dataSyncMarkThisRun)); + } + else + { + //output actual byte and recorded raw data stream to investigate missed sync-byte + _byteDataLogger.Warn($"{Ident} Response SYNC Byte missing. Raw Byte Received: " + + $"{BitConverter.ToString(rxRawRecord.ToArray())}"); + } + } + } + catch (ThreadAbortException) + { throw; } + catch (TimeoutException) + { + //ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming + //bytes and therefore destroys the already started data stream. Here it had been left in to indicate + //this critical issue! + /*-------------------------------DO NOT ACTIVATE-----------------------------------------------------*/ + //flush receive buffer at timeout to force task to enter JoinWaitSleep state in finally + //if (_serialPort.IsOpen) + //{ + // _serialPort.DiscardInBuffer(); + //} + /*---------------------------------------------------------------------------------------------------*/ + + _byteDataLogger.Trace($"{Ident} Read timeout({InterByteReadDelayMs}ms)"); + //_byteDataLogger.Trace($"{Ident} Read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)"); + + } + catch (Exception ex) + { + if (_receiveThread.ThreadState == ThreadState.Aborted + || _receiveThread.ThreadState == ThreadState.AbortRequested) + { + _byteDataLogger.Warn(ex, $"{ Ident} ThreadState is Aborted or AbortRequested but an " + + "error occurred while reading records from serial port"); + } + else + { + _byteDataLogger.Error(ex, $"{ Ident} Error while reading records from serial port"); + } + } + finally + { + if (_serialPort.IsOpen && !_receiveToken.IsCancellationRequested) + { + //it makes no sense to put the thread to sleep if there is something to read + if (0 == _serialPort.BytesToRead) + { + //restore event delegate to activate handle for incoming records + _serialPort.DataReceived += PhysicalDataReceived; + + //put this thread (receive thread) to JainWaitSleep + _onSyncReceiveThread.WaitOne(); + } + } + } + }//while (!_tokenReadData.IsCancellationRequested) + }// over hole reading thread loop + catch (ThreadAbortException) + { } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + finally + { + if (_serialPort.IsOpen) + { + _serialPort.Close(); + if (_serialPort.IsOpen) + { + _byteDataLogger.Info($"{Ident} Port close delay!"); + Thread.Sleep(50); + _serialPort.Close(); + } + _serialPort.Dispose(); + _byteDataLogger.Info($"{Ident} Port is closed"); + + + } + } + } + + /// + public Boolean IsOpen() + { + return _serialPort != null && _serialPort.IsOpen; + } + + /// + /// Serial port specific write routine + /// + /// + /// + /// - Hide data in logging for e.g. passwords + /// + protected virtual void SpecificPortWrite(Byte[] txBuffer) + { + PhysicalWrite(txBuffer); + } + + /// + /// Physically sending to the serial port + /// + /// + /// - Initial + /// + /// + /// - Doubling of sync byte in protocol behind sync byte itself implemented + /// + /// + /// - Flush buffer as MOXA sometimes takes two messages and combines these to one! + /// As the Genesis needs a separated wake-up message with a following delay before the + /// real payload message, this causes a lot of trouble. + /// + protected void PhysicalWrite(Byte[] txBuffer) + { + try + { + //don't double the sync byte itself + var txByteList = new List { txBuffer[0] }; + for (var byteCtr = 1; byteCtr < txBuffer.Length; byteCtr++) + { + txByteList.Add(txBuffer[byteCtr]); + + //write the doubled sync byte again if required by transmit protocol settings + if (PortSettingsForTransmitProtocol.DoubleSyncByte) + { + if (txBuffer[byteCtr].Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) + { + txByteList.Add(txBuffer[byteCtr]); + } + } + } + + _serialPort.Write(txByteList.ToArray(), 0, txByteList.Count); + + // flush the buffer for MOXA, to avoid two subsequent communications assembled to one communication! + _serialPort.BaseStream.Flush(); + + OnRawRecordSendOut?.Invoke(this, new ListBytePortDataEventArgs(txByteList, DateTimeOffset.UtcNow)); + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + } + } +} diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs b/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs index d303d837..5e25bb8b 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs @@ -857,11 +857,20 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - CancellationToken. /// + /// + /// - Temporary excluded un-comparable registers from write as this make no sense to write them blind being + /// unable to read the correct value back. + /// private StatusReturn WriteRegisterParameterSet(List recoveryRegistersDb, Int32 recRegistersCount, MeterRegisters meterRegisters, CancellationToken cancellationToken) { foreach (var recReg in recoveryRegistersDb) { + //TODO THW Check + // Temporary excluded un-comparable registers from write as this make no sense to write them blind being + // unable to read the correct value back. + if (_excludedFromCompareParameters.Any( regName => recReg.RegisterIdent.Equals(regName))) + continue; _actualRegisterCtr++; var overallProcessCtrPercent = 100.0 * _actualRegisterCtr / recRegistersCount; diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs.bak b/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs.bak index 5a31d27f..d303d837 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs.bak +++ b/Common/Hardware/WaterMeter/Genesis/GenesisCore/RegisterRestorer.cs.bak @@ -146,10 +146,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// private static readonly List _finalizeProgrammingAll = new List { + // Writing 1 will force a log flush new RecoveryRegisterItem("LOGGER_TriggerLogFlush", new Byte[] { 0x01 }), - // set counter to 0 as this is a value how often the radio detected a reset - new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }), - // trigger idle 0 releases the display to normal operation + // Trigger idle 0 releases the display to normal operation new RecoveryRegisterItem("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 }) }; @@ -159,12 +158,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// private static readonly List _finalizeProgrammingEmea = new List { + // Set counter to 0 as this is a value how often the radio detected a reset + new RecoveryRegisterItem("SENSUSRADIO_ResetCounter", new Byte[] { 0x00 }), // System state 0xFF will force a storage of all parameters and reset, followed by system state 0x02 // for customer mode, the reset needs only a few milliseconds // Attention logout-delay-login required after this step! new RecoveryRegisterItem("SENSUSRADIO_SystemState", new Byte[] { 0xFF }), - // seal the display to lock against changes + // Seal the display to lock against changes new RecoveryRegisterItem("GENESISFLOW_SealDisplay", new Byte[] { 0x01 }), + // Set counter to 0 as this is a value how often the radio detected a reset + new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }), }; /// @@ -172,8 +175,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// private static readonly List _finalizeProgrammingEol = new List { + // Writing 1 to this register clears the volume accumulator new RecoveryRegisterItem("GENESISFLOW_ResetAccumulators", new Byte[] { 0x01 }), + // Writing 1 to this register erases the RMA area new RecoveryRegisterItem("NFC_EraseRMA", new Byte[] { 0x01 }), + // Set counter to 0 as this is a value how often the customer app detected a reset new RecoveryRegisterItem("CUSTOMER_RebootCount", new Byte[] { 0x00 }) }; @@ -182,11 +188,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// private Int32 _actualRegisterCtr; - /// - /// Stop register access - /// - public Boolean StopRegisterAccess; - /// /// Genesis meter object /// @@ -573,6 +574,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore List recoveryRegistersDb, out String errorMsg) { errorMsg = ""; + if (string.IsNullOrEmpty(pcbId)) + return StatusReturn.Failed; try { var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetProgrammingParameters?PcbID={pcbId}"; @@ -620,14 +623,18 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// list of registers to update from database without special sequence /// + /// /// true if setup succeeded /// /// - Initial. /// - public StatusReturn EolProgrammingRegisters(List recoveryRegistersDb) + /// + /// - CancellationToken. + /// + public StatusReturn EolProgrammingRegisters(List recoveryRegistersDb, CancellationToken cancellationToken) { // mark EOL as true to force accu reset - return WriteRegisters(recoveryRegistersDb, true); + return WriteRegisters(recoveryRegistersDb, cancellationToken, true); } /// @@ -668,6 +675,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// list of registers to update from database without special sequence /// + /// /// EOL (end of line) programming with accu reset /// true if restoring succeeded /// @@ -722,7 +730,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Exported write routines to . /// - public StatusReturn WriteRegisters(List recoveryRegistersDb, Boolean eolProgramming = false) + /// + /// - CancellationToken. + /// + public StatusReturn WriteRegisters(List recoveryRegistersDb, CancellationToken cancellationToken, + Boolean eolProgramming = false) { if (_currentGenesis == null || string.IsNullOrEmpty(_currentGenesis.PcbId) || @@ -740,7 +752,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore RecoveryRegisters = recoveryRegistersDb; } - StopRegisterAccess = false; _actualRegisterCtr = 0; // Build string list for debug @@ -787,7 +798,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryPreparation, actualProcessMessage: Resources.StrRegisterRecoveryPreparation)); - if (StatusReturn.Failed == WriteRegisterParameterSet(_prepareProgramming, recRegistersCount, meterRegisters)) + if (StatusReturn.Failed == WriteRegisterParameterSet(_prepareProgramming, recRegistersCount, meterRegisters, + cancellationToken)) { // the logout will force the radio app to activate the settings _currentGenesis?.Logout(); @@ -798,7 +810,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryExec, actualProcessMessage: Resources.StrRegisterRecoveryExec)); - if (StatusReturn.Failed == WriteRegisterParameterSet(recoveryRegistersDb, recRegistersCount, meterRegisters)) + if (StatusReturn.Failed == WriteRegisterParameterSet(recoveryRegistersDb, recRegistersCount, meterRegisters, + cancellationToken)) { // the logout will force the radio app to activate the settings _currentGenesis?.Logout(); @@ -809,7 +822,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryFinalization, actualProcessMessage: Resources.StrRegisterRecoveryFinalization)); - if (StatusReturn.Failed == WriteRegisterParameterSet(finalizeProgrammingAll, recRegistersCount, meterRegisters)) + if (StatusReturn.Failed == WriteRegisterParameterSet(finalizeProgrammingAll, recRegistersCount, meterRegisters, + cancellationToken)) { // the logout will force the radio app to activate the settings _currentGenesis?.Logout(); @@ -835,12 +849,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// /// + /// /// /// /// - Initial imported from . /// + /// + /// - CancellationToken. + /// private StatusReturn WriteRegisterParameterSet(List recoveryRegistersDb, Int32 recRegistersCount, - MeterRegisters meterRegisters) + MeterRegisters meterRegisters, CancellationToken cancellationToken) { foreach (var recReg in recoveryRegistersDb) { @@ -913,7 +931,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore overallProcessCtrPercent, actualProcessMsg)); } - if (StopRegisterAccess) + if (cancellationToken.IsCancellationRequested) break; } @@ -926,6 +944,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// - The _restoreRegisters has to be filled with all registers being able to restore (RW), /// - The _finalReadRegisters has to be filled. /// + /// /// true if equal /// /// - Initial. @@ -963,13 +982,15 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Hash password and encryption key with SHA256. /// - public Boolean CompareRegisters() + /// + /// - CancellationToken. + /// + public Boolean CompareRegisters(CancellationToken cancellationToken) { if (_pcbIdFinalRegisterRead != _pcbIdWriteRegisters || RecoveryRegisters == null) return false; - StopRegisterAccess = false; _actualRegisterCtr = 0; var retVal = true; @@ -1130,7 +1151,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore } } - if (StopRegisterAccess) + if (cancellationToken.IsCancellationRequested) break; } catch (Exception ex) @@ -1145,6 +1166,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// Read the registers after the update for comparision. Build the post update registers here, /// because an update may have changed the registers (new or removed registers). /// + /// /// true if registers could be read /// /// - Initial. @@ -1174,7 +1196,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Check readBack value for null. /// - public Boolean FinalReadRegisters() + /// + /// - CancellationToken. + /// + public Boolean FinalReadRegisters(CancellationToken cancellationToken) { if (_currentGenesis == null) return false; @@ -1197,7 +1222,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore _finalRegisterList.Sort(); // executes login read logout - retVal = ReadRegistersSet(ref _finalRegisters); + retVal = ReadRegistersSet(ref _finalRegisters, cancellationToken); // backup read data to RecoveryRegisters if (RecoveryRegisters != null && _finalRegisters != null) @@ -1239,6 +1264,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// The defined registers are based on the "configuration.json" and the installed application with /// a specific version. /// + /// /// true if registers could be read /// /// - Initial. @@ -1263,7 +1289,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Register list changed from RegisterDefinition to RecoveryRegisterItem list. /// - public Boolean InitialReadRegisters() + /// + /// - CancellationToken. + /// + public Boolean InitialReadRegisters(CancellationToken cancellationToken) { if (_currentGenesis == null) return false; @@ -1289,7 +1318,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore _initialRegisterList = _initialRegisters.Select(register => register.RegisterIdent).ToList(); _initialRegisterList.Sort(); - return ReadRegistersSet(ref _initialRegisters); + return ReadRegistersSet(ref _initialRegisters, cancellationToken); } /// @@ -1344,6 +1373,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// The defined registers are based on the "configuration.json" and the installed application with a specific /// version. /// + /// /// true if registers could be read /// /// - Initial. @@ -1351,7 +1381,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Register list changed from RegisterDefinition to RecoveryRegisterItem list. /// - public Boolean ReadRegisters() + /// + /// - CancellationToken. + /// + public Boolean ReadRegisters(CancellationToken cancellationToken) { if (_currentGenesis == null) return false; @@ -1366,17 +1399,21 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0, Resources.StrReadRegistersBeforeUpdate)); - return ReadRegistersSet(ref registers); + return ReadRegistersSet(ref registers, cancellationToken); } /// /// Read predefined registers from RecoveryRegisters. /// + /// /// true if registers could be read /// /// - Initial. /// - public Boolean ReadPresetRegisters() + /// + /// - CancellationToken. + /// + public Boolean ReadPresetRegisters(CancellationToken cancellationToken) { if (_currentGenesis == null || RecoveryRegisters == null || RecoveryRegisters.Count == 0) return false; @@ -1387,7 +1424,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore _pcbIdWriteRegisters = _currentGenesis.PcbId; _pcbIdFinalRegisterRead = _currentGenesis.PcbId; - return ReadRegistersSet(ref RecoveryRegisters); + return ReadRegistersSet(ref RecoveryRegisters, cancellationToken); } /// @@ -1399,6 +1436,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// Executes login, read cycle and logout. /// /// registers to read + /// /// true if registers could be read /// /// - Initial. @@ -1428,11 +1466,13 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore /// /// - Register list changed from RegisterDefinition to RecoveryRegisterItem list. /// - private Boolean ReadRegistersSet(ref List registers) + /// + /// - CancellationToken. + /// + private Boolean ReadRegistersSet(ref List registers, CancellationToken cancellationToken) { if (_currentGenesis == null) return false; - StopRegisterAccess = false; _actualRegisterCtr = 0; // read values from Genesis @@ -1501,7 +1541,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore overallProcessCtrPercent, actualProcessMsg)); } - if (StopRegisterAccess) + if (cancellationToken.IsCancellationRequested) break; } catch (Exception ex) diff --git a/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache b/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache index 4163759b..9389613c 100644 --- a/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache +++ b/Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -8cb55c312de586b683d72cf58c13e2801d10e14b767777d98338d3e1473a1213 +7df2ad6be70922b931820861acc675522243ee78 diff --git a/Common/Production/ProductionUiCordonel/App.config b/Common/Production/ProductionUiCordonel/App.config index 2eb4ac66..728a665b 100644 --- a/Common/Production/ProductionUiCordonel/App.config +++ b/Common/Production/ProductionUiCordonel/App.config @@ -34,11 +34,11 @@ - - - - - + + + + + diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs index ac3db873..d3f53451 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs @@ -176,6 +176,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr /// /// - CancellationToken. /// + /// + /// - Removed change and read back of MainAlarmMask and ExtendedAlarmMask. + /// public override StatusReturn ExecuteProcess() { // This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress @@ -304,27 +307,16 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr DisableAllButtons(); }// !skipRadioCheck - // reset all radio alarms - WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_MainAlarmMask", - new Byte[] { 0 }, ProgrammingSource.ProgramInternal)); - WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_ExtendedAlarmMask", - new Byte[] { 0 }, ProgrammingSource.ProgramInternal)); // set system state back to shipping mode WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState", new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal)); ActualProcessProgress++; - // check radio is in customer mode 0x02 and alarm masks are 0 - var mainAlarmMask = - RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask")); - var extendedAlarmMask = - RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask")); _finalRadioSystemState = RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_SystemState")); ActualProcessProgress++; - if (// Radio mode and alarm masks NOT okay forces always an error - // radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active - _finalRadioSystemState != 0x02 || mainAlarmMask != 0 || extendedAlarmMask != 0 + if (// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active + _finalRadioSystemState != 0x02 // If radio check is enabled the signal level has to be in range and the test has to be finished || (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished))) { diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs.bak b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs.bak new file mode 100644 index 00000000..ac3db873 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckRadio.cs.bak @@ -0,0 +1,829 @@ +using System; +using System.Configuration; +using System.Linq; +using System.Net; +using System.Threading; +using System.Windows.Threading; +using LaaPackages.Features.Cordonel.Models; +using Newtonsoft.Json.Linq; +using SIRTCOM; +using Xylem.Common.CommonCore.Configuration; +using Xylem.Common.CommonCore.Consts; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; +using Xylem.Common.Logic.ProductionOrderCore; +using Xylem.Common.Logic.SoftwareAccessHelper; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.Properties; +using Xylem.Common.Production.ProductionUiCordonel.UserControls; +using Button = System.Windows.Controls.Button; +// ReSharper disable UnusedAutoPropertyAccessor.Local + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses +{ + public class CheckRadio : BaseProductionProcess + { + /// + /// Maximum of expected processing steps for this process to feed the GUI progress bar. + /// Use the sleep 250 to create x seconds timeout; + /// + private const Int32 TimeoutLoop_ms = 250; + private const Int32 Timeout_s = 10; + private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s; + + /// + /// Remind the ID of the radio result as feedback from the DB. + /// + private Int64 _radioResultCheckId; + + /// + /// Final state of radio system. + /// + private Int32 _finalRadioSystemState; + /// + /// Reminder for specific user control + /// + private UcRadioCheck _checkRadioUserControl; + + /// + /// Signal level successfully tested + /// + private Boolean _successSignalLevel; + + /// + /// Radio encryption key converted to string + /// + private String _radioEncryptionKey; + + /// + /// Test finished can be good or abort by done button to exit this production process + /// + private Boolean _testFinished; + + /// + /// SIRT communication okay + /// + private Boolean _sirtComOkay; + + private DataContainer _dataContainer; + private Statemashine _stateMachine; + + /// + /// Minimum PAM signal level of SIRT got from DB + /// + private Byte? _rssiPamSirtDbMin; + /// + /// Maximum PAM signal level of SIRT got from DB + /// + private Byte? _rssiPamSirtDbMax; + /// + /// Minimum SEMI signal level of Cordonel response got from DB + /// + private Byte? _rssiSemiCordonelDbMin; + /// + /// Maximum SEMI signal level of Cordonel response got from DB + /// + private Byte? _rssiSemiCordonelDbMax; + + /// + /// Measured from SIRT the signal strength of the Cordonel SEMI answer + /// + private Byte? _rssiSemiCordonelMeasuredBySirt; + + /// + /// Measured from Cordonel the signal strength of the SIRT PAM received + /// + private Byte? _rssiPamSirtMeasuredByCordonel; + + /// + /// Number of the SIRT box as these are evaluated for their signal strength + /// + private Byte _sirtBoxNr; + + /// + /// Comport number of SIRT + /// + private Byte _sirtComport; + + /// + /// Radio address Cordonel + /// + private UInt32 _radioAddressCordonel; + + /// + /// Ctor + /// + public CheckRadio(String name) : base(name) + { + } + + /// + protected override void InitUserControl() + { + UserControl = new UcRadioCheck(); + _checkRadioUserControl = (UcRadioCheck)UserControl; + _checkRadioUserControl.OnResetRequested += delegate + { + Retry(); + }; + //_checkRadioUserControl.OnDoneRequested += delegate + //{ + // SaveRssiLevelsToDb(); + //}; + } + + /// + /// Check of radio signal strength, skips process on special permission or if NA region + /// + /// + /// + /// - Initial + /// + /// + /// - Skip process on special permission or if NA region, + /// - Remove userControl from main screen if skipped. + /// + /// + /// - Fire SingleProcessState event with argument. + /// + /// + /// - Removed signals for and as + /// these will be automatically set based on return value in + /// . + /// + /// + /// - Assignment of stateMachine and dataContainer. + /// + /// + /// - Get encryption key moved to process to speed up + /// this process as this process needs to be run exclusively. + /// + /// + /// - EOL progress signaled. + /// + /// + /// - Logout at finalization, + /// - Log all write accesses. + /// + /// + /// - SkipRadioCheck not nullable. + /// + /// + /// - SetTestInit as bool. + /// + /// + /// - Separate TestInit and SirtInit (only if test shouldn't be skipped). + /// + /// + /// - CancellationToken. + /// + public override StatusReturn ExecuteProcess() + { + // This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress + if (Meter == null || ProductionRequirements == null || + (!Meter.Region.Equals("NA") && RadioEncryptionKey?.Count == 0)) + { + return StatusReturn.Failed; + } + + // Setup all parameters being able to execute initially or repeat the test + TestInit(); + + // Check first if radio is installed, + // North America Region(NA) doesn't have a Sensus radio application installed + if (Meter.Region.Equals("NA")) + { + EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA; + LogNewStatusMsg(Resources.StrSkippedMsgRadioCheckRegion); + PrepareExit(); + return StatusReturn.Skipped; + } + + // Starting point of time-consuming operations + SignalRunningState(MaxActualProgressSteps); + + // If requirement forces skipping but radio is installed, it should at least be set to customer mode and checked for + // it. + // Reminder for skip radio check forced by requirements + var skipRadioCheck = ProductionRequirements.SkipRadioCheck; + + try + { + if (!Meter.IsLoggedOn) + { + Meter.Login(); + if (!Meter.IsLoggedOn) + { + return SetTestFailed(Resources.StrErrorMsgCordonelLogin); + } + } + + // Standard run for radio signal test (RSSI) + if (!skipRadioCheck) + { + if (UserControl == null) + { + return SetTestFailed(Resources.StrErrorMsgRadioCheck); + } + + // Initialize the SIRT + if (!SirtInit()) + { + return SetTestFailed(Resources.StrErrorMsgSirt); + } + + //FrequencyIndicator + var freq = RegisterConverter.ByteArrayToValue( + Meter.ReadRegister("SENSUSRADIO_FrequencyIndicator")); + _radioAddressCordonel = + RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_RadioAddress")); + ActualProcessProgress++; + + var meterRawEncryptionKey = Meter.ReadRegister("SENSUSRADIO_EncryptionKey", 16); + var meterEncryptionKey = ""; + + // convert byte array to string + if (meterRawEncryptionKey != null) + { + meterEncryptionKey = BitConverter.ToString(meterRawEncryptionKey.ToArray()).Replace("-", ""); + } + + if (RadioEncryptionKey != null) + { + _radioEncryptionKey = RadioEncryptionKey[0].ToUpper(); + } + + // Compare the meterEncryptionKey read out from meter with the database RadioEncryptionKey + // got with the MeterProcessState/api/FinalCheck/GetEncryptionKey + if (meterRawEncryptionKey == null || RadioEncryptionKey == null || + string.IsNullOrEmpty(meterEncryptionKey) || string.IsNullOrEmpty(_radioEncryptionKey) || + !_radioEncryptionKey.Equals(meterEncryptionKey)) + { + return SetTestFailed(Resources.StrErrorMsgRadioKey); + } + + SuccessMsgDispatcher(Resources.StrSuccessMsgDbToMeterEncryptionKey); + ActualProcessProgress++; + + _sirtComport = Convert.ToByte(ConfigurationManager.AppSettings[$"SirtComport{freq}"]); + _sirtBoxNr = Convert.ToByte(ConfigurationManager.AppSettings["SirtBoxNr"]); + var ucRadio = (UcRadioCheck)UserControl; + ucRadio.SetHeadline($"{Resources.StrStateRadioCheck} BOX: {_sirtBoxNr}"); + LogNewStatusMsg($"{Resources.StrStateRadioCheck} Port: COM{_sirtComport}, BOX: {_sirtBoxNr}, " + + $"f: {freq} MHz"); + + // Tell the radio that something is in the slot which will impact the radio transmission + WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_IrDAModulePresent", + new Byte[] { 1 }, ProgrammingSource.ProgramInternal)); + + ActualProcessProgress++; + + // Clear the measurement as on a retry started from main control the old values will be displayed + ClearRssiMeasurement(); + // kick off RSSI test + StartRssiMeasurement(); + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + CancellationProcedure(); + + var exitTime = DateTime.Now + TimeSpan.FromSeconds(Timeout_s); + var actualTime = DateTime.Now; + // wait on result or timeout or stop request from main window + while (!_testFinished && exitTime > actualTime && SingleProcessState != SingleProcessState.Idle) + { + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + CancellationProcedure(); + + Thread.Sleep(TimeoutLoop_ms); + actualTime = DateTime.Now; + ActualProcessProgress++; + } + + // lock all buttons as process is finished + DisableAllButtons(); + }// !skipRadioCheck + + // reset all radio alarms + WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_MainAlarmMask", + new Byte[] { 0 }, ProgrammingSource.ProgramInternal)); + WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_ExtendedAlarmMask", + new Byte[] { 0 }, ProgrammingSource.ProgramInternal)); + // set system state back to shipping mode + WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState", + new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal)); + ActualProcessProgress++; + + // check radio is in customer mode 0x02 and alarm masks are 0 + var mainAlarmMask = + RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask")); + var extendedAlarmMask = + RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask")); + _finalRadioSystemState = RegisterConverter.ByteArrayToValue(Meter.ReadRegister("SENSUSRADIO_SystemState")); + ActualProcessProgress++; + + if (// Radio mode and alarm masks NOT okay forces always an error + // radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active + _finalRadioSystemState != 0x02 || mainAlarmMask != 0 || extendedAlarmMask != 0 + // If radio check is enabled the signal level has to be in range and the test has to be finished + || (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished))) + { + return SetTestFailed(!_sirtComOkay ? Resources.StrErrorMsgSirt : Resources.StrErrorMsgRadioCheck); + } + + SuccessMsgDispatcher(Resources.StrSuccessMsgRadioInCustomerMode); + SuccessMsgDispatcher(Resources.StrSuccessMsgRadioAlarmsReset); + + // Skip radio check by requirement + if (skipRadioCheck) + { + EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA; + WarningMsgDispatcher(Resources.StrSkippedMsgRadioCheckSpecialPermission); + PrepareExit(); + return StatusReturn.Skipped; + } + return SetTestSucceeded(); + + } + catch (Exception ex) + { + Meter?.WriteLog(ex.Message); + return SetTestFailed(Resources.StrErrorMsgRadioCheck); + } + } + + /// + /// Common for test Init + /// + private void TestInit() + { + // Uncheck executed process to leave the result open needed for retries + EolProgress.RadioCheckId = null; + EolProgress.FinalRadioSystemState = null; + EolProgress.FinalRadioSystemStateCheck = null; + // Value not saved as test hasn't been executed + _radioResultCheckId = 0; + _finalRadioSystemState = 0; + + _sirtComOkay = false; + _testFinished = false; + _successSignalLevel = false; + } + + /// + /// Initialize the SIRT + /// + /// + private Boolean SirtInit() + { + try + { + // Reset needed for repeated run + _stateMachine?.Stop(); + _stateMachine?.ClearBUPRadioAdresses(); + _stateMachine?.ClearPam(); + _stateMachine = null; + _dataContainer = null; + _stateMachine = new Statemashine(); + _dataContainer = new DataContainer(); + + } + catch (Exception ex) + { + Meter?.WriteLog(ex.Message); + return false; + } + + return true; + } + + + /// + /// Common to prepare exit for all status returns + /// + private void PrepareExit() + { + EolProgress.RadioCheckId = _radioResultCheckId; + EolProgress.FinalRadioSystemState = _finalRadioSystemState; + + // Essential to logout from meter at any exit + Meter?.Logout(); + + _stateMachine?.Stop(); + _stateMachine = null; + _dataContainer = null; + } + + /// + /// Common for test failed + /// + /// error message to dispatch and log + /// + private StatusReturn SetTestFailed(String errorMsg) + { + EolProgress.FinalRadioSystemStateCheck = EOLStatus.FAIL; + ErrorMsgDispatcher(errorMsg); + PrepareExit(); + return StatusReturn.Failed; + } + + /// + /// Common for test succeeded + /// + /// + private StatusReturn SetTestSucceeded() + { + EolProgress.FinalRadioSystemStateCheck = EOLStatus.OK; + PrepareExit(); + return StatusReturn.Okay; + } + + /// + /// Disable all buttons of user control. + /// + /// + /// + /// - Initial + /// + private void DisableAllButtons() + { + //EnableBtn(_checkRadioUserControl.btnSkip, false); + EnableBtn(_checkRadioUserControl.btnRetry, false); + } + + private void EnableBtn(Button btn, Boolean enable) + { + btn.Dispatcher.Invoke(DispatcherPriority.Send, + new Action(() => + { + btn.IsEnabled = enable; + } + )); + } + + private void UpdateUc() + { + _checkRadioUserControl.SetUi(new UcRadioCheck.UiContainer(_rssiPamSirtDbMin, _rssiSemiCordonelDbMin, + _rssiPamSirtMeasuredByCordonel, _rssiSemiCordonelMeasuredBySirt, _rssiPamSirtDbMax, _rssiSemiCordonelDbMax)); + } + + /// + /// Check of radio signal strength. + /// + /// + /// + /// - Initial + /// + private void StartRssiMeasurement() + { + // disable all buttons during ongoing measurement process + DisableAllButtons(); + UpdateUc(); + + try + { + ActualProcessProgress++; + GetRssiMinMaxValuesFromDb(_sirtBoxNr, Meter.OrderNumber); + SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey); + UpdateUc(); + ActualProcessProgress++; + } + catch (Exception ex) + { + _testFinished = true; + Meter?.WriteLog(ex.Message); + LogNewStatusMsg(Resources.StrErrorMsgRadioCheck); + } + } + + /// + /// Clear RSSI values for display of unknown values but leave limits active as + /// those are already loaded from DB and will not change during retry. Therefore, + /// the process can be optimized to avoid recurrent DB access! + /// + /// + /// + /// - Initial + /// + private void ClearRssiMeasurement() + { + _sirtComOkay = false; + _rssiPamSirtMeasuredByCordonel = null; + _rssiSemiCordonelMeasuredBySirt = null; + } + + /// + /// Retry collection of RSSI values (entire process with sending PAM and receiving SEMI). + /// + /// + /// + /// - Initial + /// + private void Retry() + { + // reset process counter to start from scratch + ActualProcessProgress = 0; + if (_stateMachine != null) + { + _stateMachine.ClearPam(); + _stateMachine.Stop(); + } + ClearRssiMeasurement(); + UpdateUc(); + SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey); + } + + /// + /// ATTENTION: + /// As those definitions are directly used as DB definitions for pushing the result: + /// + /// LEAVE THOSE NAMING AS DEFINED HERE! + /// + /// + private class DataContainer + { + public Int32 RadioAdress + { + get; set; + } + public Byte RSSI_PAM + { + get; set; + } + public Byte RSSI_SEMI + { + get; set; + } + public Int32 LimitId + { + get; set; + } + public Byte Result + { + get; set; + } + } + + + /// + /// Save RSSI values to DB. + /// + /// + /// + /// - Initial + /// + private void SaveRssiLevelsToDb() + { + try + { + if (_dataContainer != null) + { + _dataContainer.RadioAdress = (Int32)_radioAddressCordonel; // 0-4294967295 + var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}SaveRSSI"; + + var response = LocalWebRequest.PostRequestAsync(url, 30000, out var httpStatus, _dataContainer); + if (!string.IsNullOrEmpty(response) && httpStatus == HttpStatusCode.OK) + { + var jsonObj = JObject.Parse(response); + _radioResultCheckId = Convert.ToInt32(jsonObj.SelectToken("radiocheckid")); + } + + SuccessMsgDispatcher(Resources.StrSuccessMsgSetRadioRssiLimitsToDb); + ActualProcessProgress++; + } + else + { + ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb); + } + } + catch (Exception ex) + { + Meter?.WriteLog(ex.Message); + ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb); + } + finally + { + _testFinished = true; + } + } + + /// + /// Get RSSI values from DB. + /// + /// + /// + /// - Initial + /// + private void GetRssiMinMaxValuesFromDb(Byte BoxNr, Int32 productionOrderNo) + { + try + { + if (_dataContainer != null) + { + var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}GetLimits/{BoxNr}/{productionOrderNo}"; + var body = LocalWebRequest.GetRequest(url, 300000, out var httpStatus); + if (httpStatus != HttpStatusCode.OK || string.IsNullOrEmpty(body)) + { + ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb); + return; + } + var o = JObject.Parse(body); + + _dataContainer.LimitId = Convert.ToInt32(o.SelectToken("LimitId")?.ToString()); + _rssiPamSirtDbMin = Convert.ToByte(o.SelectToken("RSSI_PAM_Min")?.ToString()); + _rssiPamSirtDbMax = Convert.ToByte(o.SelectToken("RSSI_PAM_Max")?.ToString()); + _rssiSemiCordonelDbMin = Convert.ToByte(o.SelectToken("RSSI_SEMI_Min")?.ToString()); + _rssiSemiCordonelDbMax = Convert.ToByte(o.SelectToken("RSSI_SEMI_Max")?.ToString()); + SuccessMsgDispatcher(Resources.StrSuccessMsgGetRadioRssiLimitsFromDb + + $": ID: ({_dataContainer.LimitId}), " + + $"SIRT Min: ({_rssiPamSirtDbMin}), " + + $"SIRT Max: ({_rssiPamSirtDbMax}), " + + $"Cordonel Min: ({_rssiSemiCordonelDbMin}), " + + $"Cordonel Max: ({_rssiSemiCordonelDbMax})"); + ActualProcessProgress++; + } + } + catch (Exception ex) + { + Meter?.WriteLog(ex.Message); + ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb); + } + } + + /// + /// Send PAM to force a BUP and a SEMI and start the RSSI observer to measure the signal strength. + /// + /// + /// + /// - Initial + /// + /// + /// - Optimized based on new documentations. + /// + private void SendPamListenSemi(Int32 radioAddress, Int32 byteComPort, String strKey) + { + if (_dataContainer == null || _stateMachine == null) + return; + + _rssiPamSirtMeasuredByCordonel = null; + _rssiSemiCordonelMeasuredBySirt = null; + + Byte P81_1 = 0; + + _dataContainer.RadioAdress = radioAddress; + + _stateMachine.initialise((Byte)byteComPort); + var date = DateTime.Now.Date; + var loggingPath = $"C:\\GenesisLog\\{date:yyyy-MM-dd}\\ProductionUiCordonel\\{Meter.PcbId}_RadioCheck.log"; + _stateMachine.StartLoggin(loggingPath); + + LogNewStatusMsg($"{Resources.StrRadioMsgSendPam}: {radioAddress.ToString()}"); + + // Initialize the state machine of the SIRT dll + _stateMachine.Stop(); + _stateMachine.ClearPam(); + _stateMachine.Timeout_s = 30; + + // Dummy PAM 00 with radio encryption key, SIRT dll takes the key for the response and the + // address for the request and response, CommandHex 00 is data size of 0, so it makes no sense + // to encrypt the 00 PAM, but the key will be used in the SEMI response + _stateMachine.SendPAM(radioAddress.ToString(), "00", "", P81_1, 1, 0, strKey, "0", true); + ActualProcessProgress++; + + // SystemState 0x55 will force meter immediately to send BUP, internal the system state remains + // on 0x02. This has to be sent after PAM! + Meter.WriteRegister("SENSUSRADIO_SystemState", 0x55); + + // Start polling + _stateMachine.OnStateChanged += StateMachineOnStateChange_Handler; + _stateMachine.Start(); + + ActualProcessProgress++; + } + + /// + /// Get RSSI (Received Signal Strength Indicator) values from SIRT (Sensus Interface Radio Tool). + /// Set the + /// + /// + /// + /// - Initial + /// + /// + /// - Initial + /// + private void GotRssiCallback(Byte rssiSemiCordonel, Byte rssiPamSirt) + { + if (_dataContainer == null) + return; + + // RSSI PAM sent by SIRT and measured by Cordonel + LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiSirt}: ({rssiPamSirt})"); + // RSSI SEMI sent by Cordonel and measured by SIRT + LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiCordonel}: ({rssiSemiCordonel})"); + + _rssiPamSirtMeasuredByCordonel = rssiPamSirt; + _rssiSemiCordonelMeasuredBySirt = rssiSemiCordonel; + _dataContainer.RSSI_PAM = rssiPamSirt; + _dataContainer.RSSI_SEMI = rssiSemiCordonel; + + UpdateUc(); + + // threshold check for failed test + if (_rssiPamSirtMeasuredByCordonel < _rssiPamSirtDbMin || + _rssiPamSirtMeasuredByCordonel > _rssiPamSirtDbMax || + _rssiSemiCordonelMeasuredBySirt < _rssiSemiCordonelDbMin || + _rssiSemiCordonelMeasuredBySirt > _rssiSemiCordonelDbMax) + { + _dataContainer.Result = 0; + // done can finish the test which is equal to abort on error + //EnableBtn(_checkRadioUserControl.btnSkip, true); + // reset can repeat the test as often as wanted + EnableBtn(_checkRadioUserControl.btnRetry, true); + _successSignalLevel = false; + } + else + { + // on success there is no choice to made, it is finished + _dataContainer.Result = 1; + _successSignalLevel = true; + + // safe those results to DB for logging + SaveRssiLevelsToDb(); + } + } + + /// + /// State machine state change handler. + /// + /// + /// + /// - Initial + /// + private void StateMachineOnStateChange_Handler(Object sender, MyEventArgs e) + { + if (_stateMachine == null) + return; + try + { + // Reading back sent PAM from SIRT to Cordonel + var pam = _stateMachine.GetPAM(1); + + if (pam != null) + { + if (pam.State == PAM.PAMStates.Timeout) + { + LogNewStatusMsg($"Timeout {pam.GetTime()} s"); + } + + if (pam.CountSemi > 0) + { + // SEMI received from Cordonel + _stateMachine.Stop(); + _stateMachine.ClearPam(); + + var hexBytesSemi = pam.Semi.HexData.Replace(" ", ""); + var semiBytes = new Byte[hexBytesSemi.Length / 2]; + + for (var i = 0; i < hexBytesSemi.Length; i += 2) + semiBytes[i / 2] = Convert.ToByte(hexBytesSemi.Substring(i, 2), 16); + + // SEMI Status okay if 0, if SIRT dll returns unequal to 0 the encryption key is wrong + // If 0 the encryption key is correct + if (semiBytes[19] == 0) + { + // RSSI SEMI sent by Cordonel and measured by SIRT (signal level of Cordonel in byte 2) + var rssiSemiMeasuredBySirt = semiBytes[2]; + + // RSSI PAM sent by SIRT and measured by Cordonel (signal level of SIRT packet to SEMI in byte 20) + var rssiPamMeasuredByCordonel = semiBytes[20]; + + GotRssiCallback(rssiSemiMeasuredBySirt, rssiPamMeasuredByCordonel); + _sirtComOkay = true; + } + else + { + // SEMI Status not 0 is internal error which should be logged + LogNewStatusMsg(pam.Semi.GetPamErrorMessage()); + _sirtComOkay = false; + } + } + else + { + LogNewStatusMsg($"BUPS: ({pam.CountBup})"); + _sirtComOkay = false; + } + }//pam not null + else + { + _stateMachine.Stop(); + _stateMachine.ClearPam(); + } + } + catch (Exception ex) + { + Meter?.WriteLog(ex.Message); + } + } + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs index 40b7fb09..ad66190a 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs @@ -453,11 +453,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses /// /// - Handle genesis meter is null as new meter. /// + /// + /// - Check _genesisMeter not null before equality with old pcbid. + /// private Boolean CheckForNewMeter() { if (_genesisMeter == null || (!string.IsNullOrEmpty(_lastPcbId) && - !_genesisMeter.PcbId.Equals(_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. diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs.bak b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs.bak new file mode 100644 index 00000000..40b7fb09 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/ProcessController.cs.bak @@ -0,0 +1,857 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using LaaPackages.Features.Cordonel.Models; +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.WaterMeterCore; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; +using Timer = System.Threading.Timer; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses +{ + /// + /// Implementation for all methods needed to drive the state machine of the process controller + /// + 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 _lockedProcessState; + // sender of state change event to evaluate in e.g. error process + private IProductionProcess _stateChangeRequestProcess; + // List of production processes + public List 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 _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; + + /// + /// Change the overview table of the GUI to the state of the individual process + /// + public event EventHandler OnSingleProcessStateChanged; + + /// + /// The overall process chain changed needed to signal to the GUI + /// - Can be forced by the state machine to invoke, + /// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next + /// required sate after break, error or completion to change the state of the state machine. + /// + public event EventHandler OnOverallProcessStateChanged; + + /// + /// Request for logging to GUI output and log-file + /// + public event EventHandler OnProcessLogRequest; + + /// + /// Keep the progress bars of the GUI up to date + /// + public event EventHandler OnProcessProgressChanged; + + // cancellation token + private CancellationTokenSource _cancellationTokenSource; + private CancellationToken _cancellationToken; + + // cyclic timer for state machine call + private Timer _processCtrlTimer; + private const Int32 ProcessTimerCycleMs = 250; + + /// + /// Avoid waiting for a single process completion. + /// + public const IProductionProcess DoNotWaitForSingleProcessCompletion = null; + + /// + /// Kick off a parallel execution of the state machine. + /// + public const Boolean ExecuteInParallel = true; + + #endregion --------------------------------------- Variables -------------------------------------------------- + #region ------------------------------------------ Class ------------------------------------------------------ + + /// + /// Ctor: + /// - Setup process timer for cyclic call of state machine, + /// - Create and setup Cordonel. + /// + /// physical slot for this meter + /// + /// + /// - Initial + /// + /// + /// - Always set to production mode to load the registers for this FW and not the latest, + /// - Always RegisterWatchService on, + /// - Always MinMaxCHeck on. + /// + public ProcessController(Int32 slot, IProcessStateDef processSateDef) + { + _processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs); + _slot = slot; + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + _processStateDef = processSateDef; + InitNewMeter(); + InitProcesses(); + // program start with idle process + _stateMachineState = ProcessState.Idle; + } + + /// + /// Initialization of new meter + /// + /// + /// - Initial + /// + /// + /// - Initialize processes to build a list of all production processes. + /// + /// + /// - Removed list of failed production processes + /// + 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 + _lockedProcessState = ProcessState.StateListDelimiter; + // Create a register restorer + _registerRestorer = new RegisterRestorer(_genesisMeter); + _radioEncryptionKey = new List(); + // 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(); + } + + /// + /// Removal of old meter + /// + 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; + } + + /// + /// Dispose. + /// + /// + /// - Initial + /// + public void Dispose() + { + _processCtrlTimer?.Dispose(); + _processCtrlTimer = null; + _processStateDef = null; + KillProcesses(); + RemoveMeter(); + } + + #endregion --------------------------------------- Class ------------------------------------------------------ + #region ------------------------------------------ Events ----------------------------------------------------- + /// + /// Single process state or process list changed event, + /// Use event to force special procedure like uninstalling events + /// + /// + /// + /// + /// - Initial + /// + /// + /// - Remove process events if process is finished. + /// + /// + /// - Remove process events if process is finished or skipped. + /// + private void SingleProcessStateChanged_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; + } + // signal state change to GUI + OnSingleProcessStateChanged?.Invoke(sender, e); + } + + private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e) + { + OnProcessProgressChanged?.Invoke(sender, e); + } + + private void ProcessLog_Handler(Object sender, ProcessLogArgs e) + { + OnProcessLogRequest?.Invoke(sender, e); + } + /// + /// Handler to set a new state for the state machine regarding the entire final-process + /// + /// + /// + /// + /// - Initial + /// + private void OverallProcessStateChanged_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; + } + } + + /// + /// Process timer + /// + /// + /// + /// - Initial + /// + private void TmrProcessCtrlCycle_Elapsed(Object state) + { + StateMachine(); + } + + #endregion --------------------------------------- Events ----------------------------------------------------- + #region ------------------------------------------ State Machine ---------------------------------------------- + + /// + /// 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 + /// + /// + /// - Initial + /// + /// + /// - Generic implementation + /// + private void StateMachine() + {// avoid repeated execution of the state machine on unchanged state + if (_stateMachineState == _lockedProcessState) + { + Thread.Sleep(ProcessTimerCycleMs >> 1); + } + else + { + try + { + // remind backup state to avoid repeated execution and side effects + _lockedProcessState = _stateMachineState; + + // signal overall process progress to GUI based on ready processes + CalcAndPushOverAllProgress(); + + // signal state change to logger + OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState)); + + // get the processStateStruct of the actual set stateMachineSTate + var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState); + + switch (_stateMachineState) + { + case ProcessState.Idle: + // This state needs an external state change like user input to go ahead + break; + + case ProcessState.DetectCordonel: + // Wait for cancellation + if (_cancellationToken.IsCancellationRequested) + { + Thread.Sleep(500); + // Reset the cancellation request + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + _cancellationToken = _cancellationTokenSource.Token; + } + + // Set up the counter for maximum overall process progress bar in GUI + CalcAndPushMaxOverAllProcessSteps(); + + // Detection of Cordonel has to be completed before assigning next state, so leave to automatic + // to change the state after successfully execution. The detection is the base for all others. + StartProcess(processStateStruct); + break; + + case ProcessState.CheckNewMeter: + // Detection of Cordonel has to be completed before assigning next state. It has to be checked if + // the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after + // new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then + // enter the "ConnectCordonel"! + _stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel; + break; + + + case ProcessState.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 ------------------------------------------------------ + /// + /// Returns the currentGenesis information for display + /// + /// + /// + /// - Initial + /// + public CordonelRequirements GetProductionRequirements() + { + return _cordonelRequirements; + } + /// + /// Returns the currentGenesis information for display + /// + /// + /// + /// - Initial + /// + public IGenesisMeter GetGenesisMeter() + { + return _genesisMeter; + } + /// + /// Clears all collected data on new genesisMeter and reminds the detected for next + /// detection and run of this comparison. + /// + /// true for new meter + /// + /// - Initial + /// + /// + /// - 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! + /// + /// + /// - Handle genesis meter is null as new meter. + /// + private Boolean CheckForNewMeter() + { + if (_genesisMeter == null || + (!string.IsNullOrEmpty(_lastPcbId) && + !_genesisMeter.PcbId.Equals(_lastPcbId))) + { + // This loop will be entered after the meter has changed, but not on the initial meter + // after program start. + KillProcesses(); + RemoveMeter(); + InitNewMeter(); + InitProcesses(); + // Remind actual genesis + _lastPcbId = _genesisMeter?.PcbId; + + // New meter detected + return true; + } + + // Remind actual genesis + _lastPcbId = _genesisMeter?.PcbId; + + // Old meter + return false; + } + + /// + /// Restart final test on first failed or aborted test. + /// + /// + /// - Initial + /// + /// + /// - Removed list of failed production processes and search in Processes + /// + /// + /// - Leave skipped processes as is. + /// + 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 + 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; + } + /// + /// Error handler + /// + /// + /// - Initial + /// + /// + /// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization + /// to re-start with ProductionProcessExecFinalParametrization. + /// + /// + /// - Removed list of failed production processes + /// + /// + /// - Kick off exec parametrization if check after reboot failed. + /// + 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.CheckFinalParametrization); + pp.RetryProcess(); + } + + // Error signal needed to abort to all processes + AbortProcesses(); + } + + /// + /// Calculate the maximum of all processes expected to execute and signal to GUI. + /// + private void CalcAndPushMaxOverAllProcessSteps() + { + if (_processStateDef == null) + return; + + var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses(); + OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps, + ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps)); + } + + + /// + /// Calculate all processes which are completed or skipped and signal to GUI. + /// + 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)); + } + + /// + /// Initialize all processes which are connected to a production process. + /// + /// + /// - Initial + /// + private void InitProcesses() + { + if (Processes == null) + Processes = new List(); + + // get all processes relevant for EOL from table which has to be sorted + var processes = _processStateDef.GetAllEolRelevantProcesses(); + + foreach (var process in processes) + { + // process is standby for execution after idle, this overrides the eventually abort state + 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); + } + } + } + + /// + /// 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. + /// + /// + /// actual state + /// + /// - Initial + /// + /// + /// - Skipped process due to one of the previous processes failed + /// + /// + /// - Restructured to avoid process start if processes to wait for are not completed + /// + /// + /// - Changed from programming parameters to register restorer + /// + /// + /// - Changed setup process to start process being able to create a process list in advance. + /// + /// + /// - Changed setup process to start process being able to create a process list in advance. + /// + /// + /// - Feed with processStateStruct. + /// + protected 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; + } + + // process is waiting for execution after init, this overrides the eventually abort state + processStateStruct.ProductionProcess.InitProcess(); + + // 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; + processStateStruct.ProductionProcess.CancellationToken = _cancellationToken; + + // 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); + } + + /// + /// Check all processes for error or abort request + /// + /// true if abort required + /// + /// - Cancellation token. + /// + private Boolean AnyProcessRequiresAbortion() + { + return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error || + pp.SingleProcessState == SingleProcessState.Abort || + _cancellationToken.IsCancellationRequested); + } + /// + /// Check process if in status waiting or running (incomplete) + /// + /// production process + /// true if process to wait for execution or exit running state + private Boolean WaitForProcess(IProductionProcess pp) + { + return pp.SingleProcessState == SingleProcessState.Waiting || + pp.SingleProcessState == SingleProcessState.Running; + } + /// + /// Install process events + /// + /// + private void AddProcessEvents(IProductionProcess process) + { + if (process == null) + return; + + process.OnProcessProgressChanged += ProcessProgress_Handler; + process.OnProcessLogRequest += ProcessLog_Handler; + process.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler; + process.OnOverallProcessStateChanged += OverallProcessStateChanged_Handler; + } + + /// + /// Remove process events + /// + /// + private void RemoveProcessEvents(IProductionProcess process) + { + if (process == null) + return; + + process.OnProcessProgressChanged -= ProcessProgress_Handler; + process.OnProcessLogRequest -= ProcessLog_Handler; + process.OnSingleProcessStateChanged -= SingleProcessStateChanged_Handler; + process.OnOverallProcessStateChanged -= OverallProcessStateChanged_Handler; + } + + /// + /// Get the actual process state of the state machine + /// + /// actual process state + /// + /// - Initial + /// + private ProcessState GetProcessState() + { + return _stateMachineState; + } + + /// + /// Set the new process state of the state machine + /// + /// true, if process state is set + /// + /// - Initial + /// + public Boolean SetProcessState(ProcessState newProcessState) + { + if (newProcessState != _stateMachineState) + { + _stateMachineState = newProcessState; + return true; + } + + return false; + } + + /// + /// Remove all processes from the list: + /// - Signal change to GUI, + /// - Remove process events all, + /// - Set state to abort. + /// + /// + /// - Initial + /// + /// + /// - Abort register access. + /// + /// + /// - Cancellation token source. + /// + private void KillProcesses() + { + _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) + { + pp.IdleProcess(); + } + } + + /// + /// Abort all processes if one previous process failed, but leave the list intact. + /// Leave all events for those which are already running intact to force a clean stop. + /// + /// + /// - Initial + /// + 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 ------------------------------------------------------ + } +} diff --git a/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs index 81001603..774a2546 100644 --- a/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs +++ b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs @@ -803,6 +803,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel // remove last userControl dpSubProcess.Children.Clear(); rtbLog.Document.Blocks.Clear(); + + // clear all visual fields as it may be a new pcbId + UpdateContentControl(lblOrderNumberValue, "?"); + UpdateContentControl(lblRadioAddressValue, ""); + UpdateContentControl(lblRadioAddressText, ""); + UpdateContentControl(lblFwValue, "?"); + UpdateContentControl(lblMeterSizeValue, "?"); + UpdateContentControl(lblLutValue, "?"); + UpdateContentControl(lblSerialNumberValue, "?"); + UpdateContentControl(lblRegionValue, "?"); + UpdateUi(lblPcbIdValue, "?"); + _processCtrl?.SetProcessState(ProcessState.DetectCordonel); } @@ -883,7 +895,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel // Returns the compressed file for error report printout File.WriteAllText(fileName, compressedSb.ToString()); if (printReport) + { Process.Start(fileName); + } } } diff --git a/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs.bak b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs.bak new file mode 100644 index 00000000..81001603 --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs.bak @@ -0,0 +1,963 @@ +using System; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Threading; +using LaaPackages.Features.Cordonel.Models; +using Newtonsoft.Json; +using NLog; +using Xylem.Common.CommonCore.Configuration; +using Xylem.Common.CommonCore.Consts; +using Xylem.Common.Hardware.Interfaces.Ports.PortCore; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs; +using Xylem.Common.Utils.Logging; +using Xylem.Common.Utils.UserAccessCtrl; +using Control = System.Windows.Forms.Control; +using TextBox = System.Windows.Controls.TextBox; +using UserControl = System.Windows.Controls.UserControl; + +namespace Xylem.Common.Production.ProductionUiCordonel +{ + /// + /// Interaction logic for FinalTest.xaml + /// + public sealed partial class ProductionWindow + { + #region variables and properties + private readonly ILogger _logger; + // remind manually changed culture setting + private static readonly CultureInfo _cultureInfo = Thread.CurrentThread.CurrentCulture; + private static readonly AssemblyName _assemblyName = Assembly.GetExecutingAssembly().GetName(); + private static readonly String _assemblyExecRootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private static readonly Version _version = _assemblyName.Version; + private readonly String _strSoftwareNameVersion; + private RegisterLDAPUser _registerForm; + + private Int32 Slot + { + get; set; + } + + // maximum processing steps for calculation of progress bar + private Int32 _maxOverallProcessSteps; + private Int32 _maxSingleProcessSteps; + private ProcessController _processCtrl; + private DateTimeOffset _startTime; + private DispatcherTimer _timer; + private String _lastLoggingTextToAvoidRepetition; + private CordonelRequirements _productionRequirements; + private UserAccessWindow _userAccessWindow; + private IProcessStateDef _processStateDef; + + #endregion + + public ProductionWindow(String softwareName, String projectName, IProcessStateDef processStateDef) + { + _logger = NLogHelper.CreateOrGetLogger(projectName); + _processStateDef = processStateDef; + String msg; + try + { + // returns true in DEBUG configuration + if (!Logic.SoftwareAccessHelper.Access.HasAccess(_assemblyName)) + { + msg = $"{Properties.Resources.StrStartMessageSwLicenseExpired} {_version}"; + _logger.Error(msg); + MessageBox.Show(msg, Properties.Resources.StrProcessStateError, + MessageBoxButton.OK, MessageBoxImage.Error); + Close(); + return; + } + } + catch (Exception e) + { + msg = Properties.Resources.StrErrorMsgMissingNetworkConnetction + " " + e.Message; + _logger.Error(msg); + MessageBox.Show(msg, + Properties.Resources.StrProcessStateError, + MessageBoxButton.OK, MessageBoxImage.Error); + Close(); + return; + } + + InitializeComponent(); + // Log version to files for debug purposes + _strSoftwareNameVersion = $"{softwareName} {_version}"; + _logger.Info(_strSoftwareNameVersion); + + Title = _strSoftwareNameVersion; + + //static labels + UpdateContentControl(lblSerialNumberTxt, Properties.Resources.StrLblSerialNumberTxt + ":"); + UpdateContentControl(lblRadioAddressText, Properties.Resources.StrLblRadioAddressTxt + ":"); + UpdateContentControl(lblOrderNumberText, Properties.Resources.StrLblOrderNumberTxt + ":"); + UpdateContentControl(lblMeterSizeText, Properties.Resources.StrLblMeterSizeTxt + ":"); + UpdateContentControl(lblSingleProcessText, Properties.Resources.StrLblSingleProcessText + ":"); + UpdateContentControl(lblTotalProcessText, Properties.Resources.StrLblTotalProcessText + ":"); + optionsMenu.Header = Properties.Resources.StrMenuOptionsText; + optionsMenuChangePassword.Header = Properties.Resources.StrMenuChangePasswordText; + hlpMenu.Header = Properties.Resources.StrMenuHelpText; + UpdateUi(lblApprovalText, ""); + UpdateUi(lblTimeText, Properties.Resources.StrLblTimeText + ":"); + + // buttons + UpdateContentControl(btnStart, Properties.Resources.StrBtnStart); + UpdateContentControl(btnStop, Properties.Resources.StrBtnStop); + + InitializeFormState(); + + } + + private ApplicationSettings Settings + { + get; set; + } + + /// + /// Initialize the FinalTest GUI: + /// - Read the configuration file for slot and port assignments and settings, + /// - Dispatch slots to slot combobox, + /// - Read setting from "ProductionUiConfig.json" file for GUI and slot setup, + /// - Create final process controller and install events, + /// - Create a dispatch timer to display the elapsed time of the process. + /// + /// + /// + private void Window_Initialized(Object sender, EventArgs e) + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + nameof(Hardware.WaterMeter.Genesis), ProgramConfig.SerialConfigFileName); + + if (!File.Exists(configFile)) + { + var msg = Properties.Resources.StrErrorMsgMissingConfigurationFile; + MessageBox.Show(msg, + Properties.Resources.StrProcessStateError, + MessageBoxButton.OK, MessageBoxImage.Error); + _logger.Error(msg); + throw new ApplicationException(msg); + } + var tr = new StreamReader(configFile); + var meterConfigList = JsonConvert.DeserializeObject(tr.ReadToEnd()); + + cbxSlotBox.Items.Clear(); + if (meterConfigList != null) + { + foreach (var item in meterConfigList.OrderBy(s => s.Slot)) + { + cbxSlotBox.Items.Add(item.Slot); + } + } + + cbxSlotBox.SelectedIndex = 0; + Settings = new ApplicationSettings("ProductionUiConfig.json"); + + UseSetting(true); + + if (cbxSlotBox.SelectedItem == null || string.IsNullOrEmpty(cbxSlotBox.SelectedItem.ToString()) || + !int.TryParse(cbxSlotBox.SelectedItem.ToString(), out var slot)) + { + Slot = 1; + } + else + { + Slot = slot; + } + + try + { + _processCtrl = new ProcessController(Slot, _processStateDef); + } + catch (Exception ex) + { + _logger.Error(ex.Message); + MessageBox.Show(ex.Message, + Properties.Resources.StrProcessStateError, + MessageBoxButton.OK, MessageBoxImage.Error); + throw new ApplicationException(ex.Message); + } + + //install the process changed event handler for single processes + _processCtrl.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler; + _processCtrl.OnProcessLogRequest += ProcessLog_Handler; + _processCtrl.OnProcessProgressChanged += ProcessProgress_Handler; + _processCtrl.OnOverallProcessStateChanged += ProcessStateChange_Handler; + + _timer = new DispatcherTimer(); + _timer.Tick += TmrProgressUpdate_Tick; + _timer.Interval = TimeSpan.FromMilliseconds(500); + + UpdateStatusGrid(); + } + + private void Window_Closing(Object sender, CancelEventArgs e) + { + _userAccessWindow?.Close(); + _processCtrl.OnSingleProcessStateChanged -= SingleProcessStateChanged_Handler; + _processCtrl.OnProcessLogRequest -= ProcessLog_Handler; + _processCtrl.OnProcessProgressChanged -= ProcessProgress_Handler; + _processCtrl.OnOverallProcessStateChanged -= ProcessStateChange_Handler; + _userAccessWindow = null; + + _processCtrl.Dispose(); + _processCtrl = null; + _timer.Stop(); + _timer.Tick -= TmrProgressUpdate_Tick; + _timer = null; + _registerForm = null; + } + private void UseSetting(Boolean setSlot = false) + { + //tblkAutodetectState.Text = "Off"; + //if (Settings.AutoDetect) + //{ + // tblkAutodetectState.Text = "On"; + //} + if (setSlot) + { + cbxSlotBox.SelectedValue = Settings.Slot; + } + } + + private void InitializeFormState() + { + if (Software.IsRegistrationPending) + { + _registerForm = new RegisterLDAPUser(); + _registerForm.OnCancellation += OnUserAccessControlCancellation; + UiElmEnable(optionsMenu, false, false); + } + else + { + UiElmEnable(optionsMenu, true); + UiElmEnable(optionsMenuChangePassword, Software.IsAutenticated); + UiElmEnable(optionsMenuLogout, Software.IsAutenticated); + UiElmEnable(optionsMenuLogin, !Software.IsAutenticated); + UiElmEnable(btnStart, Software.IsAutenticated); + UiElmEnable(btnStop, false); + UpdateContentControl(lblOperator, + Software.IsAutenticated + ? $"{Properties.Resources.StrLblOperatorNameText}: {Software.UserName}" + : $"{Properties.Resources.StrLblOperatorNameText}: ?"); + } + } + + /// + /// Shows the user access window with login form or password change form. + /// + /// + /// Windows forms control + /// + /// + /// - Initial. + /// + private void ShowUserAccessWindow(Boolean visible = true, Control control = null, String headerTxt = "") + { + if (visible && control != null) + { + UiElmEnable(optionsMenu, false); + + if (_userAccessWindow == null) + _userAccessWindow = new UserAccessWindow(control, headerTxt); + // View the user access window as container for the windows forms password change and login + _userAccessWindow.OnCloseWindow += OnUserAccessWindowClosed_Handler; + _userAccessWindow.ShowDialog(); + } + else + { + if (_userAccessWindow != null) + { + _userAccessWindow.OnCloseWindow -= OnUserAccessWindowClosed_Handler; + try + { + _userAccessWindow.Close(); + } + catch (Exception) + { + //ignore + } + _userAccessWindow = null; + } + + if (_registerForm != null) + _registerForm.OnCancellation -= OnUserAccessControlCancellation; + + UiElmEnable(optionsMenu, true); + + } + } + + /// + /// Close event handler of the user access window with login form or password change form. + /// + /// + /// + /// + /// - Initial. + /// + private void OnUserAccessWindowClosed_Handler(Object sender, EventArgs eventArgs) + { + ShowUserAccessWindow(false); + InitializeFormState(); + } + + private void UpdateUi(ContentControl c, String content, Brush color = null) + { + if (color == null) + color = Brushes.Black; + + c.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + c.Content = content; + c.Foreground = color; + })); + } + + private void UpdateUi(TextBox c, String content, Brush color = null) + { + if (color == null) + color = Brushes.Black; + + c.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + c.Text = content; + c.Foreground = color; + })); + } + private void UpdateUi(TextBlock c, String content, Brush color = null) + { + if (color == null) + color = Brushes.Black; + + c.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + c.Text = content; + c.Foreground = color; + })); + } + + /// + /// Update the elapsed time as information + /// + /// information attached to process depending on region + /// + /// - Initial + /// + private void SetTimeDisplay() + { + var time = DateTimeOffset.UtcNow; + var timeSpan = time - _startTime; + UpdateContentControl(lblTimeValue, $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}"); + } + + /// + /// Common routine for state change: + /// - Log state change, + /// - Execute stop procedure on error. + /// + /// + /// + /// + /// - Initial + /// + /// + /// - Extended. + /// + /// + /// - Extended. + /// + private void ProcessStateChange_Handler(Object sender, ProcessStateArgs e) + { + _logger.Debug($"Slot:{Slot} - Final test state: {e.State}"); + + // Nothing to display + if (e.State == ProcessState.Idle) + { + // Update progress bars + ProcessProgress_Handler(this, new ProcessProgressArgs(0, + ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps)); + ProcessProgress_Handler(this, new ProcessProgressArgs(0, + ProcessProgressArgs.ProcessProgressType.ActualProcessSteps)); + } + // Stop all procedures + if (e.State == ProcessState.Stop) + { + _timer?.Stop(); + UiElmEnable(btnStart, true); + UiElmEnable(btnStop, false); + // update progress bars + ProcessProgress_Handler(this, new ProcessProgressArgs(0, + ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps)); + ProcessProgress_Handler(this, new ProcessProgressArgs(0, + ProcessProgressArgs.ProcessProgressType.ActualProcessSteps)); + // remove user control + SetNewUserControl(null); + } + // Repeat failed tests + if (e.State == ProcessState.RepeatFailedTests) + { + UiElmEnable(btnStart, false); + UiElmEnable(btnStop, true); + _timer?.Start(); + } + // Stop time for manual operation and visual inspection as this is not part of the configuration + if (e.State == ProcessState.CheckOrderNumber) + { + _timer?.Stop(); + } + // Prepare shipping + if (e.State == ProcessState.PrepareShippingMode) + { + _timer?.Stop(); + UiElmEnable(btnStart, false); + UiElmEnable(btnStop, false); + } + // Ready for shipping + if (e.State == ProcessState.ReadyForShipping) + { + // Create extended report including all steps and each register write, read and comparison + CreateFile(Properties.Resources.StrInfoSuccessPapers); + BtnStop_Click(this, null); + } + // Print return report + if (e.State == ProcessState.PrintReturnReport) + { + PrintErrorNote(); + BtnStop_Click(this, null); + } + // Print success report + if (e.State == ProcessState.PrintSuccessReport) + { + PrintSuccessNote(); + BtnStop_Click(this, null); + } + } + + /// + /// Enable UI elements + /// + /// + /// + /// + private void UiElmEnable(UIElement elm, Boolean isEnabled, Boolean isVisible = true) + { + elm.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + elm.Visibility = isVisible ? Visibility.Visible : Visibility.Hidden; + elm.IsEnabled = isEnabled; + + })); + } + + /// + /// Fill logging window with information + /// + /// + /// + /// + /// - Initial + /// + /// + /// - Changed to rich text edit with colored output. + /// + /// + /// - Avoid output of repeated text or useless empty text. + /// + private void ProcessLog_Handler(Object sender, ProcessLogArgs e) + { + rtbLog.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + if (e != null) + { + try + { + // remove unacceptable spacing between the lines + rtbLog.Document.LineHeight = 1; + + var color = e.Color ?? Brushes.Black; + var text = e.Data; + if (!string.IsNullOrEmpty(text) && !text.Equals(_lastLoggingTextToAvoidRepetition)) + { + var tr = new TextRange(rtbLog.Document.ContentEnd, rtbLog.Document.ContentEnd) + { + Text = text + }; + tr.ApplyPropertyValue(TextElement.ForegroundProperty, color); + rtbLog.ScrollToEnd(); + rtbLog.AppendText(Environment.NewLine); + } + + _lastLoggingTextToAvoidRepetition = text; + } + catch (FormatException) + { + + } + } + } + )); + } + + /// + /// Set values for progress bar, maximum and actual for single process and overall process. + /// - Limit values to 0.0..100.0. + /// + /// + /// + /// + /// - Initial + /// + /// + /// - Introduced multi line text with center alignment. + /// + private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e) + { + if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps) + { + _maxOverallProcessSteps = e.Progress; + } + else if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps) + { + _maxSingleProcessSteps = e.Progress; + } + if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps) + { + pbTotalProgress.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + // limit output to progress bar to 0..100 as integer for process bar + var progress = _maxOverallProcessSteps <= 0 ? 0.0 : e.Progress * 100.0 / _maxOverallProcessSteps; + progress = progress > 100.0 ? 100.0 : progress < 0.0 ? 0.0 : progress; + pbTotalProgress.Value = (Int32)progress; + UpdateContentControl(lblTotalProgressValue, $@"{progress:##0.0} %"); + } + )); + } + else if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.ActualProcessSteps) + { + pbSubProgress.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + // limit output to progress bar to 0..100 as integer for process bar + var progress = _maxSingleProcessSteps <= 0 ? 0.0 : e.Progress * 100.0 / _maxSingleProcessSteps; + progress = progress > 100.0 ? 100.0 : progress < 0.0 ? 0.0 : progress; + pbSubProgress.Value = (Int32)progress; + UpdateContentControl(lblSubProgressValue, $@"{progress:##0.0} %"); + } + )); + } + } + + /// + /// Output message to control without access violation. + /// + /// + /// + /// - Initial + /// + /// + /// - Introduced multi line text with center alignment. + /// + private static void UpdateContentControl(ContentControl ctl, String text) + { + ctl.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + ctl.Content = new TextBlock() + { + Text = text, + TextWrapping = TextWrapping.Wrap, + TextAlignment = TextAlignment.Center + }; + } + )); + } + + /// + /// Status grid update for all single processes + /// + private void UpdateStatusGrid() + { + var dt = new DataTable(); + dt.Columns.Add(Properties.Resources.StrDataTableColumnProcess); + dt.Columns.Add(Properties.Resources.StrDataTableColumnState); + var i = 0; + if (_processCtrl?.Processes == null) + return; + foreach (var item in _processCtrl.Processes) + { + dt.Rows.Add(); + dt.Rows[i][0] = item.ProcessName; + dt.Rows[i][1] = ProductionProcessStateHelper.GetTextFromProductionProcessState(item.SingleProcessState); + i++; + } + + dgTotalProcess.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + dgTotalProcess.DataContext = dt; + } + )); + } + + /// + /// Dispatch new user control to main screen + /// + /// + private void SetNewUserControl(UserControl uc) + { + dgTotalProcess.Dispatcher.Invoke(DispatcherPriority.Normal, + new Action(() => + { + // remove last userControl + dpSubProcess.Children.Clear(); + if (uc != null) + { + dpSubProcess.Children.Add(uc); + DockPanel.SetDock(uc, Dock.Top); // & Dock.Left); + } + } + )); + } + + #region TimerControls + /// + /// Handle timing depending updates. + /// + /// + /// + /// - Initial + /// + private void TmrProgressUpdate_Tick(Object sender, EventArgs e) + { + SetTimeDisplay(); + } + #endregion + + /// + /// Update of status grid with information returned by user control. + /// + /// + /// + /// + /// - Initial + /// + /// + /// - Activate new user control on start of process. + /// + private void SingleProcessStateChanged_Handler(Object sender, SingleProcessStateArgs e) + { + if (sender is IProductionProcess productionProcess) + { + if (e.SingleProcessState == SingleProcessState.Running) + { + // Set new user control + SetNewUserControl(productionProcess.GetUserControl()); + } + else + { + // remove user control on idle, skipped, abort or error + SetNewUserControl(null); + } + } + + UpdateStatusGrid(); + UpdateGuiWithProductInfos(); + } + + private void OnUserAccessControlCancellation(Control control) + { + OnUserAccessWindowClosed_Handler(this, null); + } + + + /// + /// Set labels of device information and order number + /// + /// + /// - Initial + /// + /// + /// - Additional info of Cordonel. + /// + private void UpdateGuiWithProductInfos() + { + var genesisMeter = _processCtrl?.GetGenesisMeter(); + _productionRequirements = _processCtrl?.GetProductionRequirements(); + + if (genesisMeter == null) + return; + + UpdateContentControl(lblOrderNumberValue, + genesisMeter.OrderNumber != 0 ? genesisMeter.OrderNumber.ToString() : "?"); + + UpdateContentControl(lblRadioAddressValue, + genesisMeter.RadioAddress != 0 ? genesisMeter.RadioAddress.ToString() : ""); + UpdateContentControl(lblRadioAddressText, + genesisMeter.RadioAddress != 0 ? Properties.Resources.StrLblRadioAddressTxt + ":" : ""); + + UpdateContentControl(lblFwValue, + !string.IsNullOrEmpty(genesisMeter.FwVersion) ? genesisMeter.FwVersion : "?"); + + UpdateContentControl(lblMeterSizeValue, + !string.IsNullOrEmpty(genesisMeter.MeterSize) ? genesisMeter.MeterSize : "?"); + + UpdateContentControl(lblLutValue, + !string.IsNullOrEmpty(genesisMeter.LutCrc) ? genesisMeter.LutCrc : "?"); + + + UpdateContentControl(lblSerialNumberValue, + !string.IsNullOrEmpty(genesisMeter.CustomerSerialNumber) ? genesisMeter.CustomerSerialNumber : "?"); + + UpdateContentControl(lblRegionValue, + !string.IsNullOrEmpty(genesisMeter.Region) ? genesisMeter.Region : "?"); + + UpdateUi(lblPcbIdValue, !string.IsNullOrEmpty(genesisMeter.PcbId) ? genesisMeter.PcbId : "?"); + + var msg = ""; + var color = Brushes.Black; + if (_productionRequirements?.IsSpecialActive != null && (Boolean)_productionRequirements.IsSpecialActive && + _productionRequirements?.IsSpecialApproved != null && (Boolean)_productionRequirements.IsSpecialApproved) + { + msg = $"{Properties.Resources.StrLblSpecialRequirement} - Id.Ver: {_productionRequirements.SpecialId}." + + $"{_productionRequirements.SpecialVersion}"; + color = Brushes.Red; + } + else if (_productionRequirements?.IsStandardActive != null && (Boolean)_productionRequirements.IsStandardActive && + _productionRequirements?.IsStandardApproved != null && (Boolean)_productionRequirements.IsStandardApproved) + { + msg = $"{Properties.Resources.StrLblStandardRequirement} - Id.Ver: {_productionRequirements.StandardId}." + + $"{_productionRequirements.StandardVersion}"; + } + + UpdateUi(lblApprovalText, msg, color); + } + + //private void BtnSwitchAutoDetect_Click(Object sender, RoutedEventArgs e) + //{ + // //Settings.AutoDetect = !Settings.AutoDetect; + // //StoreSetting(); + // //UseSetting(); + //} + + private void OnLogout_Click(Object sender, EventArgs e) + { + Software.Logout(); + InitializeFormState(); + } + + private void OnLogin_Click(Object sender, EventArgs args) + { + ShowUserAccessWindow(true, new LoginForm(OnUserAccessControlCancellation), + Properties.Resources.StrLoginMsg); + } + + private void OnChangePassword_Click(Object _, EventArgs _1) + { + ShowUserAccessWindow(true, new ChangePassword(OnUserAccessControlCancellation), + Properties.Resources.StrChangePasswordMsg); + } + + private void CbxSlotBox_SelectionChanged(Object sender, SelectionChangedEventArgs e) + { + + } + + private void StoreSetting() + { + Settings.Update("ProductionUiConfig.json"); + } + + //private void BtnConnect_Click(Object sender, RoutedEventArgs e) + //{ + // Settings.Slot = (Int32)cbxSlotBox.SelectedValue; + // StoreSetting(); + //} + + private void BtnStart_Click(Object sender, RoutedEventArgs e) + { + UiElmEnable(btnStart, false); + UiElmEnable(btnStop, true); + + _startTime = DateTimeOffset.UtcNow; + _timer?.Start(); + + // remove last userControl + dpSubProcess.Children.Clear(); + rtbLog.Document.Blocks.Clear(); + _processCtrl?.SetProcessState(ProcessState.DetectCordonel); + } + + private void PrintErrorNote() + { + // Safe report to file + CreateFile(Properties.Resources.StrInstructionReturnPapers, true, true); + } + + private void PrintSuccessNote() + { + // Safe report to file + CreateFile(Properties.Resources.StrInfoSuccessPapers, true, true); + } + + /// + /// Logging a file with all process information, building a compressed report for printout. + /// + /// + /// - Reduced printout. + /// + private void CreateFile(String headerInfo, Boolean shortReport = false, Boolean printReport = false) + { + var sb = new StringBuilder(); + var genesisMeter = _processCtrl.GetGenesisMeter(); + + sb.AppendLine("========================================================================"); + sb.AppendLine(headerInfo); + sb.AppendLine("========================================================================"); + sb.AppendLine(_strSoftwareNameVersion); + sb.AppendLine("------------------------------------------------------------------------"); + sb.AppendLine($"{Properties.Resources.StrDateTime}: {DateTime.Now}"); + sb.AppendLine("------------------------------------------------------------------------"); + sb.AppendLine($"PcbId: {genesisMeter.PcbId}"); + sb.AppendLine($"{Properties.Resources.StrPrintOrderNumberTxt}: {genesisMeter.OrderNumber}"); + sb.AppendLine($"{Properties.Resources.StrLblSerialNumberTxt}: {genesisMeter.CustomerSerialNumber}"); + sb.AppendLine($"{Properties.Resources.StrLblOperatorNameText}: {Software.UserName}"); + sb.AppendLine("------------------------------------------------------------------------"); + + //TODO THW take the log path from setup (done with GTB) + var loggingPath = $"C:\\GenesisLog\\{DateTime.Now:yyyy-MM-dd}\\ProductionUiCordonel\\"; + var fileName = $"{loggingPath}{DateTime.Now.ToString("yyyyMMddHHmmss")}_ExtendedReport_{genesisMeter.PcbId}.txt"; + + // Copy the header to the compressed string builder + var compressedSb = new StringBuilder(); + if (shortReport) + { + compressedSb.Append(sb); + } + + var rtbText = new TextRange( + rtbLog.Document.ContentStart, + rtbLog.Document.ContentEnd + ); + sb.Append($"{rtbText.Text}"); + // Write the complete report to the file + File.WriteAllText(fileName, sb.ToString()); + + // Reduce sb content to compress it for printout + if (shortReport) + { + + fileName = $"{loggingPath}{DateTime.Now.ToString("yyyyMMddHHmmss")}" + + $"_ShortReport_{genesisMeter.PcbId}.txt"; + var sr = new StringReader(rtbText.Text); + var srLine = sr.ReadLine(); + while (srLine != null) + { + if (srLine.Contains(Properties.Resources.StrProcessStateError) || + srLine.Contains(Properties.Resources.StrProcessStateDone) || + srLine.Contains(Properties.Resources.StrProcessStateWarning) || + srLine.Contains(Properties.Resources.StrProcessStateSkipped) || + srLine.Contains(Properties.Resources.StrWarningMsgSpecialRequirementActive)) + compressedSb.AppendLine(srLine); + srLine = sr.ReadLine(); + } + + // Returns the compressed file for error report printout + File.WriteAllText(fileName, compressedSb.ToString()); + if (printReport) + Process.Start(fileName); + } + } + + /// + /// Stop button as user interaction to kill the finalization process + /// + /// + /// + private void BtnStop_Click(Object sender, RoutedEventArgs e) + { + //set the process state to the state machine + _processCtrl?.SetProcessState(ProcessState.Stop); + } + + /// + /// Open requirement in Web-Browser. + /// + /// + /// + private void LblApprovalText_Click(Object sender, System.Windows.Input.MouseButtonEventArgs e) + { + var requirementId = 0; + var requirementWebPageLink = ServiceUrls.InspectCordonelRequirementInfoServiceUrl(); + if (_productionRequirements?.SpecialId != null && + _productionRequirements?.IsSpecialApproved != null && + _productionRequirements.SpecialId != 0) + { + requirementId = (Int32)_productionRequirements.SpecialId; + requirementWebPageLink += $"SpecialRequirements#{requirementId}"; + } + else if (_productionRequirements?.StandardId != null && + _productionRequirements?.IsStandardApproved != null && + _productionRequirements.StandardId != 0) + { + requirementId = (Int32)_productionRequirements.StandardId; + requirementWebPageLink += $"StandardRequirements#{requirementId}"; + } + + if (requirementId != 0) + { + Process.Start(requirementWebPageLink); + } + } + + private void HlpMenu_Click(Object sender, RoutedEventArgs e) + { + try + { + var filename = ""; + if (Thread.CurrentThread.CurrentCulture.Name.Contains("de")) + { + filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Handbuch Cordonel Versandfertigmachen (VFM).pdf"); + } + else if (Thread.CurrentThread.CurrentCulture.Name.Contains("en")) + { + filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Manual Cordonel End of Line Test (EOL).pdf"); + } + + if (!string.IsNullOrEmpty(filename)) + { + Process.Start(filename); + } + } + catch (Exception exception) + { + Console.WriteLine(exception); + } + } + + private void Window_Loaded(Object sender, RoutedEventArgs e) + { + if (!Software.IsAutenticated) + OnLogin_Click(this, null); + + } + } +} From c9894c53bfd67c08aa5b5286984af2f911dc7bc3 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Thu, 15 May 2025 17:35:07 +0200 Subject: [PATCH 14/16] ProductionUiCordonel: - picking: status check requirements --- Common/LaaPackages | 2 +- .../ProductionUiCordonel/App.config | 2 +- .../EolProcesses/CheckProductionState.cs | 12 +- .../EolProcesses/EolProcessStatesDef.cs | 20 +- .../PickingProcesses/CheckProductionState.cs | 840 ++++++++++++++++++ .../PickingProcessStatesDef.cs | 12 +- .../ProductionUiCordonel.csproj | 1 + .../Properties/Resources.Designer.cs | 34 +- .../Properties/Resources.de.resx | 16 +- .../Properties/Resources.resx | 16 +- 10 files changed, 903 insertions(+), 52 deletions(-) create mode 100644 Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs diff --git a/Common/LaaPackages b/Common/LaaPackages index ce1dc260..e7aa3e27 160000 --- a/Common/LaaPackages +++ b/Common/LaaPackages @@ -1 +1 @@ -Subproject commit ce1dc26047374fd7a9328914a6c2bd7eb591f012 +Subproject commit e7aa3e2723a435f0a714df661dc5d86dfd45a71c diff --git a/Common/Production/ProductionUiCordonel/App.config b/Common/Production/ProductionUiCordonel/App.config index 728a665b..c940b21c 100644 --- a/Common/Production/ProductionUiCordonel/App.config +++ b/Common/Production/ProductionUiCordonel/App.config @@ -34,7 +34,7 @@ - + diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs index 563b2bb1..2bd5c005 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs @@ -727,7 +727,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr { if (EolProgress != null) EolProgress.RequirementPcbIdChecked = EOLStatus.FAIL; - feedbackMsg = Resources.StrSuccessMsgOrderBasedRequirements; + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; return EOLStatus.FAIL; } @@ -1034,14 +1034,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // Create an output message addon for alternative power consumption calculation var strAlternativeCalc = ProductionStatus.AlternativeRequiredBatteryLoadPercent == null ? "" - : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBatteryAtEol}: " + + : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBattery}: " + $"{ProductionRequirements.MaxDrainedBatteryLoadPercentShipping:F2} %" + $" ({Resources.StrMsgAlternativeDrainedBatteryCalculation})"; - var batteryLoad = $"{Resources.StrMsgDrainedBatteryAtEol}: " + + var batteryLoad = $"{Resources.StrMsgDrainedBattery}: " + // Actual calculated battery load based on readings from meter $"{ProductionStatus.DrainedBatteryLoadPercent:F2} %" + - $" - {Resources.StrMsgRequirementLimitDrainedBatteryAtEol}: " + + $" - {Resources.StrMsgRequirementLimitDrainedBattery}: " + // Required battery load from standard or special requirement $"{threshold:F2} % {strAlternativeCalc}"; @@ -1109,11 +1109,11 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr } // Build message for required versus calculated lifetime - var lifeTime = $"{Resources.StrMsgRemainingLifeTimeAtEol}: " + + var lifeTime = $"{Resources.StrMsgRemainingLifeTime}: " + // Actual calculated lifetime based on readings from meter $"{ProductionStatus.RemainingLifeTimeYears:F2} " + $"{Resources.StrMsgYears}" + - $" - {Resources.StrMsgRequiredLifeTimeAtEol}: " + + $" - {Resources.StrMsgRequiredLifeTime}: " + // Required lifetime from special or standard requirement $"{ProductionRequirements.RequiredLifeTimeYearsShipping:F2} " + $"{Resources.StrMsgYears}"; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs index c7b779db..066095b3 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs @@ -151,19 +151,19 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // This scans the order number, it has to be executed exclusively with automatic new ProcessStateStruct( - new CheckOrderNumber(Resources.StrStateCheckOrderNumber), - ProcessState.CheckOrderNumber, - Resources.StrStateCheckOrderNumber, - 11, - ProcessState.CheckSerialNumber), + productionProcess: new CheckOrderNumber(Resources.StrStateCheckOrderNumber), + processState: ProcessState.CheckOrderNumber, + processInfo: Resources.StrStateCheckOrderNumber, + productionProcessNo: 11, + nextStateOnSuccess: ProcessState.CheckSerialNumber), // This scans the serial number, it has to be executed exclusively with automatic new ProcessStateStruct( - new CheckSerialNumber( Resources.StrStateCheckSerialNumber), - ProcessState.CheckSerialNumber, - Resources.StrStateCheckSerialNumber, - 12, - ProcessState.CheckAttachments), + productionProcess: new CheckSerialNumber( Resources.StrStateCheckSerialNumber), + processState: ProcessState.CheckSerialNumber, + processInfo: Resources.StrStateCheckSerialNumber, + productionProcessNo: 12, + nextStateOnSuccess: ProcessState.CheckAttachments), #endregion -------------------------------- GENERIC-STATES -------------------------------------------- diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs new file mode 100644 index 00000000..1da6479e --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Logic.ProductionToProductMapper.Files.Fw; +using Xylem.Common.CommonCore.Consts; +using Xylem.Common.Hardware.WaterMeter.Genesis.Applications; +using Xylem.Common.Hardware.WaterMeter.Genesis.Applications.Const; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; +using Xylem.Common.Logic.ProductionOrderCore.OrderData; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.PickingProcesses +{ + /// + /// Check all parameters needed as preparation for assembly. + /// + public class CheckProductionState : BaseProductionProcess + { + /// + /// Maximum of expected processing steps for this process to feed the GUI progress bar. + /// + private const Int32 MaxActualProgressSteps = 13; + + /// + /// FW update package containing the ABC and the ADF. + /// + public List FwUpdatePackage + { + get; set; + } + + /// + /// Ctor + /// + public CheckProductionState(String name) : base(name) + { + } + + /// + /// Start database calls and meter readouts here for better testability and ability to fill the + /// objects manually by the test routine. + /// + /// + /// - Initial. + /// + public override StatusReturn PreExecuteProcess() + { + if (Meter == null || string.IsNullOrEmpty(Meter.PcbId) || Meter.PcbId.Length < GenesisMeter.MinPcbIdLength) + { + Meter?.WriteLog("PcbId requirements do not match"); + return StatusReturn.Failed; + } + + // Some statistics for GUI and internal processing + SignalRunningState(MaxActualProgressSteps); + + // Get the requirement (Standard or special) + if (!GenesisStatusHandler.GetCordonelRequirementInfoFromDb(Meter.PcbId, ProductionRequirements, + out var error) || ProductionRequirements == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgProductionRequirements); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Get clustered FileId of firmware package for production or on special requirement release candidates + var isSpecialActive = ProductionRequirements.IsSpecialActive != null && + (Boolean)ProductionRequirements.IsSpecialActive && + ProductionRequirements.IsSpecialApproved != null && + (Boolean)ProductionRequirements.IsSpecialApproved; + + if (string.IsNullOrEmpty(ProductionRequirements.FwVersion) || + !MeterFwUpdate.SearchProdFwInDb(ProductionRequirements.FwVersion, out var parentFileId, out error, + !isSpecialActive) || parentFileId == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLoadingFwFromDb); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Get the real content of the FW package including all binaries of each app and the application description file + FwUpdatePackage = new List(); + if (!MeterFwUpdate.GetFwPackageFromDb((Int32)parentFileId, FwUpdatePackage, out error) || + FwUpdatePackage == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLoadingFwFromDb); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Read out the Cordonel and calculate remaining lifetime and fill the ProductionStatus + if (!GenesisStatusHandler.BuildLifeTimeInformation(Meter, ProductionStatus, out error)) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLifeTimeCalculation); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + return StatusReturn.Okay; + } + + /// + /// Check all parameters needed as preparation for completion to validate the status and the + /// remaining operational time. This includes special permissions for individual parameters. + /// The picking requirements including the special permissions are taken from the DB and can be + /// assigned to an order number (meaning for all devices produced under this order) or an individual + /// pcbId. The setup and calculation of the exceeded time and drained will be taken from the Cordonel + /// (read from registers in POWERMON). + /// Precondition: - The process has to + /// be completed as the PcbId and the order number is needed. + /// Checks: + /// - Requirements are collected, + /// - PcbId, + /// - Region, + /// - FW, + /// - LUT CRC, + /// - Meter size + /// - Drained battery load versus required remaining battery load in percent, + /// - Remaining run time in years, + /// + /// + /// + /// - Initial. + /// + public override StatusReturn ExecuteProcess() + { + #region ----------------------------------- Check Requirements -------------------------------------------- + + var statusReturn = CheckRequirements(out var feedbackMsg); + if (StatusReturn.Failed == statusReturn) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Production Order ---------------------------------------- + + // Production order + if (StatusReturn.Failed == CheckProductionOrderNumber(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check PCBID --------------------------------------------------- + // PCBID read from meter compared with requirement PcbIds + statusReturn = CheckPcbId(out feedbackMsg); + + // Exit here with error + if (StatusReturn.Failed == statusReturn) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Region -------------------------------------------------- + + if (StatusReturn.Failed == CheckRegion(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check LUT CRC ------------------------------------------------- + + if (string.IsNullOrEmpty(CheckLutCrc(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Meter Size ---------------------------------------------- + + if (string.IsNullOrEmpty(CheckMeterSize(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Firmware and Apps --------------------------------------- + + if (string.IsNullOrEmpty(CheckFwAndApps(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Remaining Battery Load ---------------------------------- + + var erroneousSequenceStep = false; + if (StatusReturn.Failed == CheckRemainingBatteryLoad(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Remaining Life Time ------------------------------------- + + if (StatusReturn.Failed == CheckRemainingLifetime(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Storage Months ------------------------------------------ + + if (StatusReturn.Failed == CheckStorageMonths(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Summarize Check Results --------------------------------------- + + // One or more checks failed + if (erroneousSequenceStep) + return StatusReturn.Failed; + + // Here everything went fine + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + + // All test successfully passed reaching this line + return StatusReturn.Okay; + #endregion -------------------------------- Summarize Check Results --------------------------------------- + } + + /// + /// Common routine to exit with error and user window with error message + /// + /// + /// + /// - Extracted from for better testability. + /// + private StatusReturn ExitWithErrorAndUserWindow(String errorMsg) + { + ErrorMsgDispatcher(errorMsg); + MessageBox.Show(errorMsg, Resources.StrProcessStateError, MessageBoxButton.OK, MessageBoxImage.Error); + return StatusReturn.Failed; + } + + /// + /// Check for requirement permission special or standard or none: + /// - If the special requirement is active all fields a filled with the according values from this "special" + /// requirement, + /// - Else those fields contain the "standard" requirements! + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_001]_Check_Requirements + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRequirements(out String feedbackMsg) + { + feedbackMsg = ""; + if (ProductionRequirements == null) + { + feedbackMsg = Resources.StrErrorMsgProductionRequirements; + return StatusReturn.Failed; + } + + // Check if requirement is active (free for production) and approved by a second person. Priority special + // requirement. + var isSpecialActive = false; + var isStandardActive = false; + if (ProductionRequirements.IsSpecialActive != null && (Boolean)ProductionRequirements.IsSpecialActive && + ProductionRequirements.IsSpecialApproved != null && (Boolean)ProductionRequirements.IsSpecialApproved) + isSpecialActive = true; + if (ProductionRequirements.IsStandardActive != null && (Boolean)ProductionRequirements.IsStandardActive && + ProductionRequirements.IsStandardApproved != null && (Boolean)ProductionRequirements.IsStandardApproved) + isStandardActive = true; + + // At least one requirement needs to be active and a requirement number and version has to be set + if ((!isStandardActive && !isSpecialActive) || + // The standard Id and version have always to be set even if the special is active + ProductionRequirements.StandardId == null || ProductionRequirements.StandardVersion == null || + // If the special is active the version and Id have to be defined + (isSpecialActive && (ProductionRequirements.SpecialId == null || + ProductionRequirements.SpecialVersion == null))) + { + feedbackMsg = Resources.StrErrorMsgRequirementInactive; + return StatusReturn.Failed; + } + + feedbackMsg = isSpecialActive && + ProductionRequirements.SpecialId != null && + ProductionRequirements.SpecialVersion != null ? + Resources.StrWarningMsgSpecialRequirementActive + + $" Id.Ver: {ProductionRequirements.SpecialId}.{ProductionRequirements.SpecialVersion}" : + Resources.StrSuccessMsgRequirementActive + + $" Id.Ver {ProductionRequirements.StandardId}.{ProductionRequirements.StandardVersion}"; + + + return StatusReturn.Okay; + } + + /// + /// Check production order number between meter and requirements. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_002]_Check_ProductionOrderNo + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckProductionOrderNumber(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + ProductionRequirements.ProductionOrderNr != Meter.OrderNumber) + { + feedbackMsg = Resources.StrErrorMsgRequirementProductionOrder; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementProductionOrder} - " + + $"{Resources.StrLblOrderNumberTxt}: {Meter.OrderNumber}"; + return StatusReturn.Okay; + } + + /// + /// Check PcbId of meter and requirements. Mark PcbId as skipped if + /// production requirements PcbId is null as this is the indication that all Cordonels + /// assigned to this production order are produced according to this requirement. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_003]_Check_PcbId + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckPcbId(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + Meter.PcbId == null || + Meter.PcbId.Length < GenesisMeter.MinPcbIdLength) + { + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; + return StatusReturn.Failed; + } + + if (ProductionRequirements.PcbId == null) + { + feedbackMsg = Resources.StrSuccessMsgOrderBasedRequirements; + return StatusReturn.Okay; + } + if (Meter.PcbId.Equals(ProductionRequirements.PcbId.ToString())) + { + feedbackMsg = $"{Resources.StrSuccessMsgRequirementPcbId} - PcbId: {Meter.PcbId}"; + return StatusReturn.Okay; + } + + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; + return StatusReturn.Failed; + } + + /// + /// Check region between meter and requirements + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_004]_Check_Region + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRegion(out String feedbackMsg) + { + feedbackMsg = ""; + if (ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.Region) || + string.IsNullOrEmpty(Meter.Region) || + !Meter.Region.Equals(ProductionRequirements.Region)) + { + feedbackMsg = Resources.StrErrorMsgRequirementRegion; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementRegion} - Region: {Meter.Region}"; + return StatusReturn.Okay; + } + + /// + /// Check Look-Up-Table (LUT) for metrology calibration values for the checksum (CRC) of this table + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_005]_Check_LUT_CRC + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckLutCrc(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.LutCrc) || + string.IsNullOrEmpty(Meter.LutCrc) || + !Meter.LutCrc.Equals(ProductionRequirements.LutCrc)) + { + feedbackMsg = Resources.StrErrorMsgRequirementLut; + return ""; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementLut} - LUT CRC: {Meter.LutCrc}"; + + return Meter.LutCrc; + } + + /// + /// Check meter size + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_006]_Check_Meter_Size + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckMeterSize(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || string.IsNullOrEmpty(ProductionRequirements.MeterSize) || + string.IsNullOrEmpty(Meter.MeterSize) || + MeterSizeConverter.ConvertMeterSizeNameToEnum(Meter.MeterSize) != + MeterSizeConverter.ConvertMeterSizeNameToEnum(ProductionRequirements.MeterSize)) + { + feedbackMsg = Resources.StrErrorMsgRequirementMeterSize; + return ""; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementMeterSize} - " + + $"{Resources.StrLblMeterSizeTxt}: {Meter.MeterSize}"; + + return ProductionRequirements.MeterSize; + } + + /// + /// Check the installed firmware (FW) and all individual applications (Apps) installed to the meter: + /// - Take the FLEXNETVERSION as base information which FW is installed in the Cordonel and compare this with + /// the production-requirement, + /// - The downloaded FW package containing all Apps as application-binary-container (ABC) will be compared with + /// the reported Apps by the Cordonel for their version and CRC using the " class, + /// - Crosscheck if the application-description-file (ADF) states all Apps which are installed in the Cordonel, + /// - Check the Core-Revision of the Cordonel against the ADF to guarantee that this FW is supported by the + /// Cordonel operational system (Breeze). + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_007]_Check_FW_and_Apps + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckFwAndApps(out String feedbackMsg) + { + feedbackMsg = ""; + // Check installed FW version with FLEXNET information + var fwUpdate = new MeterFwUpdate(Meter); + if (Meter == null || + ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.FwVersion) || + string.IsNullOrEmpty(Meter.FwVersion) || + !Meter.FwVersion.Equals(ProductionRequirements.FwVersion) || + // Check if FW package has been loaded and contains at least the minimal required files + FwUpdatePackage == null || + FwUpdatePackage.Count < MeterFwUpdate.MinAppsRequiredForOperation) + { + feedbackMsg += $"{Resources.StrErrorMsgRequirementFw}\n"; + return ""; + } + + var fileApps = new List(); + if (FwUpdatePackage != null) + { + foreach (var file in FwUpdatePackage) + { + // load the ABC (application binary container) + if (file.FileName.Contains("binfile") && + file.FileName.EndsWith(".bin") && + // Sometimes the * complete *.bin is part of the binaries containing all app in one file. + // As this is not part of the ADF it has to be skipped from loading!!!! + !file.FileName.Contains("complete")) + { + var fileApplication = new FileApplications(file.FileName) + { + BinData = new List(file.FileContent.ToList()) + }; + fileApps.Add(fileApplication); + } + // Load the ADF (application description file, containing the information + // of each application) + else if (file.FileName.Contains("product") && file.FileName.EndsWith(".txt")) + { + var enc = new System.Text.ASCIIEncoding(); + var adf = enc.GetString(file.FileContent.ToArray()); + fwUpdate.LoadPackageFileFromText(adf); + } + } + + fwUpdate.LoadFileApps(fileApps); + } + + var checkSucceeded = true; + // Compare every installed application for version and CRC with the required to be defined by the downloaded + // ABC (application binary container) + fwUpdate.CompareAllMeterAndFileApps(); + + if (// Validate package description file with required Apps (fileApps) and build the CORE REVISIONS MIN MAX + !fwUpdate.ValidateFileAppsWithPackageFile() || + // Check if any meter Apps is outdated or does contain an un-matching CRC + Meter.MeterAppListVersion.Any(app => app.Status != MeterAppState.MeterAppUpToDate && + app.Status != MeterAppState.MeterAppNotRequired)) + { + feedbackMsg += $"{Resources.StrErrorMsgAppVersionOrCrc}\n"; + checkSucceeded = false; + // Do not immediately return to summarize all errors following up + } + + // The package description file has to be parsed in advance using the fwUpdate.ValidateFileAppsWithPackageFile()!! + if (!fwUpdate.CheckCoreRevision(Meter.CoreRevision)) + { + feedbackMsg += $"{Resources.StrErrorMsgCoreRevision}\n"; + // Do not immediately return to summarize all errors following up + checkSucceeded = false; + } + + if (!checkSucceeded) + { + feedbackMsg += $"{Resources.StrErrorMsgRequirementFw}\n"; + return ""; + } + feedbackMsg = $"{Resources.StrSuccessMsgRequirementFw} - Firmware: {Meter.FwVersion}"; + return Meter.FwVersion; + } + + /// + /// Check the remaining battery load based on the thresholds which can be either: + /// - The AlternativeRequiredBatteryLoadPercent if the QuiescentCurrent_uA is set to a value greater than 50 uA + /// and the EstimatedProductionBatteryLoadPercent is greater than 0.0 % + /// and + /// - the MaxDrainedBatteryLoadPercentAssembly limits the threshold, + /// or only + /// - the MaxDrainedBatteryLoadPercentAssembly limits the threshold as absolut limit. + /// + /// ATTENTION: The MaxDrainedBatteryLoadPercentAssembly forces the absolut limit even if the alternative + /// calculation is being used. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_008]_Check_Remaining_Battery_Load + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRemainingBatteryLoad(out String feedbackMsg) + { + if (Meter == null || + ProductionRequirements?.MaxDrainedBatteryLoadPercentAssembly == null || + ProductionStatus == null) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // If the values for calculation of drained battery load and exceeded lifetime are not accessible + if (ProductionStatus.DrainedBatteryLoad_uAs == 0 || ProductionStatus.ExceededLifeTime_s == 0) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Set up the alternative calculation values needed for given current and supplement for production. + // These values will be set by the requirement and have a higher priority than the standard calculation + // based on the drained battery load and the estimated prediction based on this and the time of operation. + if (ProductionRequirements.QuiescentCurrent_uA > GenesisMeter.QuiescentCurrent_uA && + ProductionRequirements.EstimatedProductionBatteryLoadPercent > 0) + { + ProductionStatus.QuiescentCurrent_uA = ProductionRequirements.QuiescentCurrent_uA; + ProductionStatus.EstimatedProductionBatteryLoadPercent = + ProductionRequirements.EstimatedProductionBatteryLoadPercent; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus)) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Check battery load in percent and compare with requirements for limits for drained battery load + // For the alternative calculation based quiescent current and production supplement this will be used. + // Build message for required versus calculated lifetime + var threshold = ProductionStatus.AlternativeRequiredBatteryLoadPercent ?? + ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly; + + // Create an output message addon for alternative power consumption calculation + var strAlternativeCalc = ProductionStatus.AlternativeRequiredBatteryLoadPercent == null + ? "" + : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBattery}: " + + $"{ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly:F2} %" + + $" ({Resources.StrMsgAlternativeDrainedBatteryCalculation})"; + + var batteryLoad = $"{Resources.StrMsgDrainedBattery}: " + + // Actual calculated battery load based on readings from meter + $"{ProductionStatus.DrainedBatteryLoadPercent:F2} %" + + $" - {Resources.StrMsgRequirementLimitDrainedBattery}: " + + // Required battery load from standard or special requirement + $"{threshold:F2} % {strAlternativeCalc}"; + + + // This is the absolut limit even if the "AlternativeRequiredBatteryLoadPercent" will be used + if (ProductionStatus.DrainedBatteryLoadPercent > + ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {batteryLoad}"; + return StatusReturn.Failed; + } + + // This is the limit given by the "AlternativeRequiredBatteryLoadPercent" + if (ProductionStatus.AlternativeRequiredBatteryLoadPercent != null && + ProductionStatus.DrainedBatteryLoadPercent > ProductionStatus.AlternativeRequiredBatteryLoadPercent) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {batteryLoad}"; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsg}: {batteryLoad}"; + + return StatusReturn.Okay; + } + + /// + /// Check the remaining lifetime + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_009]_Check_Remaining_Lifetime + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRemainingLifetime(out String feedbackMsg) + { + if (Meter == null || + ProductionRequirements?.RequiredLifeTimeYearsAssembly == null || + ProductionRequirements.RequiredLifeTimeYearsAssembly <= 0 || + ProductionStatus == null) + { + feedbackMsg = Resources.StrErrorMsgLifeTimeCalculation; + return StatusReturn.Failed; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus)) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Build message for required versus calculated lifetime + var lifeTime = $"{Resources.StrMsgRemainingLifeTime}: " + + // Actual calculated lifetime based on readings from meter + $"{ProductionStatus.RemainingLifeTimeYears:F2} " + + $"{Resources.StrMsgYears}" + + $" - {Resources.StrMsgRequiredLifeTime}: " + + // Required lifetime from special or standard requirement + $"{ProductionRequirements.RequiredLifeTimeYearsAssembly:F2} " + + $"{Resources.StrMsgYears}"; + + // Check required years for assembly + if (ProductionRequirements.RequiredLifeTimeYearsAssembly > ProductionStatus.RemainingLifeTimeYears) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {lifeTime}"; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsg}: {lifeTime}"; + return StatusReturn.Okay; + } + + /// + /// Check the months between first battery activation and now + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_010]_Check_Storage_Months + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckStorageMonths(out String feedbackMsg) + { + // Based on first attached battery and the POWEMON_TotalUsedSeconds (Kitron production date) and actual date + if (Meter == null || + ProductionRequirements?.MaxStorageMonths == null || + ProductionRequirements.MaxStorageMonths < 0.0 || + ProductionStatus == null || + ProductionStatus.DrainedBatteryLoad_uAs == 0 || + ProductionStatus.ExceededLifeTime_s == 0) + { + // Storage months requirement or Kitron production date unknown + feedbackMsg = Resources.StrErrorMsgRequirementMaximumStorageMonthUnknown; + return StatusReturn.Failed; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus) || ProductionStatus.BatteryAssemblyDateTimeUtc == null) + { + feedbackMsg = Resources.StrErrorMsgRequirementMaximumStorageMonthUnknown; + return StatusReturn.Failed; + } + + var actualDate = DateTime.UtcNow.Date; + var batteryAssemblyDate = ProductionStatus.BatteryAssemblyDateTimeUtc.Value.Date; + // Give one day more as rounding issue + var expirationDate = batteryAssemblyDate.AddDays((Double)ProductionRequirements.MaxStorageMonths * GenesisStatusHandler.DAYS_PER_MONTH + 1.0); + var strStorageProduction = $"{Resources.StrLblKitronProductionDate}: " + + $"{ProductionStatus.BatteryAssemblyDateTimeUtc.Value:dd-MMM-yyyy} - " + + $"{Resources.StrLblExpirationDate}: {expirationDate:dd-MMM-yyyy} - " + + $"{Resources.StrLblMaxStorageMonths}: " + + $"{(Int32)ProductionRequirements.MaxStorageMonths} {Resources.StrLblMonths}"; + + if (actualDate >= expirationDate) + { + // Storage months exceeded + feedbackMsg = $"{Resources.StrErrorMsgRequirementMaximumStorageMonth} - " + strStorageProduction; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementMaximumStorageMonth} - " + strStorageProduction; + return StatusReturn.Okay; + } + } +} + diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs index 121408c7..d0adec0d 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs @@ -153,11 +153,21 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Picki processState: ProcessState.CheckOrderNumber, processInfo: Resources.StrStateCheckOrderNumber, productionProcessNo:3, - nextStateOnSuccess: ProcessState.Idle), + nextStateOnSuccess: ProcessState.CheckProductionState), #endregion -------------------------------- GENERIC-STATES -------------------------------------------- #region ----------------------------------- PICKING-STATES -------------------------------------------- + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + new ProcessStateStruct( + productionProcess: new CheckProductionState(Resources.StrStateCheckProductionState), + processState: ProcessState.CheckProductionState, + processInfo: Resources.StrStateCheckProductionState, + productionProcessNo: 4, + nextStateOnSuccess: ProcessState.Idle), + //TODO Roland: implement your states here #endregion -------------------------------- PICKING-STATES -------------------------------------------- diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 4565ffaf..aacaff13 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -299,6 +299,7 @@ + diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs index 6c2b205e..128dfc26 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -864,18 +864,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to ERROR: Maximum drained battery load overflow. /// - internal static string StrErrorMsgThresholdDrainedBatteryAtEol { + internal static string StrErrorMsgThresholdDrainedBattery { get { - return ResourceManager.GetString("StrErrorMsgThresholdDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrErrorMsgThresholdDrainedBattery", resourceCulture); } } /// /// Looks up a localized string similar to ERROR: Minimum required lifetime underflow. /// - internal static string StrErrorMsgThresholdRemainingLifeTimeAtEol { + internal static string StrErrorMsgThresholdRemainingLifeTime { get { - return ResourceManager.GetString("StrErrorMsgThresholdRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrErrorMsgThresholdRemainingLifeTime", resourceCulture); } } @@ -1269,9 +1269,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Drained battery load. /// - internal static string StrMsgDrainedBatteryAtEol { + internal static string StrMsgDrainedBattery { get { - return ResourceManager.GetString("StrMsgDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgDrainedBattery", resourceCulture); } } @@ -1341,36 +1341,36 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Estimated remaining lifetime. /// - internal static string StrMsgRemainingLifeTimeAtEol { + internal static string StrMsgRemainingLifeTime { get { - return ResourceManager.GetString("StrMsgRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRemainingLifeTime", resourceCulture); } } /// /// Looks up a localized string similar to Minimum required lifetime. /// - internal static string StrMsgRequiredLifeTimeAtEol { + internal static string StrMsgRequiredLifeTime { get { - return ResourceManager.GetString("StrMsgRequiredLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequiredLifeTime", resourceCulture); } } /// /// Looks up a localized string similar to Absolut restriction. /// - internal static string StrMsgRequirementAbsolutLimitDrainedBatteryAtEol { + internal static string StrMsgRequirementAbsolutLimitDrainedBattery { get { - return ResourceManager.GetString("StrMsgRequirementAbsolutLimitDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequirementAbsolutLimitDrainedBattery", resourceCulture); } } /// /// Looks up a localized string similar to Maximum allowed. /// - internal static string StrMsgRequirementLimitDrainedBatteryAtEol { + internal static string StrMsgRequirementLimitDrainedBattery { get { - return ResourceManager.GetString("StrMsgRequirementLimitDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequirementLimitDrainedBattery", resourceCulture); } } @@ -2061,9 +2061,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Success: Minimum required lifetime is in range. /// - internal static string StrSuccessMsgThresholdRemainingLifeTimeAtEol { + internal static string StrSuccessMsgThresholdRemainingLifeTime { get { - return ResourceManager.GetString("StrSuccessMsgThresholdRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrSuccessMsgThresholdRemainingLifeTime", resourceCulture); } } diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx index 6ed0cd2f..f369a09e 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx @@ -700,34 +700,34 @@ Erfolg: Datenbank und Cordonel Funkschlüssel erfolgreich abgeglichen - + Verbrauchte Batterieladung Berechnung basierend auf Ruhestrom und Produktionszuschlag - + FEHLER: Maximale erlaubte Batterieentladung überschritten - + Absoluter Grenzwert - + Maximal erlaubt - + Abgeschätzte Restlaufzeit - + FEHLER: Minimale geforderte Restlaufzeit unterschritten FEHLER: Batterieentladung konnte nicht berechnet werden - + Erfolg: Minimale Restlaufzeit eingehalten - + Minimal geforderte Restlaufzeit diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.resx index 2810476c..2a3d91c6 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.resx @@ -700,34 +700,34 @@ Success: Database with Cordonel radio encryption key successfully crosschecked - + Drained battery load Calculation based on quiescent current and production supplement - + ERROR: Maximum drained battery load overflow - + Absolut restriction - + Maximum allowed - + Estimated remaining lifetime - + ERROR: Minimum required lifetime underflow ERROR: Failed to calculate battery load - + Success: Minimum required lifetime is in range - + Minimum required lifetime From 15cd3be1123a301e8ccf811e430ee29d956a1459 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Mon, 26 May 2025 11:15:31 +0200 Subject: [PATCH 15/16] removed *.bak files --- .../GenesisCore/GenesisConfigReader.cs.bak | 241 -- .../Registers/RegisterConverter.cs.bak | 555 ----- .../MagFluxConfig/MagFluxConfig.csproj.bak | 71 - .../MagFluxCore/MagFluxCore.csproj.bak | 140 -- .../MagFlux/MagFluxCore/MagFluxMeter.cs.bak | 1408 ------------ .../RequestProtocol/RequestProtocol.cs.bak | 673 ------ .../RequestProtocol.csproj.bak | 83 - .../StreamingProtocol.csproj.bak | 83 - .../WaterMeter/WaterMeterCore/IMeter.cs.bak | 287 --- Common/Ui/GenesisToolBox/FrmFwUpdate.cs.bak | 1932 ----------------- 10 files changed, 5473 deletions(-) delete mode 100644 Common/Hardware/WaterMeter/Genesis/GenesisCore/GenesisConfigReader.cs.bak delete mode 100644 Common/Hardware/WaterMeter/Genesis/Registers/RegisterConverter.cs.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/MagFluxConfig/MagFluxConfig.csproj.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxCore.csproj.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxMeter.cs.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.cs.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.csproj.bak delete mode 100644 Common/Hardware/WaterMeter/MagFlux/Protocols/StreamingProtocol/StreamingProtocol.csproj.bak delete mode 100644 Common/Hardware/WaterMeter/WaterMeterCore/IMeter.cs.bak delete mode 100644 Common/Ui/GenesisToolBox/FrmFwUpdate.cs.bak diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisCore/GenesisConfigReader.cs.bak b/Common/Hardware/WaterMeter/Genesis/GenesisCore/GenesisConfigReader.cs.bak deleted file mode 100644 index 518c6571..00000000 --- a/Common/Hardware/WaterMeter/Genesis/GenesisCore/GenesisConfigReader.cs.bak +++ /dev/null @@ -1,241 +0,0 @@ -namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore -{ - using Applications; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - using Registers; - using Registers.DataTypes; - using Registers.Json; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WaterMeterRegisters; - - /// - /// JSON filer reader for generation of register lists - /// - public class GenesisConfigurationReader - { - /// - /// Registers defined in configuration.json file - /// - public List ConfigRegistersDefinitions { get; } - = new List(); - - /// - /// Applications defined in configuration.json file - /// - public List ConfigApplicationDefinitions { get; } - = new List(); - - /// - /// Ctor - /// - public GenesisConfigurationReader() { } - - /// - /// Ctor - /// - /// the file path for configuration.json as interface description to the meter - public GenesisConfigurationReader(String configFilePath) - { - if (string.IsNullOrEmpty(configFilePath)) - { - throw new ApplicationException("JsonFileRegisterReader needs at least one file path"); - } - if (!File.Exists(configFilePath)) - { - throw new ApplicationException($" {configFilePath} doesn't exists"); - } - - BuildRegisterList(File.ReadAllText(configFilePath)); - } - - /// - /// Convert file content to register list - /// - /// - /// - /// - public void BuildRegisterList(String configurationJson) - { - IDictionary sections; - - try - { - sections = JsonConvert - .DeserializeObject>(configurationJson, SerializerSettings) - ?? new Dictionary(); - } - catch (Exception) - { - sections = new Dictionary(); - } - - foreach (var sectionKVP in sections) - { - var appId = sectionKVP.Value.Id; - var appName = sectionKVP.Key; - - ConfigApplicationDefinitions.Add(new ApplicationDefinition - { - AppId = appId, - AppName = appName - }); - - var section = sectionKVP.Value; - - var registers = section.Registers; - - if (registers is null) - { - continue; - } - - foreach (var registerKVP in registers) - { - var register = registerKVP.Value; - - if (register is null) - { - continue; - } - - var details = register.Details; - - if (details is null) - { - continue; - } - - var regId = register.Id; - - foreach (var detail in details) - { - var values = detail.Values; - - ConfigRegistersDefinitions.Add(new RegisterDefinition - { - AppAddress = appId, - AppName = appName, - DataType = GetType(detail.Type), - Default = values?.Default is Int64 d ? d : default(Int64?), - IsAvailable = default(Boolean), - Maximum = values?.Maximum is Int64 max ? max : default(Int64?), - Minimum = values?.Minimum is Int64 min ? min : default(Int64?), - RegAddressInApp = regId, - // RegisterAddress = new byte[] { appId, regId }, - RegisterDetail = detail, - RegisterName = registerKVP.Key, - RestoreCapability = new StaticType(detail.StaticType) - }); - } - } - } - } - - /// - /// Settings - /// - private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - DateParseHandling = DateParseHandling.None, - Converters = - { - AccessConverter.Singleton, - new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } - }, - }; - - private static Type GetType(String value) - { - try - { - switch (value.ToLower()) - { - case "bool_t": return typeof(Boolean); - case "rpc": return typeof(Rpc); - case "string": return typeof(String); - case "uint32_t": return typeof(UInt32); - case "time_t": return typeof(TimeT); - case "status_t": return typeof(StatusT); - case "uint16_t": return typeof(UInt16); - case "uint8_t": return typeof(Byte); - case "enum8": return typeof(Enum8); - case "uint64_t": return typeof(UInt64); - case "uint96_t": return typeof(UInt96); - case "uint128_t": return typeof(UInt128); - case "int8_t": return typeof(SByte); - case "int32_t": return typeof(Int32); - case "int64_t": return typeof(Int64); - case "int16_t": return typeof(Int16); - default: throw new Exception($"Type {value} not recognized!"); - } - } - catch (Exception ex) - { - //throw ex; - return typeof(Boolean); - } - - } - } - - /// - /// - /// - public class AccessConverter : JsonConverter - { - /// - /// - /// - /// - /// - public override Boolean CanConvert(Type t) => t == typeof(Access) || t == typeof(Access?); - - /// - /// - /// - /// - /// - /// - /// - /// - public override Object ReadJson(JsonReader reader, Type t, Object existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - - var value = serializer.Deserialize(reader); - - return Enum.TryParse(value, true, out Access access) ? access : default(Access); - } - - /// - /// - /// - /// - /// - /// - public override void WriteJson(JsonWriter writer, Object untypedValue, JsonSerializer serializer) - { - if (untypedValue is Access access) - { - writer.WriteValue(access.ToString()); - } - else - { - throw new Exception("Cannot marshal type Lvl"); - } - } - - /// - /// - /// - public static readonly AccessConverter Singleton = new AccessConverter(); - } -} diff --git a/Common/Hardware/WaterMeter/Genesis/Registers/RegisterConverter.cs.bak b/Common/Hardware/WaterMeter/Genesis/Registers/RegisterConverter.cs.bak deleted file mode 100644 index 76e866ef..00000000 --- a/Common/Hardware/WaterMeter/Genesis/Registers/RegisterConverter.cs.bak +++ /dev/null @@ -1,555 +0,0 @@ -using System; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; - -namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers -{ - /// - /// Helper to convert genesis register values store in bytes to data types and back - /// - public static class RegisterConverter - { - /// - /// Convert single register from Genesis and return a message of the result. The value input is a byte array - /// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character - /// at index 0, this does not need to be reversed as it is readable by default. - /// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost. - /// The message contains the register name, the swapped raw value and on the converted value. - /// - /// - /// the raw byte array is always % 4 0 padded for unused elements, the LSB is - /// on index 0 (little endian, LSB first) - /// message with register name, swapped raw value and converted value - /// - /// - Initial. - /// - /// - /// - Reworked. - /// - /// - /// - Avoid logging of encryption key. - /// - /// - /// - Hash password and encryption key with SHA256. - /// - public static String GetRegisterContentText(RegisterDefinition registerDefinition, Byte[] regRawByteArray) - { - String msg; - String registerName = "?"; - try - { - registerName = registerDefinition.GetIdent(); - // getting a result value as text of the data type e.g. UInt32 - var strValue = ConvertToText(regRawByteArray, registerDefinition.DataType); - // remove non-printable char of the converted result - var strValueResult = Regex.Replace(strValue, @"\p{C}+", string.Empty); - - // use hashed output being able to compare written and read back values - String strRawResult; - if (registerName.Contains("EncryptionKey") || registerName.Contains("Password")) - { - strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray, encryptData:true); - msg = $"{registerName}: ({strRawResult})h"; - } - else - { - strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray); - msg = $"{registerName}: ({strRawResult})h ({strValueResult})"; - } - } - catch (Exception) - { - throw new ApplicationException($"Cannot convert register {registerName}"); - } - - return msg; - } - - /// - /// Convert single register from Genesis and return a message of the result. The value input is a byte array - /// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character - /// at index 0, this does not need to be reversed as it is readable by default. - /// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost. - /// The message contains the swapped raw value. - /// - /// - /// the raw byte array is always % 4 0 padded for unused elements, the LSB is - /// on index 0 (little endian, LSB first) - /// data encryption for data required SHA256 hash instead of clear text - /// message with swapped raw value - /// - /// - Initial. - /// - /// - /// - Hash password and encryption key with SHA256. - /// - public static String GetRegisterRawText(RegisterDefinition registerDefinition, Byte[] regRawByteArray, - Boolean encryptData = false) - { - String msg; - var strRawResult = "?"; - try - { - // inverse the raw value to get a readable byte order MSB left and LSB last right position - var tmpList = regRawByteArray.ToList(); - - // reverse the list if it is not a string or the UInt48, UInt72, UInt88,UInt96 or UInt128 which - // will be used as placeholder for a string - if (registerDefinition.DataType != typeof(String) && - registerDefinition.DataType != typeof(UInt48) && - registerDefinition.DataType != typeof(UInt72) && - registerDefinition.DataType != typeof(UInt88) && - registerDefinition.DataType != typeof(UInt96) && - registerDefinition.DataType != typeof(UInt128)) - { - tmpList.Reverse(); - } - - // use hashed output being able to compare written and read back values - if (encryptData) - { - var hash = SHA256.Create().ComputeHash(regRawByteArray); - strRawResult = BitConverter.ToString(hash.ToArray()); - msg = $"SHA256 - {strRawResult}"; - } - else - { - strRawResult = BitConverter.ToString(tmpList.ToArray()); - msg = $"{strRawResult}"; - } - } - catch (Exception) - { - msg = $"{strRawResult}"; - } - - return msg; - } - - /// - /// Preset the raw byte array with the default value. - /// - /// - /// - /// true if value could be set - /// - /// - Initial. - /// - public static Boolean SetToDefault(RegisterDefinition registerDefinition, out Byte[] regRawByteArray) - { - if (registerDefinition.Default.HasValue) - { - // will always return an Int64 value Byte[8] ! - regRawByteArray = ValueToByteArray(registerDefinition.Default); - return true; - } - - regRawByteArray = new Byte[] {0}; - return false; - } - - /// - /// Convert a value (T) into byte[] - /// - /// Type to convert from - /// value to convert - /// byte[] like it store in genesis meter - /// - /// - Init. - /// - /// - /// - Used to calculate time in UTC based on 01. Jan 2000 - /// and the given offset in seconds. - /// - public static Byte[] ValueToByteArray(T value) - { - var converted = new Byte[] { 0 }; - if (value == null) - return converted; - - if (value.GetType() == typeof(TimeT)) - { - var teaTime = value as TimeT; - if (teaTime != null) - { - // the value contains always the seconds since 01.Jan 2000 - var intValue = Convert.ToInt32(teaTime.SecondsSince2000); - return BitConverter.GetBytes(intValue); - } - } - - if (value is Boolean) - { - var tmp = Convert.ToBoolean(value); - return BitConverter.GetBytes(tmp); - } - - if (value is UInt16) - { - var tmp = Convert.ToUInt16(value); - return BitConverter.GetBytes(tmp); - } - - if (value is UInt32) - { - var tmp = Convert.ToUInt32(value); - return BitConverter.GetBytes(tmp); - } - - if (value is UInt64) - { - var tmp = Convert.ToUInt64(value); - return BitConverter.GetBytes(tmp); - } - - if (value is Int16) - { - var intValue = Convert.ToInt16(value); - return BitConverter.GetBytes(intValue); - } - - if (value is Int32) - { - var intValue = Convert.ToInt32(value); - return BitConverter.GetBytes(intValue); - } - - if (value is Int64) - { - var tmp = Convert.ToInt64(value); - return BitConverter.GetBytes(tmp); - } - - if (value is String) - { - return Encoding.ASCII.GetBytes(value.ToString()); - } - - if (value is Byte || value is Enum8) - { - converted[0] = Convert.ToByte(value); - } - else if (value is SByte) - { - return BitConverter.GetBytes(Convert.ToSByte(value)); - } - - else if (value is Byte[]) - { - return (Byte[])Convert.ChangeType(value, typeof(Byte[])); - } - else - { - throw new ApplicationException($"Data type {typeof(T)} not implemented"); - } - - return converted; - } - - /// - /// Get size of type - /// - /// - /// size in bytes - /// - /// - Init. - /// - public static Int32 SizeOf(RegisterDefinition registerDefinition) - { - var size = 4; - - if (registerDefinition.DataType == typeof(Boolean)) - { - size = sizeof(Boolean); - } - if (registerDefinition.DataType == typeof(Byte) || - registerDefinition.DataType == typeof(SByte) || - registerDefinition.DataType == typeof(Char) || - registerDefinition.DataType == typeof(Enum8)) - { - size = sizeof(Byte); - } - else if (registerDefinition.DataType == typeof(Int16) || - registerDefinition.DataType == typeof(UInt16)) - { - size = sizeof(Int16); - } - else if (registerDefinition.DataType == typeof(Int32) || - registerDefinition.DataType == typeof(UInt32) || - registerDefinition.DataType == typeof(TimeT)) - { - size = sizeof(Int32); - } - - else if (registerDefinition.DataType == typeof(Int64) || - registerDefinition.DataType == typeof(UInt64)) - { - size = sizeof(Int64); - } - - else if (registerDefinition.DataType == typeof(UInt48)) - { - // 48 Bits / 8 Bits/Byte - size = 6; - } - - else if (registerDefinition.DataType == typeof(UInt72)) - { - // 72 Bits / 8 Bits/Byte - size = 9; - } - - else if (registerDefinition.DataType == typeof(UInt88)) - { - // 88 Bits / 8 Bits/Byte - size = 11; - } - - else if (registerDefinition.DataType == typeof(UInt96)) - { - // 96 Bits / 8 Bits/Byte - size = 3 * 4; - } - - else if (registerDefinition.DataType == typeof(UInt128)) - { - // 128 Bits / 8 Bits/Byte - size = 4 * 4; - } - - else if (registerDefinition.DataType == typeof(String)) - { - // just some value - size = 6 * 4; - } - - return size; - } - - /// - /// Build the result of the array as text. - /// - /// byte value for conversion - /// type of the result - /// - /// - /// - Init. - /// - /// - /// - Used to calculate time in UTC based on 01. Jan 2000 - /// and the given offset in seconds. - /// - public static String ConvertToText(Byte[] rawByteArray, Type type) - { - try - { - if (type == typeof(Boolean)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - - if (type == typeof(TimeT)) - { - var teaTime = ByteArrayToValue(rawByteArray); - return teaTime.ToString(); - } - if (type == typeof(UInt16)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(UInt32)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(UInt64)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(Int16)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(Int32)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(Int64)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(Byte) || type == typeof(Enum8)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - if (type == typeof(UInt48)) - { - return ""; - } - if (type == typeof(UInt72)) - { - return ""; - } - if (type == typeof(UInt88)) - { - return ""; - } - if (type == typeof(UInt96)) - { - return ""; - } - if (type == typeof(UInt128)) - { - return ""; - } - if (type == typeof(SByte)) - { - return ByteArrayToValue(rawByteArray).ToString(); - } - - return ByteArrayToValue(rawByteArray); - } - catch (Exception) - { - return ""; - } - - } - - - /// - /// convert form byte (meter) to data type - /// - /// Type to cast in - /// byte value for conversion - /// converted value - /// - /// - Init. - /// - /// - /// - Used to calculate time in UTC based on 01. Jan 2000 - /// and the given offset in seconds. - /// - String removed from first zero in string until the end to avoid ghost signs! - /// - public static T ByteArrayToValue(Byte[] rawByteArray) - { - try - { - Object convertedObj; - var type = typeof(T); - - if (type == typeof(Boolean)) - { - convertedObj = BitConverter.ToBoolean(rawByteArray, 0); - } - else if (type == typeof(UInt16)) - { - convertedObj = BitConverter.ToUInt16(rawByteArray, 0); - } - else if (type == typeof(UInt32)) - { - convertedObj = BitConverter.ToUInt32(rawByteArray, 0); - } - else if (type == typeof(UInt64)) - { - convertedObj = BitConverter.ToUInt64(rawByteArray, 0); - } - else if (type == typeof(UInt48)) - { - return default(T); - } - else if (type == typeof(UInt72)) - { - return default(T); - } - else if (type == typeof(UInt88)) - { - return default(T); - } - else if (type == typeof(UInt96)) - { - return default(T); - } - else if (type == typeof(UInt128)) - { - return default(T); - } - else if (type == typeof(Int16)) - { - convertedObj = BitConverter.ToInt16(rawByteArray, 0); - } - else if (type == typeof(Int32)) - { - convertedObj = BitConverter.ToInt32(rawByteArray, 0); - } - else if (type == typeof(Int64)) - { - convertedObj = BitConverter.ToInt64(rawByteArray, 0); - } - else if (type == typeof(Byte) || type == typeof(Enum8)) - { - convertedObj = rawByteArray[0]; - } - else if (type == typeof(SByte)) - { - // set all others to 0 as only the LSB is of interest - for (var idx = 1; idx < rawByteArray.Length; idx++) - rawByteArray[idx] = 0; - convertedObj = Convert.ToSByte((SByte)rawByteArray[0]); - } - else if (type == typeof(Boolean)) - { - convertedObj = Convert.ToBoolean(rawByteArray[0]); - } - else if (type == typeof(String)) - { - // the value may contain zeros at the beginning of the value-record, these have to be removed - var zeroPaddingCounter = 0; - while (rawByteArray.Length > zeroPaddingCounter && rawByteArray[zeroPaddingCounter] == 0) - zeroPaddingCounter += 1; - var trimmedValue = new Byte[rawByteArray.Length - zeroPaddingCounter]; - Int32 i; - for (i = 0; i < rawByteArray.Length - zeroPaddingCounter; i++) - trimmedValue[i] = rawByteArray[i + zeroPaddingCounter]; - - // as a string is terminated with a zero all following elements after including the initial - // zero have to be removed. This is caused due to the 4 byte chunks which will be sent. - var validCharCtr = 0; - // find the first zero in the trimmed value array - while (trimmedValue.Length > validCharCtr && trimmedValue[validCharCtr] != 0) - validCharCtr += 1; - // reserve space for valid chr plus the trailing 0 which will be added if - var rawResult = new Byte[validCharCtr + 1]; - for (i = 0; i < validCharCtr; i++) - rawResult[i] = trimmedValue[i]; - if (validCharCtr < trimmedValue.Length) - { - rawResult[i] = 0; - convertedObj = Encoding.ASCII.GetString(rawResult); - } - else - { - convertedObj = Encoding.ASCII.GetString(trimmedValue); - } - } - else if (type == typeof(TimeT)) - { - // the value contains always the seconds since 01.Jan 2000 - convertedObj = new TimeT { SecondsSince2000 = ByteArrayToValue(rawByteArray) }; - } - else - { - throw new ApplicationException($"Data type {type} not implemented"); - } - - return (T)Convert.ChangeType(convertedObj, type); - } - catch (Exception) - { - return default(T); - } - } - } -} \ No newline at end of file diff --git a/Common/Hardware/WaterMeter/MagFlux/MagFluxConfig/MagFluxConfig.csproj.bak b/Common/Hardware/WaterMeter/MagFlux/MagFluxConfig/MagFluxConfig.csproj.bak deleted file mode 100644 index 7df7d4b8..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/MagFluxConfig/MagFluxConfig.csproj.bak +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Debug - AnyCPU - {70398B99-B134-4BBD-968B-7399B70C3CE2} - Library - Properties - Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig - Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig - v4.8 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\..\..\..\packages\NLog.5.2.2\lib\net46\NLog.dll - - - - - - - - - - - - - - - - - - - - - - - - {2E139F63-B6FE-4EA9-A098-85364FE9B26C} - PortCore - - - {63E0AC5E-B7C8-4363-A2FE-BE489E648FF0} - TransmitProtocol - - - - \ No newline at end of file diff --git a/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxCore.csproj.bak b/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxCore.csproj.bak deleted file mode 100644 index 74ec0df8..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxCore.csproj.bak +++ /dev/null @@ -1,140 +0,0 @@ - - - - - Debug - AnyCPU - {B5DA260C-71CC-45CA-B79E-D1B376AF718C} - Library - Properties - Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore - Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore - v4.8 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\..\..\..\packages\NLog.5.2.2\lib\net46\NLog.dll - - - - - - - - - - - - - - - - - - - - - - - - - {9eb1f659-6f73-4f65-bcab-4114fd94f5fc} - CommonCore.Configuration - - - {1b02c79e-0b19-43e2-8f6b-71ef0c786c97} - CommonCore - - - {94ca71f3-c5d7-4ec6-ba47-fa8eea3c9406} - ThreadWatcher - - - {6ccdb656-c676-4360-baba-c4596aad175d} - Measurements - - - {a33e3c6d-eaae-4a20-a0ee-f9e6922fd029} - SoftwareAccessHelper - - - {20f137fd-c25e-42b7-a68f-58c746475897} - Tools.JsonToXlsHelper - - - {4b88b0d3-e791-4774-9521-eabeacdc420e} - Logging - - - {2e139f63-b6fe-4ea9-a098-85364fe9b26c} - PortCore - - - {72037e89-4f63-4b3f-beff-4138e578ce99} - SerialPorts - - - {8BDD2789-96D4-43EA-AAD7-0F49D0347998} - ProtocolCore - - - {63e0ac5e-b7c8-4363-a2fe-be489e648ff0} - TransmitProtocol - - - {04201E95-90A0-4B73-A72F-379739B2A3AE} - GenesisCore - - - {8203abb8-38e5-472e-a742-33f38b7a74a6} - WaterMeterCore - - - {26afcf1e-1287-48b8-a506-32b1e8a0dd0e} - WaterMeterRegisters - - - {BA17D20E-0CEB-4B27-9772-2392BD750ED5} - DataPackages - - - {70398b99-b134-4bbd-968b-7399b70c3ce2} - MagFluxConfig - - - {00748F11-4B49-4D8B-93C4-4A11A210CCE2} - RequestProtocol - - - {d6b81c60-2763-4c4d-989f-90822f53b01a} - StreamingProtocol - - - - - - - - \ No newline at end of file diff --git a/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxMeter.cs.bak b/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxMeter.cs.bak deleted file mode 100644 index 7d0f22e2..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/MagFluxCore/MagFluxMeter.cs.bak +++ /dev/null @@ -1,1408 +0,0 @@ -using System; -using NLog; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using Newtonsoft.Json; -using Xylem.Common.CommonCore.Consts; -using Xylem.Common.Hardware.Interfaces.Ports.PortCore; -using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments; -using Xylem.Common.Hardware.Interfaces.Ports.SerialPorts; -using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore; -using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.EventArguments; -using Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol; -using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.DataTypes; -using Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.StreamingProtocol; -using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.EventArguments; -using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.MeasurementRecords; -using Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig; -using Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore.Consts; -using Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.RequestProtocol; -using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; -using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts; -using Xylem.Common.Metrology.Measurements; -using Xylem.Common.Metrology.Measurements.Consts; -using Xylem.Common.Utils.Logging; - - -namespace Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore -{ - /// - /// - /// MagFlux meter is the core class for the MJK MagFlux water meter. - /// One meter always is always linked to two port, one for communication and one for LED streaming. - /// - public class MagFluxMeter : IMeter, IMeterEvents - { - #region Variables and Properties - - /// - /// Ident of request protocol - /// - private String RequestIdent - { - get; - set; - } - /// - /// Reminder for status bits - /// - private UInt32 _lastStatusBits; - /// - /// Mark the streaming catcher as active - /// - private Boolean _streamingCatcherIsActive; - - /// - /// Enable raw record logging, if set every incoming package will be logged - /// - private Boolean _enableRawDataLogging; - - /// - /// Simulated flow activity - /// - public Boolean SimulatedFlowIsActive - { - get; - private set; - } - /// - /// Empty pipe ignore - /// - public Boolean IgnoreEmptyPipeDetection - { - get; - private set; - } - /// - /// to reduce the IrdA communication in test bench, preparation will be done once (when SkipPreparationForTestBench is true) - /// - public Boolean SkipPreparationForTestBench - { - get; set; - } - /// - /// List of ongoing measurements, base- and calibration-measurements - /// - private List _listOfIMeasurement = new List(); - - /// - /// Quality watch mode can be used to decode intermediate records and check - /// for actual quality of the measurements. - /// - public Boolean QualityWatchMode - { - get; - set; - } - - /// - /// Represent FW version - /// - public String FwVersion - { - get; - private set; - } = "?"; - - /// - /// Represent meter size in mm - /// - public Int32 MeterSizeMm - { - get; - private set; - } - - /// - /// Represent FW version - /// - public String FwBuildDate - { - get; - private set; - } = "?"; - - /// - /// Represent FW GIT hash - /// - public String FwGitHash - { - get; - private set; - } = "?"; - - /// - /// Represent MagFlux API Version - /// - public String MagFluxRegisterApiVersion - { - get; - private set; - } = "?"; - - - /// - /// Represent sensor serial number - /// - public String SensorSerialNo - { - get; private set; - } - - /// - /// Average of the electrode in milli Volts - /// - public Single AverageElectrode_mV - { - get; private set; - } - - /// - /// Standard deviation of the electrode in milli Volts - /// - public Single StandardDeviationElectrode_mV - { - get; private set; - } - - /// - /// Represents Empty Pipe Detection control bits - /// Bit0="EPD disabled", 1="Enabled w. Flags only", 2="EPD enabled full" - /// - public Int32 EpdControlBits - { - get; private set; - } - - /// - /// Represent pcb calibration - /// - public Single BoardCalib - { - get; private set; - } - - /// - /// Represent pcb identification - /// - public String PcbId - { - get; private set; - } - - /// - /// Represent unique id - /// - public String UniqueId - { - get; private set; - } - - private String _currentActionText = ""; - /// - /// Hold the current process name e.g. FlowTest, Preadjustment for logging - /// - public String CurrentActionText - { - get => _currentActionText; - set - { - _currentActionText = value; - SetLogger(); - } - } - - ///// - ///// Add on ctor a password and it will be used for login in if no other password is set - ///// - //private String _password; - - /// - /// request port assignment/info - /// - private IPort RequestPort - { - get; - set; - } - - /// - /// request protocol assignment/info - /// - private RequestProtocol RequestProtocol; - - /// - /// streaming port assignment/info - /// - private IPort StreamingPort - { - get; - set; - } - - /// - /// streaming protocol assignment/info - /// - private StreamingProtocol StreamingProtocol; - - /// - public String SerialNumber - { - get; set; - } - - /// - /// do not use it to set ProcessStatus - /// if you want to change ProcessStatus of this genesis use - /// is just store for some routines - /// - private ProcessState _myProcessStatus; - - /// - /// if state is change - /// - /// will be invoked. - /// on some states other events will be invoke as well - public ProcessState ProcessStatus - { - get => _myProcessStatus; - - private set - { - if (_myProcessStatus == value) - { - return; - } - - _myProcessStatus = value; - Invoker(OnProcessStatusChanged); - switch (value) - { - case ProcessState.InitIsActive: - Invoker(OnInitCompleted); - break; - case ProcessState.MeasurementIsActive: - Invoker(OnMeasurementInitCompleted); - break; - case ProcessState.MeasurementIsDone: - Invoker(OnMeasurementCompleted); - break; - case ProcessState.CalibrationIsActive: - Invoker(OnCalibInitCompleted); - break; - case ProcessState.CalibrationIsDone: - Invoker(OnCalibCompleted); - break; - case ProcessState.QRefDone: - case ProcessState.WaitForQRef: - case ProcessState.WaitForCalibration: - case ProcessState.WaitForMeasurement: - case ProcessState.IsNotInit: - //no extra event needed - break; - default: - throw new OverflowException("MagFlux measurement unknown process status"); - } - } - } - - public Boolean IsLoggedOn - { - private set; - get; - } - - /// - public Int32 Slot - { - get; set; - } - - /// - public Int32 IntermediateUpdateTimeS - { - get; - set; - } = CommunicationConfig.MeasurementUpdateTimeS; - - - /// - /// Process configuration - /// - public ProcessConfig Configuration = new ProcessConfig(); - - /// - /// Transmit protocol access for underlie objects to change response timeout - /// - private ITransmitProtocol TransmitProtocol; - - //initially do not signal event - private readonly AutoResetEvent _onSyncKeepSessionThread = new AutoResetEvent(false); - private Thread _keepSessionThread; - private readonly CancellationTokenSource _keepSessionToken = new CancellationTokenSource(); - - private ILogger _logger; - //private ILogger _loggerRawData; - - #endregion - - #region Events - - event EventHandler IMeter.OnDisposeCompleted - { - add - { - // throw new NotImplementedException(); - } - - remove - { - // throw new NotImplementedException(); - } - } - //public event EventHandler HasInformationForCaller; - - /// - public event EventHandler OnCalibCompleted; - - /// - public event EventHandler OnErrorStatusChanged; - - /// - public event EventHandler OnProcessStatusChanged; - - /// - public event EventHandler OnCalibInitCompleted; - - /// - public event EventHandler OnInitCompleted; - - /// - public event EventHandler OnMeasurementInitCompleted; - - /// - public event EventHandler OnMeasurementCompleted; - - /// - /// Response received after request for record - /// - public event EventHandler OnRequestRecordReceived; - - /// - /// Streaming record is decoded - /// - public event EventHandler OnStreamingRecordDecoded; - - ///// - ///// call this if session is expired and you need an re-authorization - ///// - //public EventHandler AuthorizationRequired; - - /// - /// A helper to invoke events only if someone is listening, otherwise nothing will happen - /// - /// Event handler to call - /// Sender (can be null) - /// Event arguments (can be null) - private void Invoker(EventHandler handler, Object sender = null, EventArgs e = null) - { - if (handler != null) - { - if (sender == null) - { - sender = this; - } - - if (e == null) - { - e = new EventArgs(); - } - - handler.Invoke(sender, e); - } - // ReSharper disable once RedundantIfElseBlock - else - { - //nobody is listening - } - } - - /// - /// Track all incoming led record packages (Calibration and Flow) - /// - /// - /// - /// I should be - /// otherwise this method does nothing - /// - private void StreamingProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e) - { - var result = e.GetEventData(); - if (result is IMeasurementRecord measurementRecord) - { - if (measurementRecord is MagFluxRecord record) - { - if (!record.IsValid) - { - return; - } - - if (record.StatusBits != _lastStatusBits) - { - _lastStatusBits = record.StatusBits; - _logger.Debug($"{RequestIdent} {StateDef.GetStateInfosOfStateMask(record.StatusBits)}"); - } - } - - AddData(measurementRecord); - } - } - - /// - /// tracking request record processing - /// - /// - /// - private void RequestProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e) - { - if (e is RequestResponseDataEventArgs) - { - OnRequestRecordReceived?.Invoke(sender, (RequestResponseDataEventArgs)e); - } - } - - /// - /// Kick off the streaming catcher. - /// - public void StartStreamingCatcher() - { - if (_streamingCatcherIsActive) - { - return; - } - if (!(StreamingPort is LedSerialPort)) - { - throw new ApplicationException("No valid streaming port"); - } - - SyncStreamingBuffer(SyncMarkRecord.DecodeEveryPackage); - StreamingProtocol.OnRecordIsDecoded += StreamingCatcher_OnRawRecordReceived; - LogRawData(true); - _streamingCatcherIsActive = true; - } - - /// - /// Cyclic execution of catcher on incoming events. - /// - /// - /// - private void StreamingCatcher_OnRawRecordReceived(Object sender, BaseDataEventArgs e) - { - OnStreamingRecordDecoded?.Invoke(this, (MagFluxFlowDataEventArgs)e); - } - - /// - /// Stop the streaming catcher. - /// - /// - public void StopStreamingCatcher() - { - if (StreamingPort == null || !(StreamingPort is LedSerialPort)) - { - throw new ApplicationException("No valid streaming port"); - } - LogRawData(false); - StreamingProtocol.OnRecordIsDecoded -= StreamingCatcher_OnRawRecordReceived; - SyncStreamingBuffer(SyncMarkRecord.SkipDecoding); - _streamingCatcherIsActive = false; - } - - #endregion - - #region class handling - - /// - /// Ctor - default - /// - /// - /// - Initial - /// - public MagFluxMeter() - { - } - - /// - public void Dispose() - { - WriteLog("Start to dispose"); - - StreamingProtocol.OnRecordIsDecoded -= StreamingProtocol_ApplyDecodedRecord; - RequestProtocol.OnRecordIsDecoded -= RequestProtocol_ApplyDecodedRecord; - - LogRawData(false); - LogManager.Flush(); - //isPart of Logout - //StopKeepSession(); - - //Cancel receive tokens - _keepSessionToken?.Cancel(); - - try - { - Logout(); - } - catch (Exception) - { - // ignored - } - - - //Run thread again to notice CancellationToken has changed - _onSyncKeepSessionThread?.Set(); - - //this timeout counter is being used for dispose only - var timeoutCounter = 100; - if (_keepSessionThread != null) - { - while (_keepSessionThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0) - { - Thread.Sleep(1); - timeoutCounter -= 1; - } - - if (_keepSessionThread.ThreadState != ThreadState.Stopped) - { - //if thread is still running try to abort - // try to avoid abort (takes age to run and is not safe) - _keepSessionThread.Abort(); - } - } - - _keepSessionThread = null; - - RequestPort?.Dispose(); - StreamingPort?.Dispose(); - - RequestProtocol?.Dispose(); - StreamingProtocol?.Dispose(); - WriteLog("Dispose is finished"); - } - - - ///// - ///// Ctor - ///// - ///// Slot number for test-bench - ///// for request Port - ///// for streaming Port - ///// don´t send CRC error or telegrams with error flag to caller - ///// - ///// If you have a password for highest access level you need, if you don't want to access the meter - ///// (just read led record) than you can leave it null - ///// - ///// - ///// - Initial - ///// - //public MagFluxMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort, - // Boolean ignoreCorruptedData = true, String password = null) - //{ - // _logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{slot}", "Slot", "Meter", "MeterBase", "MeterBase"); - // SetupMagFluxMeter(slot, requestPort, streamingPort, ignoreCorruptedData, password); - //} - - #endregion - - #region MagFlux handling - /// - /// just internal setup. called on ctor or from vb6 setup function - /// - /// Slot Number for test-bench - /// for RFID/UART/IrDA Port - /// for LED Port - /// don´t send CRC error or telegrams with error flag to caller - /// - /// If you have a password for highest access level you need, if you don't want to access the meter - /// (just read led record) than you can leave it nullS - /// - private void SetupMagFluxMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort, - Boolean ignoreCorruptedData = true, String password = "") - { - Slot = slot; - //_password = password; - - if (requestPort != null) - { - if (requestPort.Value.Type == "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort") - { - RequestIdent = $"Slot:{Slot}, Port:{requestPort.Value.PortName}, Protocol:Request, Type:UART -"; - - RequestProtocol = new RequestProtocol(RequestIdent); - TransmitProtocol = new UartTransmitProtocol(requestPort.Value.PortName); - //RequestProtocol.SetTransmitProtocol(TransmitProtocol); - - // request port is needed to assign the port name from file, else it is not used - // as the IMagFluxRequestProtocol implements the port handling and communication - RequestPort = new UartSerialPort(RequestIdent, requestPort.Value.GetSerialPort(), - TransmitProtocol.GetTransmitPortSettings()); - } - } - - if (streamingPort != null) - { - var ledTransmit = new LedTransmitProtocol(streamingPort.Value.PortName); - - var streamingIdent = $"Slot:{Slot}, Port:{streamingPort.Value.PortName}, Protocol:Streaming, Type:LED -"; - - StreamingPort = new LedSerialPort(streamingIdent, streamingPort.Value.GetSerialPort(), - ledTransmit.GetTransmitPortSettings()); - StreamingProtocol = new StreamingProtocol(streamingIdent, ignoreCorruptedData); - } - Configuration = new ProcessConfig(ProgramConfig.ConfigFileName); - - SetLogger(); - WriteLog("Setup done"); - } - - /// - /// - /// Start Event listening, has to be called after - /// - public void ConnectMeter() - { - if (RequestPort != null && RequestPort.GetPortName() != BaseSerialPort.PortNotAssigned) - { - if (RequestProtocol.Connect(RequestPort.GetPortName())) - { - WriteLog("RequestPort and RequestProtocol linked"); - RequestProtocol.OnRecordIsDecoded += RequestProtocol_ApplyDecodedRecord; - RequestProtocol.CloseConnection(); - } - else - { - WriteLog("RequestPort Connection failed"); - } - } - if (StreamingPort != null && StreamingPort.GetPortName() != BaseSerialPort.PortNotAssigned) - { - LinkPortToProtocol(StreamingPort, StreamingProtocol); - WriteLog("StreamingPort and StreamingProtocol linked"); - StreamingProtocol.OnRecordIsDecoded += StreamingProtocol_ApplyDecodedRecord; - } - - WriteLog("Connected and communication started"); - } - - /// - /// Add a port to working queue - /// - /// On should one once added in runtime - /// - private static void LinkPortToProtocol(IPort port, IProtocol protocol) - { - //for streaming mode only - if (port == null) - { - return; - } - - port.OnRawRecordReceived += delegate (Object o, BasePortDataEventArgs rawMsg) - { - protocol.FillDecodingBuffer(rawMsg); - }; - try - { - if (!port.IsOpen()) - { - port.Open(); - } - } - catch (Exception ex) - { - throw new ApplicationException($"Cannot Open Comport. Check TaskManager for other Genesis relevant programs and close them. Also check if the comport is valid! { Environment.NewLine }{ ex.Message}"); - } - - protocol.OnRecordReadyToSend += delegate (Object o, BasePortDataEventArgs dataToSend) - { - var data = (List)dataToSend.GetData(); - port.PortWrite(data.ToArray()); - }; - } - - /// - /// Synchronization of the record for the streaming interface - /// - /// - /// - private void SyncStreamingBuffer(SyncMarkRecord markRecord) - { - if (StreamingPort == null) - { - throw new ApplicationException("Synchronization not possible due to undefined streaming port"); - } - - StreamingPort.SynchronizeReceiveBuffer(markRecord); - } - - private void RequestProtocol_AuthorizationGrant(Object sender, EventArgs e) - { - WriteLog("Authorization succeeded"); - } - - public void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData = true) - { - SetupFromConfigFile(slot, ignoreCorruptedData, true, true); - } - - - /// - /// Copy list of flow results to and kick off the - /// writing of calibrations to MagFlux. - /// - /// List of flow results - public void WriteCalibrationTestBench(List results) - { - if (RequestProtocol == null) return; - var calibrationsCmPerH = new List(); - var calibrationCmPerH = new Calibration(); - foreach (var result in results) - { - calibrationCmPerH.refFlowRate = (Single)result.MeasuredRefFlowQmPerH; - calibrationCmPerH.dutFlowRate = (Single)result.MeasuredDutFlowQmPerH; - calibrationsCmPerH.Add(calibrationCmPerH); - } - WriteLog("Test bench calibration values received"); - - WriteCalibration(calibrationsCmPerH); - ReadCalibration(out _); - - - } - - /// - void IMeter.SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData, Boolean useRequest, Boolean useStreaming) - { - SetupFromConfigFile(slot, ignoreCorruptedData); - } - /// - /// Setup water meter from configuration file: - /// -slot, - /// -streaming protocol, - /// -streaming port - /// -request port - /// - /// - /// - /// - /// - /// - private void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData, Boolean useRequest, Boolean useStreaming) - { - Slot = slot; - var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(MagFlux), ProgramConfig.SerialConfigFileName); - - if (!File.Exists(configFile)) - { - configFile = Path.Combine(ProgramConfig.SerialConfigFileName); - if (!File.Exists(configFile)) - { - throw new ApplicationException($"Configuration file {configFile} not found "); - } - } - var tr = new StreamReader(configFile); - var slotConfigList = JsonConvert.DeserializeObject(tr.ReadToEnd()); - - if (slotConfigList != null) - { - var slotConfig = slotConfigList.FirstOrDefault(t => t.Slot == slot); - - if (slotConfig == null) - { - throw new ApplicationException($"Slot:{slot} - File " + - $"{ProgramConfig.SerialConfigFileName} does not contain valid configuration"); - } - if (!useRequest) - { - SetupMagFluxMeter(slot, null, slotConfig.Streaming, ignoreCorruptedData); - } - else if (!useStreaming) - { - SetupMagFluxMeter(slot, slotConfig.Request, null, ignoreCorruptedData); - } - else - { - SetupMagFluxMeter(slot, slotConfig.Request, slotConfig.Streaming, ignoreCorruptedData); - } - } - - WriteLog("Configuration loaded"); - } - - /// - public Boolean Logout() - { - RequestProtocol.CloseConnection(); - IsLoggedOn = false; - return true; - } - - /// - /// - /// - Unique Id introduced, - /// - Sensor Serial number mapped as PcbId. - /// - /// - /// -Introduced readout of calibration values. - /// - public Boolean Login() - { - if (RequestProtocol == null) - return false; - - if (!RequestProtocol.Connect(RequestPort.GetPortName())) - return IsLoggedOn; - - IsLoggedOn = true; - if (RequestProtocol.GetFirmwareVersion(out var fwVersion)) - { - FwVersion = fwVersion; - } - if (RequestProtocol.GetFwBuildDate(out var fwBuildDate)) - { - FwBuildDate = fwBuildDate; - } - if (RequestProtocol.GetMeterSizeMm(out var meterSizeMm)) - { - MeterSizeMm = meterSizeMm; - } - if (RequestProtocol.GetFwGitHash(out var gitHash)) - { - FwGitHash = gitHash; - } - if (RequestProtocol.GetApiRegisterVersion(out var apiRegisterVersion)) - { - MagFluxRegisterApiVersion = apiRegisterVersion; - } - if (RequestProtocol.GetUniqueId(out var uniqueId)) - { - UniqueId = uniqueId; - } - if (RequestProtocol.GetSensorSerialNo(out var sensorSerialNo)) - { - SensorSerialNo = sensorSerialNo; - } - if (RequestProtocol.GetPcbId(out var pcbId)) - { - PcbId = pcbId; - } - if (RequestProtocol.GetElectrodeBoardCorrectionFactor(out var boardCalib)) - { - BoardCalib = boardCalib; - } - if (RequestProtocol.GetEmptyPipeControl(out var epd_cntl_bits)) - { - EpdControlBits = epd_cntl_bits; - } - if (RequestProtocol.GetStdDev_Electrode_mV(out var u_mV)) - { - StandardDeviationElectrode_mV = u_mV; - } - if (RequestProtocol.GetAverage_Electrode_mV(out u_mV)) - { - AverageElectrode_mV = u_mV; - } - - SimulatedFlowIsActive = RequestProtocol.IsSimulatedFlowActive(); - IgnoreEmptyPipeDetection = RequestProtocol.GetIgnoreFailOnEmptyPipeOnHealthy(); - - RequestProtocol.GetCalibrationPoints(out _); - StreamingProtocol.SetDecodingMask(SensorSerialNo, uniqueId); - - return IsLoggedOn; - } - - /// - public Boolean Login(String password, Boolean runImmediately = true, Boolean skipReadMeterFwAndAssignRegisters = false) - { - return true; - } - - /// - public void InitMeasurement() - { - WriteLog("Init measurement"); - PreMeasurement(); - } - - private void PreMeasurement() - { - if (!SkipPreparationForTestBench) - { - SkipPreparationForTestBench = true; - } - else - { - WriteLog("Wait on measurement start"); - } - ProcessStatus = ProcessState.WaitForMeasurement; - } - - /// - public void StartMeasurement() - { - _listOfIMeasurement = new List - { - new BaseMeasurement(Slot, typeof(MagFluxRecord), IntermediateUpdateTimeS) - }; - - foreach (var measurement in _listOfIMeasurement) - { - measurement.OnActionStateHasChanged += Measurement_ActionStateHasChanged; - } - - WriteLog("Start measurement"); - - ProcessStatus = ProcessState.MeasurementIsActive; - _listOfIMeasurement.ForEach(m => m.MeasurementState = MeasurementStates.IsWaitingForStartData); - SyncStreamingBuffer(SyncMarkRecord.SyncStart); - } - - /// - public void StopMeasurement() - { - WriteLog("Stop measurement"); - ProcessStatus = ProcessState.MeasurementIsDone; - _listOfIMeasurement.ForEach(m => m.MeasurementState = MeasurementStates.IsWaitingForEndData); - SyncStreamingBuffer(SyncMarkRecord.SyncEnd); - } - - private void Measurement_ActionStateHasChanged(Object sender, EventArgs e) - { - SyncStreamingBuffer(SyncMarkRecord.DecodeIntermediate); - } - - /// - public MeasurementResults GetMainMeasurementResult(Double? refVolume, Double? refTimeS, - Boolean intermediateMeasurement = false, MeasurementDirection dir = MeasurementDirection.TakeBoth) - { - if (!intermediateMeasurement && GetMeasurementState(0) != MeasurementStates.IsCompleted) - { - throw new ApplicationException("Measurement is not completed"); - } - return GetAllMeasurementResults(refVolume, refTimeS, intermediateMeasurement, 0,dir).First(); - } - - /// - public List GetAllMeasurementResults(Double? refVolume, Double? refTimeS, Boolean intermediateMeasurement = false, - Int32? channel = null, MeasurementDirection dir = MeasurementDirection.TakeBoth) - { - if (!intermediateMeasurement && GetMeasurementState(channel) != MeasurementStates.IsCompleted) - { - throw new ApplicationException("Measurement is not completed"); - } - var mainMeasurement = _listOfIMeasurement.FirstOrDefault(m => m.GetType() == typeof(MagFluxRecord)); - - if (mainMeasurement == null) - { - throw new ApplicationException("Can not find an ongoing measurement"); - } - mainMeasurement.FlowDirectionforCalculatingVol = dir; - - var results = new List(); - var mainResult = intermediateMeasurement - ? mainMeasurement.GetIntermediateMeasurementResult(refVolume, refTimeS) - : mainMeasurement.GetFinalMeasurementResult(refVolume, refTimeS); - results.Add(mainResult); - _logger.Debug($"Slot:{Slot}, Channel:{mainMeasurement.GetChannel()} - MeasuredTime({mainResult.DutTimeS}s), " + - $"MeasuredVolume({mainResult.DutVolumeCm}Qm)"); - - foreach (var otherMeasurement in _listOfIMeasurement.Where(m => m.GetType() != typeof(MagFluxRecord))) - { - try - { - var otherResults = intermediateMeasurement - ? otherMeasurement.GetIntermediateMeasurementResult(refVolume, refTimeS) - : otherMeasurement.GetFinalMeasurementResult(refVolume, refTimeS); - _logger.Debug($"Slot:{Slot}, Channel:{otherMeasurement.GetChannel()} - ChannelTime({otherResults.DutTimeS}s), " + - $"ChannelVolume({otherResults.DutVolumeCm}Qm)"); - results.Add(otherResults); - } - catch (Exception ex) - { - _logger.Warn($"Slot:{Slot}, No Results for channel:{otherMeasurement.GetChannel()} Ex: {ex.Message}"); - if (channel == null) - { - throw new ApplicationException($"Can not read result from channel:{otherMeasurement.GetChannel() } Ex: {ex.Message}"); - } - } - } - - return results; - } - - /// - public MeasurementStates GetMeasurementState(Int32? onlyChannelNr = null) - { - WriteLog("Get measurement state"); - if (!_listOfIMeasurement.Any()) - { - return MeasurementStates.NotStarted; - } - - if (onlyChannelNr.HasValue) - { - var measurement = _listOfIMeasurement.First(f => f.GetChannel() == onlyChannelNr.Value); - return measurement.GetMeasurementState(); - } - return _listOfIMeasurement.Min(m => m.MeasurementState); - } - - /// - public Boolean RequestIntermediateMeasurementState(Int32? onlyChannelNr = null) - { - WriteLog("Get measurement state"); - if (!_listOfIMeasurement.Any()) - { - return false; - } - Boolean? ret = null; - foreach (var item in _listOfIMeasurement) - { - if (!onlyChannelNr.HasValue || item.GetChannel() == onlyChannelNr.Value) - { - var measurement = _listOfIMeasurement.First(f => onlyChannelNr != null && f.GetChannel() == onlyChannelNr.Value); - if ((measurement.MeasurementState == MeasurementStates.IsRunning - || measurement.MeasurementState == MeasurementStates.IsWaitingForIntermediateData)) - { - measurement.RequestIntermediateRecord(); - if (!ret.HasValue) - { - ret = true; - } - } - else - { - ret = false; - } - } - } - return ret ?? false; - } - - - /// > - public String GetPcbId() - { - if (!string.IsNullOrEmpty(PcbId) && !string.Equals("?", PcbId)) - { - return PcbId; - } - var pcbId = PcbId; - RequestProtocol?.GetPcbId(out pcbId); - PcbId = pcbId; - return pcbId; - } - /// - /// Get empty pipe control bits MagFlux - /// - public Boolean GetEmptyPipeControl() - { - var retVal = RequestProtocol.GetEmptyPipeControl(out var epd_cntl_bits); - if (retVal) - { - EpdControlBits = epd_cntl_bits; - } - - return retVal; - } - /// - /// Write calibration values to MagFlux - /// - public void WriteCalibration(List calibrationsCmPerH) - { - RequestProtocol?.SetCalibrationPoints(calibrationsCmPerH); - //RequestProtocol?.CloseConnection(); - } - /// - /// Read calibration values to MagFlux - /// - public void ReadCalibration(out List calibrationsCmPerH) - { - calibrationsCmPerH = null; - RequestProtocol?.GetCalibrationPoints(out calibrationsCmPerH); - //RequestProtocol?.CloseConnection(); - } - - /// - /// Average Electrode measurement used for Device calibration verification - /// - /// Average Electrode [mV] - /// true on okay or false on error - public Boolean GetAverage_Electrode_mV(out Single u_mV) - { - u_mV = 0; - var retVal = RequestProtocol.GetAverage_Electrode_mV(out u_mV); - AverageElectrode_mV = u_mV; - return retVal; - } - - /// - /// Standard deviation electrode measurement used for Device calibration verification - /// Note: update will only happen after x seconds, depending on filter config in device - /// - /// Average Electrode [mV] - /// true on okay or false on error - public Boolean GetStdDev_Electrode_mV(out Single u_mV) - { - u_mV = 0; - var retVal = RequestProtocol.GetStdDev_Electrode_mV(out u_mV); - StandardDeviationElectrode_mV = u_mV; - return retVal; - } - - /// - /// Ignore Empty pipe flag on Healthy, because it is not yet working correct - /// Note: Not 100% secure all test is working during empty pipe, metrology board firmware may not allow running during detection of empty pipe - /// - /// - /// true on okay or false on error - public Boolean SetIgnoreFailOnEmptyPipeOnHealthy(Boolean ignoreEmptyPipeStatus) - { - var retVal = RequestProtocol.SetIgnoreFailOnEmptyPipeOnHealthy(ignoreEmptyPipeStatus); - if (retVal) - IgnoreEmptyPipeDetection = ignoreEmptyPipeStatus; - return retVal; - } - - /// - /// Set PCB settings and PCB calibrations to default. - /// - public Boolean SetPCBToDemoDefaults() - { - if (RequestProtocol == null) - return false; - var retVal = RequestProtocol.SetPCBToDemoDefaults(); - //RequestProtocol?.CloseConnection(); - return retVal; - } - - /// - /// Set a new meter size to MagFlux - /// - public Boolean SetMeterSizeMm(UInt16 meterSize_mm) - { - if (RequestProtocol == null) - return false; - var retVal = RequestProtocol.SetMeterSizeMm(meterSize_mm); - //RequestProtocol?.CloseConnection(); - return retVal; - } - - /// - /// Set a new sensor serial number to MagFlux - /// - public Boolean SetSensorSerialNo(UInt32 sensorSerialNo) - { - if (RequestProtocol == null) - return false; - var retVal = RequestProtocol.SetSensorSerialNo(sensorSerialNo); - //RequestProtocol?.CloseConnection(); - return retVal; - } - - /// - public void InitCalibration() - { - if (RequestProtocol == null) - return; - if (RequestProtocol.PrepareDeviceForCalibration()) - PreMeasurement(); - //RequestProtocol?.CloseConnection(); - } - - /// - public void StartCalibration() - { - StartMeasurement(); - } - - /// - public void StopCalibration() - { - StopMeasurement(); - //RequestProtocol?.CloseConnection(); - } - - /// - public MeasurementStates GetCalibrationState() - { - return GetMeasurementState(); - } - - /// - public void BuildAndCheckCalibFactorsAllChannels(Double refVolumeCm, Double? refTimeS = null, - Double reqDeviationPercent = 0, Double maxCalibFactorTolerancePercent = 0) - { - throw new NotImplementedException(); - } - - /// - public void SetCalibFactorsAllChannels(Boolean justLog = false) - { - throw new NotImplementedException(); - } - - /// - public void SetLcdText(Boolean release, Byte[] textInHex) - { - throw new NotImplementedException(); - } - - /// - public void SetProcessState(DisplayCodes newState, Boolean webRequest = true) - { - throw new NotImplementedException(); - } - - /// - public DisplayCodes GetLastProcessState() - { - throw new NotImplementedException(); - } - - /// - public void WriteLog(String text) - { - _logger.Trace($"Slot:{Slot} - {text}"); - } - - /// - public void LogRawData(Boolean on) - { - _enableRawDataLogging = on; - //if (_enableRawDataLogging) - //{ - // if (_loggerRawData == null) - // { - // _loggerRawData = NLogHelper.CreateOrGetMultiLogger( - // StreamingPort.GetPortName(), "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase"); - - // } - //} - if (StreamingPort is LedSerialPort port) - { - port.RecordStreamingRawData = _enableRawDataLogging; - } - } - - /// - public Boolean CheckStateAfterMeasurement() - { - throw new NotImplementedException(); - } - - /// - public void DisposeMeter() - { - Dispose(); - } - - private void AddData(IMeasurementRecord newRecord) - { - - foreach (var measurement in _listOfIMeasurement) - { - if (measurement.GetType() != newRecord.GetType()) - { - continue; - } - - if (measurement.GetChannel() == newRecord.GetChannel()) - { - measurement.AddData(newRecord); - } - } - - // when every measurement has state IsRunning or every measurement has state IsCompleted, the streaming buffer set to skip decoding - if (_listOfIMeasurement.All(m => m.MeasurementState == MeasurementStates.IsRunning) - || _listOfIMeasurement.All(m => m.MeasurementState == MeasurementStates.IsCompleted)) - { - SyncStreamingBuffer(QualityWatchMode - ? SyncMarkRecord.DecodeIntermediateQuality - : SyncMarkRecord.SkipDecoding); - } - - } - #endregion - - #region logging - private void SetLogger() - { - if (_logger != null) - { - LogManager.Flush(); - } - // the id is a combination of UniqueId stored in PcbId and SensorSN (SensorId) - var id = !string.IsNullOrEmpty(PcbId) ? $"PCBID{PcbId}" : "?"; - id += !string.IsNullOrEmpty(SerialNumber) ? $"_SN{SerialNumber}" : "?"; - var fileName = $"MagFluxRawData_Slot_{Slot}"; - - if (string.IsNullOrEmpty(CurrentActionText)) - { - _logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}", $"", "Meter", "MeterBase", "MeterBase"); - if (RequestProtocol != null) - { - var pathName = NLogHelper.GetPath(_logger); - var path = Path.GetDirectoryName(pathName); - RequestProtocol.SetLogLocation(path, fileName); - } - } - else - { - _logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}_{id}_{CurrentActionText}", "Slot", "Meter", "MeterBase", "MeterBase"); - if (RequestProtocol != null) - { - var pathName = NLogHelper.GetPath(_logger); - var path = Path.GetDirectoryName(pathName); - RequestProtocol.SetLogLocation(path, fileName); - } - - //TODO check with roland - if (StreamingPort is LedSerialPort) - { - var streamingIdent = $"LedRawData_Slot_{Slot}_{id}_{CurrentActionText}"; - - ((LedSerialPort)StreamingPort).Ident = streamingIdent; - ((LedSerialPort)StreamingPort).RefreshLogger(); - - - //if (!string.IsNullOrEmpty(SerialNumber) && CurrentActionText.Contains("PG")) - //{ - - // //var pathMain = NLogHelper.GetPath(_logger); - // //var pathRaw = NLogHelper.GetPath(((LedSerialPort)StreamingPort).GetRawLogger()); - - // //var basePath = Path.GetDirectoryName(pathMain); - - // //var testRunString = CurrentActionText.Replace("PG", ""); - // //var lastChat = pathMain.LastIndexOf('\\'); - // //pathMain = pathMain.Substring(lastChat + 1); - // //pathRaw = Path.GetFileName(pathRaw); - - // //var tmpUrl = $"http://10.49.40.25/MeterProcessState/api/TestBench/SetMeterLogPathInfo?SerialNumber={SerialNumber}&TestRunId={testRunString}&BasePath={basePath}&MainLogName={pathMain}&LEDName={pathRaw}"; - // //_logger.Debug($"Request Url: { tmpUrl}"); - // ////ToDO: Update to service URl - // //LocalWebRequest.GetRequest(tmpUrl); - //} - } - } - } - - public void TestBenchDispose() - { - //nothing to do - } - #endregion - } -} diff --git a/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.cs.bak b/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.cs.bak deleted file mode 100644 index 03d190de..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.cs.bak +++ /dev/null @@ -1,673 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using NLog; -using XYLEM; -using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments; -using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore; -using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.EventArguments; -using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.DataTypes; -using Xylem.Common.Utils.Logging; - -namespace Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.RequestProtocol -{ - /// - /// Wrapper for MagFlux request protocol implemented in - /// - public class RequestProtocol : BaseProtocol - { - private readonly String _ident; - private readonly ILogger _logger; - private IMagFluxRequestProtocol _magFluxRequestProtocol; - private IMagFluxAPI _magFluxApi; - - // calibration point in cubic meters per hour - private List _calibPointsCmPerH; - - /// - /// - /// - MagFluxApi - /// - public RequestProtocol(String ident) : base(ident) - { - _ident = ident; - _magFluxApi = new MagFluxAPI(); - _calibPointsCmPerH = new List(); - _logger = NLogHelper.CreateOrGetMultiLogger(_ident, "", "RequestProtocol", "MeterBase", - "MeterBase"); - } - - /// > - /// - /// - MagFluxApi - /// - public override void Dispose() - { - _calibPointsCmPerH.Clear(); - _calibPointsCmPerH = null; - CloseConnection(); - _magFluxRequestProtocol = null; - _magFluxApi = null; - base.Dispose(); - } - - /// - public override event EventHandler OnRecordReadyToSend; - - /// - public override event EventHandler OnRecordIsDecoded; - - protected override void DecodeRecord(IPortDataEventArgs data) - { - throw new NotImplementedException(); - } - - /// - /// Wrapper to connect MagFlux - /// - /// - /// - /// - /// - Initial - /// - /// - /// - MagFluxApi - /// - /// - /// - Avoid re-opening if connection is already done - /// - public Boolean Connect(String portName) - { - if (_magFluxApi == null) - return false; - if (_magFluxRequestProtocol != null && _magFluxRequestProtocol.IsConnected()) - { - _logger.Info($"{_ident} Connection already established to {portName}"); - return true; - } - - _magFluxRequestProtocol = _magFluxApi.Connect(portName); - if (_magFluxRequestProtocol == null) - { - _logger.Error($"{_ident} Connection to {portName} failed! Object reference returned null"); - return false; - } - - Thread.Sleep(100); - var retVal = _magFluxRequestProtocol.IsConnected(); - if (retVal) - { - _logger.Info($"{_ident} New connection established to {portName}"); - } - else - { - _logger.Error($"{_ident} Connection to {portName} failed!"); - } - - return retVal; - } - - public String GetComCounters() - { - throw new NotImplementedException(); - } - - /// - /// Wrapper to set the log location for MagFlux direct raw output - /// - /// location of raw logging - /// file name based on the UniqueId - /// - /// - /// - Initial - /// - public Boolean SetLogLocation(String path, String fileName) - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.SetLogLocation(path, fileName); - if (string.IsNullOrEmpty(fileName)) - fileName = "?"; - _logger.Debug(retVal ? - $"{_ident} Logging set to: {path}\\{fileName}" : - $"{_ident} Logging path setup failed!"); - return retVal; - } - - public void OpenLogFile() - { - throw new NotImplementedException(); - } - - /// - /// Wrapper to set experimental boards to defaults regarding board serial number - /// and board calibration values - /// - /// - /// - /// - Initial - /// - public Boolean SetPCBToDemoDefaults() - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.SetPCBToDemoDefaults(); - _logger.Debug(retVal ? - $"{_ident} Successfully set PCB settings and PCB calibrations to default" : - $"{_ident} PCB settings and PCB calibrations setup to default failed!"); - return retVal; - } - - /// - /// Wrapper to get the simulated flow setting - /// - /// - /// - /// - Initial - /// - public Boolean IsSimulatedFlowActive() - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.IsSimulatedFlowActive(); - _logger.Debug($"{_ident} Simulated flow active read back returns: {retVal}"); - return retVal; - } - - /// - /// Wrapper to set the simulated flow setting - /// - /// true to ignore state - /// - /// - /// - Initial - /// - public Boolean SetSimulatedFlowActive(Boolean on) - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.SetSimulatedFlowActive(on); - _logger.Debug(retVal ? - $"{_ident} Simulated flow active set to: {on}" : - $"{_ident} Simulated flow active setup failed!"); - return retVal; - } - - /// - /// Wrapper to get the ignore empty pipe mode status - /// - /// - /// - /// - Initial - /// - public Boolean GetIgnoreFailOnEmptyPipeOnHealthy() - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetIgnoreFailOnEmptyPipeOnHealthy(); - _logger.Debug($"{_ident} Ignore empty pipe status read back returns: {retVal}"); - return retVal; - } - - /// - /// Wrapper to set the ignore empty pipe mode - /// - /// true to ignore state - /// - /// - /// - Initial - /// - public Boolean SetIgnoreFailOnEmptyPipeOnHealthy(Boolean on) - { - if (_magFluxRequestProtocol == null) - return false; - _magFluxRequestProtocol.SetIgnoreFailOnEmptyPipeOnHealthy(on); - var retVal = _magFluxRequestProtocol.GetIgnoreFailOnEmptyPipeOnHealthy(); - _logger.Debug(retVal == on ? - $"{_ident} Ignore empty pipe status adjusted to: {on}" : - $"{_ident} Ignore empty pipe status setup failed!"); - return retVal == on; - } - - /// - /// Wrapper to get the empty pipe bits - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetEmptyPipeControl(out Int32 epd_cntl_bits) - { - epd_cntl_bits = 0; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetEmptyPipeControl(out epd_cntl_bits); - _logger.Debug(retVal ? - $"{_ident} Empty pipe control bits: 0x{epd_cntl_bits:X4}" : - $"{_ident} Empty pipe control bits request failed!"); - return retVal; - } - - /// - /// Wrapper to get the standard deviation value of the electrode in milli Volts - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetStdDev_Electrode_mV(out Single u_mV) - { - u_mV = 0; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetStdDev_Electrode_mV(out u_mV); - _logger.Debug(retVal ? - $"{_ident} Standard deviation of electrode: {u_mV:F6} mV" : - $"{_ident} Standard deviation of electrode request failed!"); - return retVal; - } - /// - /// Wrapper to get the standard deviation value of the electrode in milli Volts - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetAverage_Electrode_mV(out Single u_mV) - { - u_mV = 0; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetAverage_Electrode_mV(out u_mV); - _logger.Debug(retVal ? - $"{_ident} Average of electrode: {u_mV:F6} mV" : - $"{_ident} Average of electrode request failed!"); - return retVal; - } - - /// - /// Wrapper to get the board calibration value - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetElectrodeBoardCorrectionFactor(out Single value_out) - { - value_out = 0f; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetElectrodeBoardCorrectionFactor(out value_out); - _logger.Debug(retVal ? - $"{_ident} Board calibration: {value_out:F4}" : - $"{_ident} SensorSerialNo request failed!"); - return retVal; - } - - /// - /// Wrapper to get the sensor serial number - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetSensorSerialNo(out String sensorSerialNo) - { - sensorSerialNo = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetSensorSerialNo(out sensorSerialNo); - _logger.Debug(retVal ? - $"{_ident} SensorSerialNo: {sensorSerialNo}" : - $"{_ident} SensorSerialNo request failed!"); - return retVal; - } - - /// - /// Wrapper to get the PcbId - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetPcbId(out String pcbId) - { - pcbId = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetBoardSerialNo(out pcbId); - _logger.Debug(retVal ? - $"{_ident} PcbId: {pcbId}" : - $"{_ident} PcbId request failed!"); - return retVal; - } - - /// - /// Wrapper to set the sensor serial number - /// - /// - /// - /// - /// - Initial - /// - public Boolean SetSensorSerialNo(UInt32 sensorSerialNo) - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.SetSensorSerialNo(sensorSerialNo); - _logger.Debug(retVal ? - $"{_ident} Set SensorSerialNo: {sensorSerialNo}" : - $"{_ident} Set SensorSerialNo request failed!"); - return retVal; - } - - /// - /// Wrapper to get unique id referenced as PCBID - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetUniqueId(out String pcbId) - { - pcbId = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetUniqueId(out pcbId); - _logger.Debug(retVal ? - $"{_ident} UniqueId: {pcbId}" : - $"{_ident} UniqueId request failed!"); - return retVal; - } - - /// - /// Wrapper to get FW version of MagFlux - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetFirmwareVersion(out String fwVersion) - { - fwVersion = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetFirmwareVersion(out fwVersion); - _logger.Debug(retVal ? - $"{_ident} FW Version: {fwVersion}" : - $"{_ident} FW Version request failed!"); - return retVal; - } - - /// - /// Wrapper to get FW Build Date - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetFwBuildDate(out String fwBuildDate) - { - fwBuildDate = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetFwBuildDate(out fwBuildDate); - _logger.Debug(retVal ? - $"{_ident} FW Build Date: {fwBuildDate}" : - $"{_ident} FW Build Date request failed!"); - return retVal; - } - - /// - /// Wrapper to get FW GIT hash of MagFlux - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetFwGitHash(out String fwGitHash) - { - fwGitHash = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetFwGitHash(out fwGitHash); - _logger.Debug(retVal ? - $"{_ident} FW GIT Hash: {fwGitHash}" : - $"{_ident} FW GIT Hash request failed!"); - return retVal; - } - - /// - /// Wrapper to get API register version of MagFlux - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetApiRegisterVersion(out String apiRegisterVersion) - { - apiRegisterVersion = "?"; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetAPIRegisterVersion(out apiRegisterVersion); - _logger.Debug(retVal ? - $"{_ident} MagFlux API Register Version: {apiRegisterVersion}" : - $"{_ident} MagFlux API Register Version request failed!"); - return retVal; - } - - public Boolean GetFlowRate_lps(out Single value_out) - { - throw new NotImplementedException(); - } - - /// - /// Wrapper to get meter size in mm - /// - /// - /// - /// - /// - Initial - /// - public Boolean GetMeterSizeMm(out Int32 meterSize) - { - meterSize = 0; - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.GetDn_mm(out meterSize); - _logger.Debug(retVal ? - $"{_ident} MeterSize DN: {meterSize}" : - $"{_ident} MeterSize request failed!"); - return retVal; - } - - /// - /// Wrapper to set meter size in mm - /// - /// - /// - /// - /// - Initial - /// - public Boolean SetMeterSizeMm(UInt16 meterSize) - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.SetDn_mm(meterSize); - _logger.Debug(retVal ? - $"{_ident} Set MeterSize DN: {meterSize}" : - $"{_ident} Set MeterSize failed!"); - return retVal; - } - - public Boolean GetDeviceHealthy() - { - throw new NotImplementedException(); - } - - public Boolean GetDeviceErrorMessages(out List status_list_out) - { - throw new NotImplementedException(); - } - - public Boolean PrepareDeviceForCalibration() - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.PrepareDeviceForCalibration(); - _logger.Debug(retVal ? - $"{_ident} MagFlux successfully prepared for calibration" : - $"{_ident} MagFlux preparation for calibration failed!"); - return retVal; - } - - public Boolean AbortDeviceForCalibration() - { - if (_magFluxRequestProtocol == null) - return false; - var retVal = _magFluxRequestProtocol.AbortDeviceForCalibration(); - _logger.Debug(retVal ? - $"{_ident} MagFlux calibration successfully aborted" : - $"{_ident} MagFlux abortion of calibration failed!"); - return retVal; - } - - /// - /// Wrapper to get the calibration points from MagFlux: - /// - Here the conversion from to to - /// will take place. - /// - /// - /// - /// - /// - Initial - /// - /// - /// - Output in cubic meters per hour. - /// - public Boolean GetCalibrationPoints(out List calibPointsCmPerH) - { - calibPointsCmPerH = new List(); - if (_magFluxRequestProtocol == null) - return false; - - var retVal = _magFluxRequestProtocol.GetCalibrationPoints(out var calibrations); - _logger.Debug(retVal ? - $"{_ident} MagFlux calibration successfully read:\n{calibrations}" : - $"{_ident} MagFlux calibration read failed!"); - - if (!retVal) - return false; - - // extract the single calibration points from container as ref and DUT in liters per second - var calibPoints = calibrations?.calibrations?.ToList(); - if (calibPoints == null) - return false; - - var calibConverter = new Calibration(); - _calibPointsCmPerH?.Clear(); - - foreach (var cp in calibPoints) - { - // convert flow rate from liters per second to cubic meters per hour - calibConverter.refFlowRate = cp.ReferenceFlowRate_lps * 3.6f; - calibConverter.dutFlowRate = cp.ReportedFlowRate_lps * 3.6f; - _calibPointsCmPerH?.Add(calibConverter); - } - - calibPointsCmPerH = _calibPointsCmPerH; - return true; - } - - /// - /// Wrapper to set the calibration points from MagFlux: - /// - Calibration points have to be sorted ascending by request from MJK - /// - Here the conversion from to - /// will take place. - /// - /// - /// - /// - /// - Initial - /// - /// - /// - Input in cubic meters per hour, - /// - Remove multiple 0;0 calibrations as these are unused points. - /// - public Boolean SetCalibrationPoints(List calibPointsCmPerH) - { - if (_magFluxRequestProtocol == null || calibPointsCmPerH == null) - return false; - - // remove zeros as this are possibly empty (unused) fields - var removedZeros = calibPointsCmPerH.Where(x => x.dutFlowRate != 0 || x.refFlowRate != 0); - var calibrations = new List(); - calibrations.AddRange(removedZeros); - if (calibrations.All(x => x.refFlowRate != 0)) - { - calibrations.Add(new Calibration { dutFlowRate = 0, refFlowRate = 0 }); - } - - // order calibrations ascending as required by MJK - var orderedCalibrations = calibrations.OrderBy(x => x.refFlowRate); - - _calibPointsCmPerH.Clear(); - _calibPointsCmPerH.AddRange(orderedCalibrations); - - var calibPoints = new List(); - foreach (var cp in _calibPointsCmPerH) - { - // convert flow rate to liters per second from cubic meters per hour - var calibConverter = new CalibrationPoint - { - ReferenceFlowRate_lps = cp.refFlowRate / 3.6f, - ReportedFlowRate_lps = cp.dutFlowRate / 3.6f - }; - calibPoints.Add(calibConverter); - } - var cbs = new CalibrationPoints(calibPoints, calibPoints.Count); - var retVal = _magFluxRequestProtocol.SetCalibrationPoints(cbs); - _logger.Debug(retVal ? - $"{_ident} MagFlux calibration successfully set:\n{cbs}" : - $"{_ident} MagFlux calibration setup failed!"); - return retVal; - } - - - /// - /// Wrapper to close the connection to the MagFlux - /// - /// - /// - /// - Initial - /// - /// - /// - Corrected logic, if !IsConnected = connection is closed - /// - public Boolean CloseConnection() - { - if (_magFluxRequestProtocol == null) - return false; - MagFluxAPI.CloseAllConnections(); - - Thread.Sleep(100); - var retVal = _magFluxRequestProtocol.IsConnected(); - _logger.Info(!retVal ? - $"{_ident} Connection is closed" : - $"{_ident} Close connection request failed!"); - return retVal; - } - } -} diff --git a/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.csproj.bak b/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.csproj.bak deleted file mode 100644 index 53469aa6..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/Protocols/RequestProtocol/RequestProtocol.csproj.bak +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Debug - AnyCPU - {00748F11-4B49-4D8B-93C4-4A11A210CCE2} - Library - Properties - Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.RequestProtocol - Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.RequestProtocol - v4.8 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\MagFluxApi\magflux_api.dll - - - ..\..\..\..\..\packages\NLog.5.2.2\lib\net46\NLog.dll - - - ..\..\MagFluxApi\shared_code.dll - - - - - - - - - - - - - - - - - - - - - - {4B88B0D3-E791-4774-9521-EABEACDC420E} - Logging - - - {2E139F63-B6FE-4EA9-A098-85364FE9B26C} - PortCore - - - {8BDD2789-96D4-43EA-AAD7-0F49D0347998} - ProtocolCore - - - {BA17D20E-0CEB-4B27-9772-2392BD750ED5} - DataPackages - - - - - - - \ No newline at end of file diff --git a/Common/Hardware/WaterMeter/MagFlux/Protocols/StreamingProtocol/StreamingProtocol.csproj.bak b/Common/Hardware/WaterMeter/MagFlux/Protocols/StreamingProtocol/StreamingProtocol.csproj.bak deleted file mode 100644 index fd1cdc8e..00000000 --- a/Common/Hardware/WaterMeter/MagFlux/Protocols/StreamingProtocol/StreamingProtocol.csproj.bak +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Debug - AnyCPU - {D6B81C60-2763-4C4D-989F-90822F53B01A} - Library - Properties - Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.StreamingProtocol - Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.StreamingProtocol - v4.8 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\..\..\packages\NLog.5.2.2\lib\net46\NLog.dll - - - - - - - - - - - - - - - - - - - - {1B02C79E-0B19-43E2-8F6B-71EF0C786C97} - CommonCore - - - {6CCDB656-C676-4360-BABA-C4596AAD175D} - Measurements - - - {91DEF0C0-AA4F-40AE-8F18-CB717FDE7E7F} - Crc16Ccitt - - - {2E139F63-B6FE-4EA9-A098-85364FE9B26C} - PortCore - - - {8BDD2789-96D4-43EA-AAD7-0F49D0347998} - ProtocolCore - - - {BA17D20E-0CEB-4B27-9772-2392BD750ED5} - DataPackages - - - - - - - \ No newline at end of file diff --git a/Common/Hardware/WaterMeter/WaterMeterCore/IMeter.cs.bak b/Common/Hardware/WaterMeter/WaterMeterCore/IMeter.cs.bak deleted file mode 100644 index 72ca84b0..00000000 --- a/Common/Hardware/WaterMeter/WaterMeterCore/IMeter.cs.bak +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts; -using Xylem.Common.Metrology.Measurements; -using Xylem.Common.Metrology.Measurements.Consts; - -namespace Xylem.Common.Hardware.WaterMeter.WaterMeterCore -{ - - /// - /// - /// IMeter is the highest level interface between a meter with direct communication to a test bench. - /// has general things like calibration, measurement and set up routines. - /// no specific meter stuff in here! - /// it is always disposable - /// - [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71") - , ComVisible(true) - , InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - - public interface IMeter : IDisposable - { - #region events - - /// - /// occurs when ProcessState has changed - /// - event EventHandler OnProcessStatusChanged; - - /// - /// occurs when ErrorState has changed - /// - event EventHandler OnErrorStatusChanged; - - /// - /// occurs when Init is completed - /// - event EventHandler OnInitCompleted; - - /// - /// occurs when the initialization for Measurement is completed - /// - event EventHandler OnMeasurementInitCompleted; - - /// - /// occurs when the Measurement is completed - /// - event EventHandler OnMeasurementCompleted; - - /// - /// occurs when the initialization for calibration is completed - /// - event EventHandler OnCalibInitCompleted; - - /// - /// occurs when the calibration is completed - /// - event EventHandler OnCalibCompleted; - - /// - /// occurs when meter gets disposed - /// - event EventHandler OnDisposeCompleted; - #endregion - - #region properties - /// - /// unique meter identification - /// - String SerialNumber { get; set; } - - /// - /// read-only to see current Process state - /// - ProcessState ProcessStatus { get; } - - /// - /// the current slot in test-bench - /// - Int32 Slot { get; set; } - - /// - /// Setup of intermediate update time for measurement updates and overflow detections, - /// has to be calculated depending on flow-rate and nominal diameter (DN) - /// - Int32 IntermediateUpdateTimeS { get; set; } - /// - /// Skip the test bench preparation process - /// - Boolean SkipPreparationForTestBench { get; set; } - /// - /// Set the current action test - /// - String CurrentActionText { get; set; } - #endregion - - #region method - - /// - /// Read the PcbId from IMeter, if device has no readable PcbId simulate one - /// - /// - String GetPcbId(); - - /// - /// Open Com ports, try some communication, login - /// - void ConnectMeter(); - - /// - /// Setup up meter from config file. just a workaround for vb6 calls. please do not use if your working with .net - /// - /// Slot Number for Test bench - /// don´t send CRC error or telegrams with error flag to caller - /// - /// - void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData = true, Boolean useRequest = true, Boolean useStreaming = true); - - - - /// - /// Logout from device - /// - Boolean Logout(); - - /// - /// Start Login with password service - /// - Boolean Login(); - - /// - /// Start Login with out password service and a fixed password - /// - /// password for login - /// execute immediately - /// skip app readout and register generation to speed up - /// the initial connection process - Boolean Login(String password, Boolean runImmediately = true, Boolean skipReadMeterFwAndAssignRegisters = false); - - /// - /// set up measurement parameter and set meter into test-mode - /// - void InitMeasurement(); - - /// - /// Starting a Measurement - /// Meter must be initialized - /// Start to receive record from meter and decode this record into MeasurementRecord - /// - /// - [ComVisible(false)] - void StartMeasurement(); - - /// - /// Meter must have a active measurement - /// Stop receive record from meter and decode record - /// - void StopMeasurement(); - - /// - /// - simplify the measurement results handling - /// - getting the main measurement of the entire device (combination of all paths) - /// - add MeasurementResult - /// - remove all other methods - /// - /// - /// After a completed measurement , you can grab - /// - /// Calculated results - MeasurementResults GetMainMeasurementResult(Double? refVolume, Double? refTimeS, Boolean intermediateMeasurement = false, MeasurementDirection dir = MeasurementDirection.TakeBoth); - - - /// - /// The first measurement is ALWAYS a FlowTestRecord. - /// - List GetAllMeasurementResults(Double? refVolume, Double? refTimeS, - Boolean intermediateMeasurement = false, Int32? channel = null, MeasurementDirection dir = MeasurementDirection.TakeBoth); - - - - /// - /// get current state of the running measurement - /// - /// leave empty for an aggregate state for all measurements or pass the channel number to check - /// - MeasurementStates GetMeasurementState(Int32? onlyChannelNr = null ); - - /// - /// Request for intermediate measurement state. - /// - /// - /// - Boolean RequestIntermediateMeasurementState(Int32? onlyChannelNr = null); - - /// - /// set up calibration parameter (calibration factor to default) and set meter into calibration mode - /// - void InitCalibration(); - - /// - /// Starting a Calibration - /// Meter must be initialized - /// Start to receive record from meter, decode and store this record - /// no available - /// To finish calibration process run - /// - [ComVisible(false)] - void StartCalibration(); - - /// - /// Meter must have a active for calibration - /// Stop receive record from meter, decode and store record - /// record will not calculate or store - /// - void StopCalibration(); - - /// - /// Get current state of the running calibration - /// - /// - MeasurementStates GetCalibrationState(); - - /// - /// Calculate calibration factor for static and flying start / stop - /// Results kept internal and not being saved to meter. - /// if you want to store them on meter use - /// - /// the reference volume is essential for calculation of calibration factor - /// the reference time is needed for flying start/stop - /// the required deviation to set the scale apart from 0 - /// override the default max calibration factor tolerance - void BuildAndCheckCalibFactorsAllChannels(Double refVolumeCm, Double? refTimeS = null, Double reqDeviationPercent = 0.0, Double maxCalibFactorTolerancePercent = 0.0); - - /// - /// Save calculated calibration on meter if no calibration results available an exception occurred - /// - void SetCalibFactorsAllChannels(Boolean justLog = false); - - /// - /// To control the LCD from meter for status information etc. - /// - /// if set to true the meter will show his normal screen, if set to false the next parameter will shown in display - /// test shown in LCD in byte array as hex value (0x33,0xFF shows 33FF on screen) - void SetLcdText(Boolean release, Byte[] textInHex); - - - /// - /// Sets Display text and update production database - /// - /// New ProductionState - /// web logging required per default true, for offline usage set to false - void SetProcessState(DisplayCodes newState, Boolean webRequest = true); - - /// - /// Get last Process State - /// - DisplayCodes GetLastProcessState(); - - /// - /// Add external text to internal log file handling - /// - /// text to log - void WriteLog(String text); - - /// - /// Enable/Disable Raw record Logging - /// - /// True = Write Raw record into log/ false = Stop write raw record into log - void LogRawData(Boolean on); - - /// - /// Check if the meter has any errors that can occur on a measurement - /// the error reason has find out in the log files - /// - /// false = everything is good, detect at least one error - Boolean CheckStateAfterMeasurement(); - - #endregion - - /// - /// Clear all object set up on runtime - /// - void DisposeMeter(); - void TestBenchDispose(); - } -} \ No newline at end of file diff --git a/Common/Ui/GenesisToolBox/FrmFwUpdate.cs.bak b/Common/Ui/GenesisToolBox/FrmFwUpdate.cs.bak deleted file mode 100644 index 66b9bf78..00000000 --- a/Common/Ui/GenesisToolBox/FrmFwUpdate.cs.bak +++ /dev/null @@ -1,1932 +0,0 @@ -using Logic.ProductionToProductMapper.Files.Fw; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Data; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using Xylem.Common.CommonCore.Configuration; -using Xylem.Common.Hardware.WaterMeter.Genesis.Applications; -using Xylem.Common.Hardware.WaterMeter.Genesis.Applications.Const; -using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; -using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile; -using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; -using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; -using Xylem.Common.Logic.ProductionOrderCore.FW; -using Xylem.Common.Logic.ProductionOrderCore.OrderData; -using Xylem.Common.Logic.SoftwareAccessHelper; -using Xylem.Common.Ui.GenesisToolBox.Properties; - - -namespace Xylem.Common.Ui.GenesisToolBox -{ - /// - /// FW update form - /// - public partial class FrmFwUpdate : Form - { - #region Variables - private readonly MeterBatch _meterBatch = new MeterBatch(); - private GenesisMeter _currentGenesis; - private const String StrConnecting = "Connecting to PCB"; - private const String StrNotConnected = "NOT CONNECTED"; - private const String StrCoreRevision = "System Core Revision: "; - private const String StrPartPcbConnected = "Connected to PCB ID: "; - private const String StrFileAppsLoaded = "ABC loaded"; - private const String StrUpdatePathEmpty = "No update files in path"; - private const String StrPackageFileLoaded = "ADF loaded"; - private const String StrPackageFileInvalid = "ADF invalid"; - private const String StrPackageFileToFileAppsMismatch = "ADF to ABC mismatch"; - private const String StrBurnUpgrade = "Waiting for meter response"; - private const String StrOverallProcess = "Overall Process"; - private readonly DataTable _dataTable = new DataTable(); - - //data grid table header - private const String StrAppName = "AppName"; - private const String StrAppId = "AppId"; - private const String StrMeterVersion = "MeterVersion"; - private const String StrMeterCrc = "MeterCrc"; - private const String StrFileVersion = "FileVersion"; - private const String StrFileCrc = "FileCrc"; - private const String StrFileSize = "FileSize [kB]"; - private const String StrUpdate = "Download"; - private const String StrErase = "Erase"; - private const String StrStatus = "Status"; - - private readonly DataGridViewCellStyle _styleInstalled = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleNotInstalled = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleInvalidMeterCrc = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleUpdateRequired = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleDownloadSucceeded = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleDownloadFailed = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleDownloadOngoing = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleValidated = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleVerificationRequired = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleEraseRequired = new DataGridViewCellStyle(); - private readonly DataGridViewCellStyle _styleCommunicationError = new DataGridViewCellStyle(); - - private Boolean _updateGui = true; - private MeterFwUpdate _meterFwUpdate; - private MeterResetPsu _meterResetPsu; - private Boolean _meterResetProcessActive; - - private String _lastFwUpdateState; - private Int32 _lastSingleProgress; - private DateTimeOffset _startTime; - private Boolean _resetTimeMeasurement; - - private static String DefaultBinaryPath - { - get => Settings.Default.InitialBinaryPath; - - set - { - Settings.Default.InitialBinaryPath = value; - Settings.Default.Save(); - } - } - - private static String DefaultPackageFile - { - get => Settings.Default.InitialPackageFile; - - set - { - Settings.Default.InitialPackageFile = value; - Settings.Default.Save(); - } - } - - private Boolean _fileAppsSuccessfulWritten; - private Boolean _rebootSucceeded; - - private Boolean _tryConnectPcb; - private Boolean _packageFileToFileAppsValidated; - - private const String StrUpdateErrorMessageManualControl = - "FW UPDATE FAILED!\n\n" + - "TIP1: Press [Update FW] button for next trial,\n " + - "this keeps all downloaded files without new download\n" + - "\nor\n" + - "TIP2: Demarcate largest or failed file and press [Update FW],\n" + - "this keeps all other downloaded files without new download.\n" + - "Download of demarcated file separately!\n" + - "\nor\n" + - "TIP3: Press [Connect] and select large files separately!\n" + - "This is a stepwise update, proceed until update is completed!\n"; - private const String StrUpdateErrorMessage = - "FW UPDATE FAILED!\n\n"; - - private enum FwSource - { - File, - // Production, - Db - } - - - private FwSource _fwSource = FwSource.File; - #endregion - #region FormControls - /// - /// Ctor FW update - /// - public FrmFwUpdate() - { - InitializeComponent(); - var cultureInfo = new CultureInfo("en-GB"); - Thread.CurrentThread.CurrentUICulture = cultureInfo; - Thread.CurrentThread.CurrentCulture = cultureInfo; - } - - /// - /// Clear data table and set genesis to not connected - /// - /// - /// - Enable update of Genesis Flow in Genesis Tool Box. - /// - private void Init() - { - ProcessViewControl(false); - lblConnectPcb.Text = StrNotConnected; - lblCoreRevision.Text = ""; - lblConnectPcb.ForeColor = Color.Red; - lblWaitingForMeterResponse.Visible = false; - btnConnect.Enabled = true; - btnUpdateFw.ForeColor = Color.DarkGreen; - btnStopFwUpdate.ForeColor = Color.Red; - SetControlDownloadsLocked(); - btnStopFwUpdate.Enabled = false; - btnOpenUpdateDirectory.Enabled = false; - btnOpenPackageControlFile.Enabled = false; - btnRebootMeter.Enabled = false; - btnClearDisplay.Enabled = false; - cbFilePartSize.Enabled = true; - _meterResetProcessActive = false; - CheckManualControl(); - if (_meterFwUpdate != null) - { - _meterFwUpdate.SingleFilePartsRetryEnable = cbxSingleFilePartsRetryEnable.Checked; - _meterFwUpdate.ConsecutiveFilePartsRetryEnable = cbxConsecutiveFilePartsRetryEnable.Checked; - _meterFwUpdate.StoreConfigEnable = cbxStoreConfigEnable.Checked; - } - - _dataTable.Rows.Clear(); - } - - private void FrmFwUpdate_Load(Object sender, EventArgs e) - { - - _styleInstalled.BackColor = Color.White; - _styleInstalled.ForeColor = Color.Black; - - _styleNotInstalled.BackColor = Color.LightGray; - _styleNotInstalled.ForeColor = Color.Black; - - _styleCommunicationError.BackColor = Color.LightPink; - _styleCommunicationError.ForeColor = Color.Red; - - _styleUpdateRequired.BackColor = Color.White; - _styleUpdateRequired.ForeColor = Color.Red; - - _styleEraseRequired.BackColor = Color.LightGray; - _styleEraseRequired.ForeColor = Color.Black; - - _styleDownloadOngoing.BackColor = Color.GreenYellow; - _styleDownloadOngoing.ForeColor = Color.Black; - - _styleValidated.BackColor = Color.White; - _styleValidated.ForeColor = Color.Green; - - _styleDownloadSucceeded.BackColor = Color.LightGreen; - _styleDownloadSucceeded.ForeColor = Color.Green; - - _styleDownloadFailed.BackColor = Color.LightPink; - _styleDownloadFailed.ForeColor = Color.Red; - - _styleVerificationRequired.BackColor = Color.LightGoldenrodYellow; - _styleVerificationRequired.ForeColor = Color.Green; - - _dataTable.Columns.Add(StrAppName, typeof(String)); - _dataTable.Columns.Add(StrAppId, typeof(String)); - _dataTable.Columns.Add(StrMeterVersion, typeof(String)); - _dataTable.Columns.Add(StrMeterCrc, typeof(String)); - _dataTable.Columns.Add(StrFileVersion, typeof(String)); - _dataTable.Columns.Add(StrFileCrc, typeof(String)); - _dataTable.Columns.Add(StrFileSize, typeof(String)); - _dataTable.Columns.Add(StrUpdate, typeof(Boolean)); - _dataTable.Columns.Add(StrErase, typeof(Boolean)); - _dataTable.Columns.Add(StrStatus, typeof(String)); - - var version = Assembly.GetExecutingAssembly().GetName().Version; - lblFwUpdateInfo.Text = $@"Version: {version.Major}.{version.Minor}.{version.Build}"; - _resetTimeMeasurement = true; - Init(); - } - - private void FrmFwUpdate_FormClosing(Object sender, FormClosingEventArgs e) - { - _currentGenesis?.Logout(); - _meterBatch?.RemoveAllMeters(); - _meterBatch?.Dispose(); - _meterFwUpdate?.Dispose(); - _meterFwUpdate = null; - Dispose(); - } - - private void cbComSlot_SelectedIndexChanged(Object sender, EventArgs e) - { - _resetTimeMeasurement = true; - Init(); - } - #endregion - #region DataGridControls - /// - /// Build data grid and fill it with meter and file information, - /// compare version and CRC of meter and file - /// - private void FillDataGridWithAllInfos() - { - _dataTable.Rows.Clear(); - if (_currentGenesis == null) - { - return; - } - - foreach (var meterApp in _currentGenesis.MeterAppListVersion) - { - var row = _dataTable.NewRow(); - //application information read from configuration - row[StrAppName] = meterApp.AppName; - row[StrAppId] = MeterFwUpdate.ConvertAppIdToString(meterApp.AppId); - - _dataTable.Rows.Add(row); - } - - //output data to data grid view - gridViewApplic.DataSource = _dataTable.DefaultView; - - foreach (DataRow dataRow in _dataTable.Rows) - { - DisplayFileAppInfo(dataRow); - DisplayMeterAppInfo(dataRow); - } - - foreach (DataGridViewColumn column in gridViewApplic.Columns) - { - column.SortMode = DataGridViewColumnSortMode.NotSortable; - } - - //color the results of the compare - UpdateInformationStyleSet(); - } - - /// - /// Set meter application information to data grid view - /// - /// - private void DisplayMeterAppInfo(DataRow dataRow) - { - if (_currentGenesis == null) - { - return; - } - - var meterStateAppInfo = new MeterAppStateInfo(); - foreach (var meterApp in _currentGenesis.MeterAppListVersion) - { - var appId = dataRow[StrAppId].ToString(); - if (appId != MeterFwUpdate.ConvertAppIdToString(meterApp.AppId)) - { - continue; - } - - dataRow[StrUpdate] = meterApp.Update; - dataRow[StrErase] = meterApp.Erase; - dataRow[StrStatus] = meterStateAppInfo.GetTextFromState(meterApp.Status); - - dataRow[StrMeterVersion] = meterApp.StrVersion; - dataRow[StrMeterCrc] = meterApp.IsInstalled ? - MeterFwUpdate.ConvertCrcToString(meterApp.Crc) : ""; - } - } - - /// - /// Set file application information to data grid view. - /// - /// - private void DisplayFileAppInfo(DataRow dataRow) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - foreach (var meterApp in _currentGenesis.MeterAppListVersion) - { - var appId = dataRow[StrAppId].ToString(); - if (appId != MeterFwUpdate.ConvertAppIdToString(meterApp.AppId)) - { - continue; - } - - foreach (var fileApp in _meterFwUpdate.FileApps) - { - if (meterApp.AppId != fileApp.AppId) - { - continue; - } - - dataRow[StrFileVersion] = fileApp.StrVersion; - dataRow[StrFileCrc] = MeterFwUpdate.ConvertCrcToString(fileApp.Crc); - dataRow[StrFileSize] = $"{(Double)fileApp.BinData.Count / 1024:0.000}"; - } - } - } - - /// - /// Display the update information - /// - private void UpdateInformationStyleSet() - { - foreach (DataGridViewRow dataGridRow in gridViewApplic.Rows) - { - var testString = dataGridRow.Cells[StrStatus].Value.ToString(); - var meterAppStateInfo = new MeterAppStateInfo(); - var meterAppState = meterAppStateInfo.GetStateFromText(testString); - - switch (meterAppState) - { - case MeterAppState.MeterAppInstalledUnchecked: - dataGridRow.DefaultCellStyle = _styleInstalled; - break; - case MeterAppState.MeterAppUpToDate: - dataGridRow.DefaultCellStyle = _styleValidated; - break; - case MeterAppState.MeterAppInstallationRequired: - dataGridRow.DefaultCellStyle = _styleUpdateRequired; - break; - case MeterAppState.FileAppInvalid: - dataGridRow.DefaultCellStyle = _styleNotInstalled; - break; - case MeterAppState.MeterAppVersionOutdated: - dataGridRow.DefaultCellStyle = _styleUpdateRequired; - break; - case MeterAppState.Unknown: - dataGridRow.DefaultCellStyle = _styleCommunicationError; - break; - case MeterAppState.MeterAppNotInstalled: - dataGridRow.DefaultCellStyle = _styleNotInstalled; - break; - case MeterAppState.MeterAppNotRequired: - dataGridRow.DefaultCellStyle = _styleNotInstalled; - break; - case MeterAppState.InvalidCrc: - dataGridRow.DefaultCellStyle = _styleInvalidMeterCrc; - break; - case MeterAppState.MeterAppErasureRequired: - dataGridRow.DefaultCellStyle = _styleEraseRequired; - break; - case MeterAppState.MeterAppDownloadSucceeded: - dataGridRow.DefaultCellStyle = _styleDownloadSucceeded; - break; - case MeterAppState.MeterAppDownloadSuspicious: - dataGridRow.DefaultCellStyle = _styleVerificationRequired; - break; - case MeterAppState.MeterAppSuccessfulErased: - dataGridRow.DefaultCellStyle = _styleDownloadSucceeded; - break; - case MeterAppState.MeterAppSuccessfulUpdated: - dataGridRow.DefaultCellStyle = _styleDownloadSucceeded; - break; - case MeterAppState.MeterAppDownloadFailed: - dataGridRow.DefaultCellStyle = _styleDownloadFailed; - break; - case MeterAppState.MeterAppDownloadActive: - dataGridRow.DefaultCellStyle = _styleDownloadOngoing; - break; - default: - dataGridRow.DefaultCellStyle = _styleNotInstalled; - break; - } - } - } - - /// - /// After changing the grid view cell and data row cell contents, the meter application lists - /// have to be updated with the required action (erase or update or none of them) - /// - /// - private void SetMeterAppEraseUpdateInfo(DataRow dataRow) - { - foreach (var meterApp in _currentGenesis.MeterAppListVersion) - { - var appId = dataRow[StrAppId].ToString(); - if (appId != MeterFwUpdate.ConvertAppIdToString(meterApp.AppId)) - { - continue; - } - - meterApp.Update = (Boolean)dataRow[StrUpdate]; - meterApp.Erase = (Boolean)dataRow[StrErase]; - } - //force upgrade file generation on update cell click - _meterFwUpdate?.AssignGenesis(_currentGenesis); - } - - /// - /// Overwrite cell click, because edit of cells is denied. This is needed for update and/or erase selection - /// - /// - /// - private void gridViewApplic_CellClick(Object sender, DataGridViewCellEventArgs e) - { - if (e.ColumnIndex <= 0 || !btnOpenUpdateDirectory.Enabled || - !cbxManualControl.Checked) - { - return; - } - - var dataRow = _dataTable.Rows[e.RowIndex]; - if (gridViewApplic.Columns[e.ColumnIndex].Name == StrUpdate) - { - //deny update if update file invalid - if (dataRow[StrFileVersion].ToString() == "") - { - dataRow[StrUpdate] = false; - } - else - { - //toggle selection - dataRow[StrUpdate] = (Boolean)dataRow[StrUpdate] != true; - } - - //remove erase selection - if ((Boolean)dataRow[StrUpdate]) - { - dataRow[StrErase] = false; - } - - } - if (gridViewApplic.Columns[e.ColumnIndex].Name == StrErase) - { - //deny erase if meter file not installed or the essential applications - //needed to keep the operation for the update procedure alive - if (dataRow[StrMeterVersion].ToString() == "" - || dataRow[StrAppName].ToString() == "SYSTEM" - || dataRow[StrAppName].ToString() == "CONFIGEXCHANGE" - || dataRow[StrAppName].ToString() == "IRDA") - { - dataRow[StrErase] = false; - } - else - { - //toggle selection - dataRow[StrErase] = (Boolean)dataRow[StrErase] != true; - } - - //remove update selection - if ((Boolean)dataRow[StrErase]) - { - dataRow[StrUpdate] = false; - } - } - //update information in meter application list - SetMeterAppEraseUpdateInfo(dataRow); - CheckUpdateEnabled(); - } - #endregion - #region ActivationControls - private void SetControlDownloadsUnlocked() - { - btnUpdateFw.Enabled = true; - btnStopFwUpdate.Enabled = false; - - btnDownloadFiles.Enabled = true; - if (cbxSingleFilePartsRetryEnable.Checked) - { - btnRetryFileParts.Enabled = CheckRetryPossible(); - } - - btnVerifyFiles.Enabled = true; - - btnPrepareTrigger.Enabled = true; - btnTriggerUpdate.Enabled = true; - btnClearDisplay.Enabled = true; - btnRebootMeter.Enabled = true; - } - - private void SetControlDownloadsLocked() - { - btnUpdateFw.Enabled = false; - btnStopFwUpdate.Enabled = false; - - btnDownloadFiles.Enabled = false; - btnRetryFileParts.Enabled = false; - btnVerifyFiles.Enabled = false; - - btnPrepareTrigger.Enabled = false; - btnTriggerUpdate.Enabled = false; - btnConnect.Enabled = true; - - } - - private void SetControlsDownloadIsOngoing() - { - cbFilePartSize.Enabled = false; - cbAddRetryTimeout.Enabled = false; - btnConnect.Enabled = false; - btnOpenUpdateDirectory.Enabled = false; - btnOpenPackageControlFile.Enabled = false; - - btnUpdateFw.Enabled = false; - btnStopFwUpdate.Enabled = true; - - btnDownloadFiles.Enabled = false; - btnRetryFileParts.Enabled = false; - btnVerifyFiles.Enabled = false; - - btnPrepareTrigger.Enabled = false; - btnTriggerUpdate.Enabled = false; - btnClearDisplay.Enabled = false; - btnRebootMeter.Enabled = false; - } - - private void SetControlsDownloadIsFinished() - { - _currentGenesis.RequestProtocol.AdditionalRetryTimeoutMs = 0; - cbFilePartSize.Enabled = true; - cbAddRetryTimeout.Enabled = true; - btnConnect.Enabled = true; - btnOpenUpdateDirectory.Enabled = true; - btnOpenPackageControlFile.Enabled = true; - - btnUpdateFw.Enabled = true; - btnStopFwUpdate.Enabled = false; - - btnDownloadFiles.Enabled = true; - if (cbxSingleFilePartsRetryEnable.Checked) - { - btnRetryFileParts.Enabled = CheckRetryPossible(); - } - - btnVerifyFiles.Enabled = true; - - btnPrepareTrigger.Enabled = true; - btnTriggerUpdate.Enabled = true; - btnClearDisplay.Enabled = true; - btnRebootMeter.Enabled = true; - } - #endregion - #region BoardControls - private void WaitForReboot() - { - if (_currentGenesis == null) - { - return; - } - - lblActualProcess.Text = StrBurnUpgrade; - lblOverall.Text = @"Update Applications and Restart Meter"; - ProcessViewControl(true); - - //wait until meter update is completed and meter is re-booted - const Double maxBootDelayMs = 10000.0; - const Double loopDelayMs = 200.0; - var bootDelayMs = 0.0; - var text = ""; - while (bootDelayMs < maxBootDelayMs && string.IsNullOrEmpty(text)) - { - SetTimeDisplay(); - - text = RegisterConverter.ByteArrayToValue( - _currentGenesis.ReadRegister(Register.Configexchange.PcbSerialNumber, 12)); - barOverallProgressUpdate.Value = barOverallProgressUpdate.Value + 4 > 100 ? 0 : - barOverallProgressUpdate.Value + 4; - var bootProcess = (Int32)(bootDelayMs * 100.0 / maxBootDelayMs); - barSingleProgressUpdate.Value = barSingleProgressUpdate.Value + bootProcess > 100 ? 0 : - barSingleProgressUpdate.Value + bootProcess; - Update(); - bootDelayMs += loopDelayMs; - Thread.Sleep((Int32)loopDelayMs); - } - - ProcessViewControl(false); - } - private void Connect() - { - try - { - Init(); - - if (string.IsNullOrEmpty(cbComSlot.SelectedItem.ToString()) || - !int.TryParse(cbComSlot.SelectedItem.ToString(), out var slotNr)) - { - return; - } - - lblOverall.Text = @"Establish data base connection.."; - - Task.Factory.StartNew(() => - { - ViewProgressPcb(true); - - _currentGenesis?.DisposeMeter(); - //dispose old meter - _meterBatch.RemoveAllMeters(); - _currentGenesis = null; - Thread.Sleep(200); - - //assign new meter and assign meter to FW update file if this exists - _currentGenesis = new GenesisMeter(); - - _currentGenesis.SetupFromConfigFile(slotNr); - _meterBatch.AddMeter(_currentGenesis); - - _currentGenesis.Configuration.UseRegisterWatchService = false; - _currentGenesis.Configuration.UseMinMaxCheck = false; - _meterBatch.MetersLogin(); - - if (!_currentGenesis.IsLoggedOn) - { - Thread.Sleep(2000); - _meterBatch.MetersLogin(); - } - - if (!_currentGenesis.IsLoggedOn) - { - return; - } - - _meterFwUpdate?.AssignGenesis(_currentGenesis); - - Invoke(new Action(() => - { - lblConnectPcb.Text = StrPartPcbConnected + _currentGenesis.PcbId; - lblCoreRevision.Text = StrCoreRevision + _currentGenesis.StrCoreRevision; - lblConnectPcb.ForeColor = Color.Green; - _meterFwUpdate?.CompareAllMeterAndFileApps(true); - FillDataGridWithAllInfos(); - CheckUpdateEnabled(); - _currentGenesis.Logout(); - })); - }).ContinueWith(delegate - { - ViewProgressPcb(false); - }); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message); - } - } - #endregion - #region ProcessControls - private void ViewProgressPcb(Boolean isActive) - { - if (_updateGui) - { - if (isActive) - { - Invoke(new Action(() => - { - _tryConnectPcb = true; - ProcessViewControl(true); - lblActualProcess.Text = StrConnecting; - lblOverall.Text = StrOverallProcess; - })); - } - else - { - Invoke(new Action(() => - { - _tryConnectPcb = false; - ProcessViewControl(false); - })); - } - } - } - - /// - /// View all process bars and labels - /// - private void ProcessViewControl(Boolean view) - { - if (view) - { - lblActualProcess.Visible = true; - lblOverall.Visible = true; - barOverallProgressUpdate.Visible = true; - barSingleProgressUpdate.Visible = true; - _lastFwUpdateState = ""; - tmrProgressUpdate.Enabled = true; - } - else - { - lblActualProcess.Visible = false; - lblOverall.Visible = false; - barOverallProgressUpdate.Visible = false; - barSingleProgressUpdate.Visible = false; - tmrProgressUpdate.Enabled = false; - _lastFwUpdateState = ""; - lblWaitingForMeterResponse.Visible = false; - lblActualProcess.Text = ""; - lblOverall.Text = ""; - } - - //common actions and settings - lblActualProcess.Update(); - lblOverall.Update(); - barOverallProgressUpdate.Value = 0; - barOverallProgressUpdate.Update(); - barSingleProgressUpdate.Value = 0; - barSingleProgressUpdate.Update(); - Update(); - } - - private void AutomaticUpdateControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - SetControlsDownloadIsFinished(); - - if (_fileAppsSuccessfulWritten) - { - WaitForReboot(); - btnConnect_Click(this, null); - - //var genesisStatus = new GenesisStatus(); - //if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, genesisStatus)) - //{ - - // var sbMSG = new StringBuilder(); - // sbMSG.AppendLine($"Pcb = {_currentGenesis.PcbId}"); - - // sbMSG.AppendLine($"POWERMON_TotalUsedSeconds = {genesisStatus.ExceededLifeTime_s}"); - // sbMSG.AppendLine($"POWERMON_TotalUsedCharge = {genesisStatus.DrainedBatteryLoad_uAs}"); - // sbMSG.AppendLine($"POWERMON_BatteryQuantity = {genesisStatus.BatteryQuantity}"); - // sbMSG.AppendLine($"POWERMON_BatteryMilliAHrRating = {genesisStatus.InitialBatteryLoad_mAh}"); - // sbMSG.AppendLine(""); - // sbMSG.AppendLine($"Remaining life time in years {genesisStatus.RemainingLifeTimeYears:F2}"); - // sbMSG.AppendLine($"Totally drained battery load in % {genesisStatus.DrainedBatteryLoadPercent:F2}"); - // _currentGenesis.WriteLog(sbMSG.ToString()); - // MessageBox.Show(sbMSG.ToString(), $"Battery for {_currentGenesis.PcbId} has remaining life " + - // $"time of {genesisStatus.RemainingLifeTimeYears:F2} years"); - //} - //else - //{ - // MessageBox.Show("Failed to get life time information"); - //} - } - else - { - var requiredUpdates = _meterFwUpdate?.GetFailedFileApps(); - var text = cbxManualControl.Checked ? - StrUpdateErrorMessageManualControl : StrUpdateErrorMessage; - if (requiredUpdates != null && requiredUpdates.Count > 0) - { - text += "\nFailed to update application:\n"; - FileApplications lastFile = null; - foreach (var file in requiredUpdates) - { - if (lastFile == null || lastFile.AppId != file.AppId) - { - text += $"{file.AppName}\n"; - } - - lastFile = file; - } - } - MessageBoxShow(text, @"FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - })); - } - } - private void UploadFileControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - SetControlsDownloadIsFinished(); - })); - } - } - - private void RebootControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - _lastFwUpdateState = ""; - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - SetControlsDownloadIsFinished(); - - if (_rebootSucceeded) - { - WaitForReboot(); - btnConnect_Click(this, null); - } - else - { - MessageBoxShow("Reboot failed!", @"FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - })); - } - } - - private void DownloadFilesControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - _lastFwUpdateState = ""; - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - SetControlsDownloadIsFinished(); - - if (!_fileAppsSuccessfulWritten) - { - var text = "DOWNLOAD FAILED!\n\n" + - "If some files marked red:\n" + - "Download these files by select \"Download\" and\n" + - "then press \"Manual Download\" [Entire File Parts]!\n\n"; - var requiredUpdates = _meterFwUpdate?.GetFailedFileApps(); - if (requiredUpdates != null && requiredUpdates.Count > 0) - { - text += "\nFailed to update application:\n"; - FileApplications lastFile = null; - foreach (var file in requiredUpdates) - { - if (lastFile == null || lastFile.AppId != file.AppId) - { - text += $"{file.AppName}\n"; - } - - lastFile = file; - } - } - MessageBoxShow(text, @"FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - else - { - const String text = "Download of selected files succeeded!\n\n" + - "If some files marked red:\n" + - "Download these files by select \"Download\" and\n" + - "then press \"Manual Download\" [Entire File Parts]!\n\n" + - "If all files are downloaded:\n" + - "Select all green marked files \"Download\"," + - "all red written \"Erasure required\" with \"Erase\" and\n" + - "press [Trigger Upgrade] to finalize the update!"; - MessageBoxShow(text, @"SUCCESS", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - })); - } - } - - private void TriggerUpdateControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - _lastFwUpdateState = ""; - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - SetControlsDownloadIsFinished(); - - if (_fileAppsSuccessfulWritten) - { - const String text = "Trigger upgrade succeeded!\n\n" + - "Press [Connect] for verification!"; - MessageBoxShow(text, @"SUCCESS", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - else - { - const String text = "TRIGGER UPGRADE FAILED\n\n" + - "FW installation may be successfully executed!\n\n" + - "Press [Connect] for verification!"; - - MessageBoxShow(text, @"Verification Needed", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - })); - } - } - - - private void VerifyFilesControl(Boolean isActive) - { - if (isActive) - { - Invoke(new Action(() => - { - _lastFwUpdateState = ""; - ProcessViewControl(true); - tmrProgressUpdate.Enabled = true; - })); - } - else - { - Invoke(new Action(() => - { - ProcessViewControl(false); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - SetControlsDownloadIsFinished(); - - if (!_fileAppsSuccessfulWritten) - { - const String text = "VERIFICATION FAILED!\n\n" + - "If some files marked red:\n" + - "Download these files by select \"Download\" and\n" + - "then press \"Manual Download\" [Entire File Parts]!"; - MessageBoxShow(text, @"FAILED", MessageBoxButtons.OK, - MessageBoxIcon.Error); - } - else - { - const String text = "Verification of selected files succeeded!\n\n" + - "If some files marked red:\n" + - "Download these files by select \"Download\" and\n" + - "then press \"Manual Download\" [Entire File Parts]!\n\n" + - "If all files are verified green:\n" + - "Select all green marked files \"Download\"," + - "all red written \"Erasure required\" with \"Erase\" and\n" + - "press [Trigger Upgrade] to finalize the update!"; - MessageBoxShow(text, @"SUCCESS", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - })); - } - } - #endregion - #region Checks - private void CheckManualControl() - { - if (cbxManualControl.Checked) - { - gbxManualDownload.Visible = true; - gbxManualTriggerControl.Visible = true; - gbxManualUploadAndVerification.Visible = true; - gbxManualTimingControl.Visible = true; - } - else - { - gbxManualDownload.Visible = false; - gbxManualTriggerControl.Visible = false; - gbxManualUploadAndVerification.Visible = false; - gbxManualTimingControl.Visible = false; - } - } - private Boolean CheckUpdateEnabled() - { - // enable file load and fix battery only if device is connected - if (_currentGenesis != null) - { - btnOpenUpdateDirectory.Enabled = true; - btnOpenPackageControlFile.Enabled = true; - btnClearDisplay.Enabled = true; - btnRebootMeter.Enabled = true; - } - else - { - btnOpenUpdateDirectory.Enabled = false; - btnOpenPackageControlFile.Enabled = false; - btnClearDisplay.Enabled = false; - btnRebootMeter.Enabled = false; - } - - //this check has been placed here to force display update - if (_meterFwUpdate != null && _currentGenesis != null && - // avoid activation on invalidated update files - _packageFileToFileAppsValidated && - // avoid activation on unknown app installation status due to communication error - _currentGenesis.MeterAppListVersion.All( f => f.Status != MeterAppState.Unknown) && - // check if anything has to be updated or erased - (_currentGenesis.MeterAppListVersion.Any(f => f.Update) || - _currentGenesis.MeterAppListVersion.Any(f => f.Erase))) - { - SetControlDownloadsUnlocked(); - return true; - } - - SetControlDownloadsLocked(); - return false; - } - - private Boolean CheckRetryPossible() - { - return _meterFwUpdate != null && _meterFwUpdate.GetFailedFileApps().Count > 0; - } - - private void CheckPackageFileAndFileApps() - { - try - { - _packageFileToFileAppsValidated = _meterFwUpdate.ValidateFileAppsWithPackageFile(); - } - catch (Exception) - { - if (_meterFwUpdate.CoreRevisionMaximum == null) - MessageBoxShow(@"Core Revision missing in ADF!", @"FAILED", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - if (_packageFileToFileAppsValidated) - { - lblPackageFileLoadedInfo.ForeColor = Color.Green; - lblPackageFileLoadedInfo.Text = StrPackageFileLoaded; - } - else - { - lblPackageFileLoadedInfo.ForeColor = Color.Red; - lblPackageFileLoadedInfo.Text = StrPackageFileToFileAppsMismatch; - } - } - #endregion - #region TimerControls - private void tmrProgressUpdate_Tick(Object sender, EventArgs e) - { - //cbxManualControl.Visible = true; - if (_tryConnectPcb) - { - barOverallProgressUpdate.Value = barOverallProgressUpdate.Value + 1 > 100 ? 0 : - barOverallProgressUpdate.Value + 1; - barSingleProgressUpdate.Value = barSingleProgressUpdate.Value + 4 > 100 ? 0 : - barSingleProgressUpdate.Value + 4; - lblOverall.Text = StrOverallProcess; - } - else if (_meterResetProcessActive) - { - lblOverall.Text = @"Reboot Meter with pseudo FW update"; - lblActualProcess.Text = _meterResetPsu?.GetActualOperation(); - - barOverallProgressUpdate.Value = barOverallProgressUpdate.Value + 1 > 100 ? 0 : - barOverallProgressUpdate.Value + 1; - barOverallProgressUpdate.Update(); - - var singleProgress = (Int32)(_meterResetPsu?.SingleFileProcessCtrPercent ?? 0); - barSingleProgressUpdate.Value = singleProgress > 100 ? 100 : singleProgress; - _lastSingleProgress = singleProgress; - barSingleProgressUpdate.Update(); - } - else - { - lblOverall.Text = _meterFwUpdate?.GetFwUpdateStateOperation(); - lblActualProcess.Text = _meterFwUpdate?.GetActualOperation(); - - var overallProgress = (Int32)(_meterFwUpdate?.OverallProcessCtrPercent ?? 0); - barOverallProgressUpdate.Value = overallProgress > 100 ? 100 : overallProgress; - barOverallProgressUpdate.Update(); - - var singleProgress = (Int32)(_meterFwUpdate?.SingleFileProcessCtrPercent ?? 0); - if (_meterFwUpdate?.GetFwUpdateStateOperation() != _lastFwUpdateState) - { - _lastFwUpdateState = _meterFwUpdate?.GetFwUpdateStateOperation(); - FillDataGridWithAllInfos(); - lblWaitingForMeterResponse.Visible = false; - } - else - { - //if the communication gets frozen - lblWaitingForMeterResponse.Visible = _lastSingleProgress == singleProgress; - } - - barSingleProgressUpdate.Value = singleProgress > 100 ? 100 : singleProgress; - _lastSingleProgress = singleProgress; - barSingleProgressUpdate.Update(); - } - SetTimeDisplay(); - if (_currentGenesis != null) - cbxMetUpdPerm.Checked = (_currentGenesis.MetrologyUpgradePermission & 0xFF) == 0xFF; - Update(); - } - - private void SetTimeDisplay() - { - var time = DateTimeOffset.UtcNow; - var timeSpan = time - _startTime; - lblUpdateTime.Text = $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}"; - } - #endregion - #region Buttons - private void btnConnect_Click(Object sender, EventArgs e) - { - if (_resetTimeMeasurement) - { - _startTime = DateTimeOffset.UtcNow; - _resetTimeMeasurement = false; - } - Connect(); - - } - - /// - /// - Enable update of Genesis Flow in Genesis Tool Box. - /// - private void btnOpenPackageControlFile_Click(Object sender, EventArgs e) - { - //reload last path - dlgOpenPackageFile.InitialDirectory = DefaultPackageFile; - - dlgOpenPackageFile.DefaultExt = ".txt"; - if (dlgOpenPackageFile.ShowDialog() != DialogResult.OK) - { - return; - } - - var packageFilePathName = dlgOpenPackageFile.FileName; - - DefaultPackageFile = packageFilePathName; - - //copy path to binary file load - //if (String.IsNullOrEmpty(DefaultBinaryPath)) - // dlgBinaryPath.SelectedPath = Path.GetFullPath(packageFilePathName); - - tbxPackageFilePathName.Text = packageFilePathName; - if (_meterFwUpdate == null) - { - _meterFwUpdate = new MeterFwUpdate(_currentGenesis, true); - } - - if (_meterFwUpdate == null) - { - return; - } - - if (_meterFwUpdate.LoadPackageFile(packageFilePathName)) - { - CheckPackageFileAndFileApps(); - } - else - { - lblPackageFileLoadedInfo.ForeColor = Color.Red; - lblPackageFileLoadedInfo.Text = StrPackageFileInvalid; - } - - CheckUpdateEnabled(); - _resetTimeMeasurement = true; - } - - private void btnOpenUpdateDirectory_Click(Object sender, EventArgs e) - { - //reload last file path - dlgBinaryPath.SelectedPath = DefaultBinaryPath; - - if (dlgBinaryPath.ShowDialog() != DialogResult.OK) - { - return; - } - - var binaryFilePath = dlgBinaryPath.SelectedPath; - DefaultBinaryPath = binaryFilePath; - - //copy path to package file load - //if (String.IsNullOrEmpty(DefaultPackageFile)) - // dlgOpenPackageFile.InitialDirectory = binaryFilePath; - - tbxBinaryPath.Text = binaryFilePath; - - var meterFwUpdate = new MeterFwUpdate(_currentGenesis, true); - if (meterFwUpdate.LoadFileApps(binaryFilePath)) - { - lblFileAppsLoadedInfo.ForeColor = Color.Green; - lblFileAppsLoadedInfo.Text = StrFileAppsLoaded; - } - else - { - lblFileAppsLoadedInfo.ForeColor = Color.Red; - lblFileAppsLoadedInfo.Text = StrUpdatePathEmpty; - } - - - - PushFwFile(meterFwUpdate); - } - - /// - /// Upload selected update files and compare them with update files - /// - /// - /// - private void btnVerifyFiles_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - SetControlsDownloadIsOngoing(); - VerifyFilesControl(true); - - Task.Factory.StartNew(() => - { - _fileAppsSuccessfulWritten = _meterFwUpdate.ManualVerifyFiles(); - Thread.Sleep(1000); - }).ContinueWith(delegate - { - VerifyFilesControl(false); - }); - - } - - /// - /// Download selected update files entirely - /// - /// - /// - private void btnDownloadFiles_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - Int32 fileSizePartKb; - Int32 addRetryTimeoutMs; - if (string.IsNullOrEmpty(cbFilePartSize.SelectedItem.ToString()) || - !int.TryParse(cbFilePartSize.SelectedItem.ToString(), out fileSizePartKb)) - { - return; - } - - _meterFwUpdate.MaxPartialFileDataSize = fileSizePartKb * 1024; - - if (string.IsNullOrEmpty(cbAddRetryTimeout.SelectedItem.ToString()) || - !int.TryParse(cbAddRetryTimeout.SelectedItem.ToString(), out addRetryTimeoutMs)) - { - return; - } - - _currentGenesis.RequestProtocol.AdditionalRetryTimeoutMs = addRetryTimeoutMs; - - SetControlsDownloadIsOngoing(); - DownloadFilesControl(true); - - Task.Factory.StartNew(() => - { - _fileAppsSuccessfulWritten = _meterFwUpdate.ManualDownloadAllFileApps(); - Thread.Sleep(1000); - }).ContinueWith(delegate - { - DownloadFilesControl(false); - }); - - } - - /// - /// Reboot the meter - /// - /// - /// - /// - /// - Initial - /// - private void btnRebootMeter_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null) - { - return; - } - - // reset the time - _startTime = DateTimeOffset.UtcNow; - - // clear display before store configuration - btnClearDisplay_Click(this, null); - - _meterResetPsu = new MeterResetPsu(_currentGenesis) - { - StoreConfigEnable = cbxStoreConfigEnable.Checked - }; - - _meterResetProcessActive = true; - - SetControlsDownloadIsOngoing(); - RebootControl(true); - - Task.Factory.StartNew(() => - { - _rebootSucceeded = _meterResetPsu.MeterResetPsuExecute(); - Thread.Sleep(1000); - _meterResetProcessActive = false; - - }).ContinueWith(delegate - { - RebootControl(false); - _meterResetPsu.Dispose(); - }); - - } - - /// - /// Download remaining files parts from previous download - /// - /// - /// - private void btnRetryFileParts_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - SetControlsDownloadIsOngoing(); - DownloadFilesControl(true); - - Task.Factory.StartNew(() => - { - _fileAppsSuccessfulWritten = _meterFwUpdate.ManualDownloadRemainingFileAppsParts(); - Thread.Sleep(1000); - }).ContinueWith(delegate - { - DownloadFilesControl(false); - }); - } - - /// - /// Preselect "Download" trigger marks on successfully downloaded files - /// - /// - /// - private void btnPrepareTrigger_Click(Object sender, EventArgs e) - { - if (_meterFwUpdate == null) - { - return; - } - - _meterFwUpdate.PrepareTrigger(); - FillDataGridWithAllInfos(); - } - - /// - /// Build update control file and trigger update of selected files - /// - /// - /// - private void btnTriggerUpdate_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - SetControlsDownloadIsOngoing(); - TriggerUpdateControl(true); - - Task.Factory.StartNew(() => - { - _fileAppsSuccessfulWritten = _meterFwUpdate.ManualTriggerUpgrade(); - Thread.Sleep(1000); - }).ContinueWith(delegate - { - TriggerUpdateControl(false); - }); - - } - - /// - /// Automatic update of entire FW - /// - /// - /// - private void btnUpdateFw_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null || _meterFwUpdate?.FileApps == null) - { - return; - } - - if (_meterFwUpdate != null) - _meterFwUpdate.StoreConfigEnable = cbxStoreConfigEnable.Checked; - - SetControlsDownloadIsOngoing(); - AutomaticUpdateControl(true); - - _startTime = DateTimeOffset.UtcNow; - _resetTimeMeasurement = false; - - if (string.IsNullOrEmpty(cbFilePartSize.SelectedItem.ToString()) || - !int.TryParse(cbFilePartSize.SelectedItem.ToString(), out var fileSizePartKb)) - { - return; - } - - _meterFwUpdate.MaxPartialFileDataSize = fileSizePartKb * 1024; - - if (string.IsNullOrEmpty(cbAddRetryTimeout.SelectedItem.ToString()) || - !int.TryParse(cbAddRetryTimeout.SelectedItem.ToString(), out var addRetryTimeoutMs)) - { - return; - } - - - - //_currentGenesis.WriteRegister("POWERMON_BatteryQuantity", 2, true, true); - //_currentGenesis.WriteRegister("POWERMON_StoreConfiguration", 1, true, true); - //var genesisStatus = new GenesisStatus(); - //if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, genesisStatus)) - //{ - - // var sbMSG = new StringBuilder(); - // sbMSG.AppendLine($"Pcb = {_currentGenesis.PcbId}"); - - // sbMSG.AppendLine($"POWERMON_TotalUsedSeconds = {genesisStatus.ExceededLifeTime_s}"); - // sbMSG.AppendLine($"POWERMON_TotalUsedCharge = {genesisStatus.DrainedBatteryLoad_uAs}"); - // sbMSG.AppendLine($"POWERMON_BatteryQuantity = {genesisStatus.BatteryQuantity}"); - // sbMSG.AppendLine($"POWERMON_BatteryMilliAHrRating = {genesisStatus.InitialBatteryLoad_mAh}"); - // sbMSG.AppendLine(""); - // sbMSG.AppendLine($"Remaining life time in years {genesisStatus.RemainingLifeTimeYears:F2}"); - // sbMSG.AppendLine($"Totally drained battery load in % {genesisStatus.DrainedBatteryLoadPercent:F2}"); - // _currentGenesis.WriteLog(sbMSG.ToString()); - // MessageBox.Show(sbMSG.ToString(), $"Battery for {_currentGenesis.PcbId} has remaining life " + - // $"time of {genesisStatus.RemainingLifeTimeYears:F2} years"); - //} - //else - //{ - // MessageBox.Show("Failed to get life time information"); - //} - _currentGenesis.RequestProtocol.AdditionalRetryTimeoutMs = addRetryTimeoutMs; - - Task.Factory.StartNew(() => - { - _fileAppsSuccessfulWritten = _meterFwUpdate.UpdateMeterFw(); - Thread.Sleep(1000); - }).ContinueWith(delegate - { - - AutomaticUpdateControl(false); - }); - } - - private void btnStopFwUpdate_Click(Object sender, EventArgs e) - { - if (_meterFwUpdate != null) - { - _meterFwUpdate.StopUpdateProcess = true; - } - } - - private void btnResetAll_Click(Object sender, EventArgs e) - { - btnStopFwUpdate_Click(this, null); - _meterFwUpdate?.ResetAll(); - Init(); - FillDataGridWithAllInfos(); - CheckUpdateEnabled(); - _currentGenesis.Logout(); - } - #endregion - #region CheckBoxes - /// - /// File parts are consecutive, stop process at first failed part and retry from - /// this part on all others behind - /// - /// - /// - private void cbxConsecutiveFilePartsRetryEnable_CheckedChanged(Object sender, EventArgs e) - { - if (_meterFwUpdate == null) - { - return; - } - - _meterFwUpdate.ConsecutiveFilePartsRetryEnable = - cbxConsecutiveFilePartsRetryEnable.Checked; - cbxSingleFilePartsRetryEnable.Checked = false; - } - /// - /// Failed file parts are somewhere in between succeeded parts and will be retried separately - /// - /// - /// - private void cbxSingleFilePartsRetryEnable_CheckedChanged(Object sender, EventArgs e) - { - if (_meterFwUpdate == null) - { - return; - } - - _meterFwUpdate.SingleFilePartsRetryEnable = - cbxSingleFilePartsRetryEnable.Checked; - cbxConsecutiveFilePartsRetryEnable.Checked = false; - } - - /// - /// Activation of manual control buttons for individual update procedure - /// - /// - /// - private void cbxManualControl_CheckedChanged_1(Object sender, EventArgs e) - { - CheckManualControl(); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - CheckUpdateEnabled(); - } - #endregion - - #region ExternalCalls - - /// - /// - /// - /// - /// - /// - /// - /// - /// - Enable update of Genesis Flow in Genesis Tool Box. - /// - public Boolean Prepare(Int32 slot, String binaryPath, String packageInfoPath) - { - _updateGui = false; - cbComSlot.SelectedItem = slot; - - Connect(); - var retries = 40; - while (_currentGenesis == null || !_currentGenesis.IsLoggedOn) - { - Thread.Sleep(1000); - retries -= 1; - if (retries <= 0) - { - return false; - } - } - - Thread.Sleep(10000); - tbxBinaryPath.Text = binaryPath; - if (_meterFwUpdate == null) - { - _meterFwUpdate = new MeterFwUpdate(_currentGenesis, true); - } - - if (_meterFwUpdate == null) - { - return false; - } - - if (_meterFwUpdate.LoadFileApps(binaryPath)) - { - lblFileAppsLoadedInfo.ForeColor = Color.Green; - lblFileAppsLoadedInfo.Text = StrFileAppsLoaded; - } - else - { - lblFileAppsLoadedInfo.ForeColor = Color.Red; - lblFileAppsLoadedInfo.Text = StrUpdatePathEmpty; - } - - CheckPackageFileAndFileApps(); - - if (_currentGenesis == null) - { - return false; - } - - _meterFwUpdate?.AssignGenesis(_currentGenesis); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - - - tbxPackageFilePathName.Text = packageInfoPath; - if (_meterFwUpdate == null) - { - _meterFwUpdate = new MeterFwUpdate(_currentGenesis, true); - } - - if (_meterFwUpdate == null) - { - return false; - } - - if (_meterFwUpdate.LoadPackageFile(packageInfoPath)) - { - CheckPackageFileAndFileApps(); - } - else - { - lblPackageFileLoadedInfo.ForeColor = Color.Red; - lblPackageFileLoadedInfo.Text = StrPackageFileInvalid; - } - - if (CheckUpdateEnabled()) - { - btnUpdateFw_Click(null, EventArgs.Empty); - //wait for response return e - - return true; - } - - return false; - - - - } - /// - /// - /// - public MsgEventargs LastMsgEvent; - /// - /// - /// - public class MsgEventargs : EventArgs - { - /// - /// Message text - /// - public String Text; - /// - /// Title - /// - public String Caption; - /// - /// Buttons - /// - public MessageBoxButtons Buttons; - /// - /// The Icon - /// - public MessageBoxIcon Icon; - } - /// - /// - /// - public event EventHandler OnMsgPopUp; - private void MessageBoxShow(String text, String caption, MessageBoxButtons buttons, MessageBoxIcon icon) - { - if (_updateGui) - { - MessageBox.Show(text, caption, buttons, icon); - } - else - { - LastMsgEvent = new MsgEventargs() { Text = text, Caption = caption, Buttons = buttons, Icon = icon }; - OnMsgPopUp?.Invoke(this, LastMsgEvent); - } - - } - - - #endregion - #region User Controls - private void tabDb_Click(Object sender, EventArgs e) - { - - } - - private void tabFwSources_SelectedIndexChanged(Object sender, EventArgs e) - { - if (tabFwSources.SelectedTab == tabLocalFile) - { - CheckSelectionChanged(FwSource.File); - } - else if (tabFwSources.SelectedTab == tabProduction) - { - CheckSelectionChanged(FwSource.File); - } - else if (tabFwSources.SelectedTab == tabDb) - { - CheckSelectionChanged(FwSource.Db); - - - cbxFilterFwDn.Items.Add("ALL"); - - cbxFilterFWType.Items.Add("ALL"); - - foreach (MeterSize suit in (MeterSize[])Enum.GetValues(typeof(MeterSize))) - { - cbxFilterFwDn.Items.Add(suit); - } - cbxFilterFwDn.SelectedIndex = 0; - - cbxFilterFWType.Items.Add(CordonelFWTypeEnum.EMEA_433); - cbxFilterFWType.Items.Add(CordonelFWTypeEnum.EMEA_868); - cbxFilterFWType.Items.Add(CordonelFWTypeEnum.NA); - cbxFilterFWType.SelectedIndex = 0; - - - btnGetFWFiles_Click(sender, e); - - } - else - { - MessageBox.Show(@"No Tab selected!"); - } - - //tabFwSources.SelectedTab = tabLocalFile; - } - - - private void CheckSelectionChanged(FwSource newSource) - { - // ReSharper disable once RedundantCheckBeforeAssignment - if (newSource == _fwSource) - { - return; - } - _fwSource = newSource; - } - - private void PushFwFile(MeterFwUpdate meterFwUpdate) - { - - - _meterFwUpdate = meterFwUpdate; - - CheckPackageFileAndFileApps(); - - if (_currentGenesis == null) - { - return; - } - - _meterFwUpdate?.AssignGenesis(_currentGenesis); - _meterFwUpdate?.CompareAllMeterAndFileApps(); - FillDataGridWithAllInfos(); - CheckUpdateEnabled(); - _resetTimeMeasurement = true; - } - private void btnGetFWFiles_Click(Object sender, EventArgs e) - { - - var isDeveloper = chbFilterIsDeveloper.Checked; - - - var url = $"http://sla12iis01/MeterProcessState/api/FileUpToDate/GetFwFiles?FileIsForDeveloperOnly={isDeveloper}"; - if (cbxFilterFwDn.SelectedItem.ToString() != "ALL") - { - var myMeterSize = ((MeterSize)cbxFilterFwDn.SelectedItem).GetHashCode(); - url += $"&CordonelFwFile_Dn={myMeterSize}"; - } - if (cbxFilterFWType.SelectedItem.ToString() != "ALL") - { - var fwTypeEnum = ((CordonelFWTypeEnum)cbxFilterFWType.SelectedItem).GetHashCode(); - url += $"&CordonelFwFile_Type={fwTypeEnum}"; - } - url += "&FileCategoryID=1"; - - - var result = LocalWebRequest.GetRequest(url, 30000); - var a = JsonConvert.DeserializeObject>(result); - - dgvFiles.DataSource = a?.OrderByDescending(o => o.FileDate).ToList(); - - for (var i = 0; i < dgvFiles.ColumnCount; i++) - { - dgvFiles.Columns[i].Visible = i < 5; - } - dgvFiles.AutoResizeColumns(); - } - - /// - /// - Enable update of Genesis Flow in Genesis Tool Box. - /// - private void dgvFiles_CellContentClick(Object sender, DataGridViewCellEventArgs e) - { - - var parentFilId = (Int32)dgvFiles.Rows[e.RowIndex].Cells[1].Value; - - var url = $"{ServiceUrls.FileContentControllerUrl()}/GetFileParts?ParentFileId={parentFilId}&readContent=true"; - - var result = LocalWebRequest.GetRequest(url, 30000); - var a = JsonConvert.DeserializeObject>(result); - - lblFwNameSelected.Text = dgvFiles.Rows[e.RowIndex].Cells[1].Value.ToString(); - lblSelectedID.Text = parentFilId.ToString(); - - _meterFwUpdate = new MeterFwUpdate(_currentGenesis, true); - - var fileApps = new List(); - - if ( a!= null) - foreach (var item in a) - { - if (item.FileName.ToLower().EndsWith(".bin")) - { - - - var fileApplication = new FileApplications(item.FileName) - { - BinData = new List(item.FileContent.ToList()) - }; - - fileApps.Add(fileApplication); - } - else if (item.FileName.ToLower().EndsWith(".txt")) - { - //encode content - - var enc = new System.Text.ASCIIEncoding(); - var controlFileContent = enc.GetString(item.FileContent.ToArray()); - - - - _meterFwUpdate.LoadPackageFileFromText(controlFileContent); - } - - } - _meterFwUpdate.LoadFileApps(fileApps); - - _meterFwUpdate?.CompareAllMeterAndFileApps(); - _meterFwUpdate?.AssignGenesis(_currentGenesis); - FillDataGridWithAllInfos(); - - CheckPackageFileAndFileApps(); - CheckUpdateEnabled(); - _resetTimeMeasurement = true; - - } - - - private void btnClearDisplay_Click(Object sender, EventArgs e) - { - if (_currentGenesis == null) - return; - - lblOverall.Text = @"Set display to normal operation"; - lblOverall.Visible = true; - Update(); - - if (!_currentGenesis.IsLoggedOn) - { - _currentGenesis.ReLogin(); - } - - _currentGenesis.SetLcdText(true, new Byte[] { 0x00, 0x00, 0x00, 0x00 }); - _currentGenesis.WriteRegister(Register.Genesisflow.StoreConfiguration, 1); - _currentGenesis.Logout(); - lblOverall.Visible = false; - } - - private void cbxStoreConfigEnable_CheckedChanged(Object sender, EventArgs e) - { - if (_meterFwUpdate != null) - _meterFwUpdate.StoreConfigEnable = cbxStoreConfigEnable.Checked; - } - - #endregion - - } -} - From 2637d4cc94a44c6c9534def273a11e496307de39 Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Tue, 27 May 2025 09:24:30 +0200 Subject: [PATCH 16/16] Minor modifications to prepare for merge --- .../Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs | 4 +--- Common/Production/ProductionUiCordonel/App.xaml.cs | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs index 6185afae..db7501b0 100644 --- a/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs +++ b/Common/Hardware/Interfaces/Ports/SerialPorts/BaseSerialPort.cs @@ -217,9 +217,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts catch (Exception ex) { _byteDataLogger.Error(ex); - } - } @@ -228,7 +226,6 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts { try { - //set receive interrupt threshold for immediate execution _serialPort.ReceivedBytesThreshold = 1; _serialPort.DataReceived += PhysicalDataReceived; @@ -259,6 +256,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts throw new ApplicationException($"{Ident} {ex.Message}"); } } + /// /// Getting the base port /// diff --git a/Common/Production/ProductionUiCordonel/App.xaml.cs b/Common/Production/ProductionUiCordonel/App.xaml.cs index 5909ed80..c143505d 100644 --- a/Common/Production/ProductionUiCordonel/App.xaml.cs +++ b/Common/Production/ProductionUiCordonel/App.xaml.cs @@ -21,12 +21,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel "ProductionUiCordonel", new EolProcessStateDef()); ProductionWindow.Show(); break; + case "2": ProductionWindow = new ProductionWindow( ProductionUiCordonel.Properties.Resources.StrPickingSoftwareNameVersion, "PickingUiCordonel", new PickingProcessStateDef()); ProductionWindow.Show(); break; + default: ProductionWindow = new ProductionWindow( ProductionUiCordonel.Properties.Resources.StrEolSoftwareNameVersion,