Merge remote-tracking branch 'origin/main' into Dev_CUST

# Conflicts:
#	Common/Common.sln.DotSettings.user
#	Common/Hardware/WaterMeter/WaterMeterCore/WaterMeterRegisters/obj/Debug/WaterMeterRegisters.csproj.CoreCompileInputs.cache
#	Common/LaaPackages
#	Common/Tools/Tools.JsonToXlsHelper/obj/Debug/Tools.JsonToXlsHelper.csproj.CoreCompileInputs.cache
#	LaaProductionWeb/LaaPackages
This commit is contained in:
Thomas Wiedebusch
2025-06-05 14:45:28 +02:00
1938 changed files with 825276 additions and 158089 deletions
@@ -0,0 +1,66 @@
# ===============================================================================================================================
Feature: FinalParamSequence_Write_Store_Reboot_Read_Verify
Test the sequence of execute final parametrization, store all settings to FLASH memory, reboot the Cordonel and finally read back
and verify the parameters which should remain persistent in non-volatile memory. This should avoid loosing of the parametrization
if the Cordonel accidentally reboots in the field. With the reboot it loads all values from FLASH, if those cannot be loaded, it
presets these values with defaults.This test used a MockGenesis and real data stored as MockRawParamsExamples for the DN50, the
DN80 and the US2.
# ===============================================================================================================================
@CheckFinalParamSequence
Scenario: [SRS_TBF_EOL_PP_CHECK_FINAL_PARAM_001]_Check_Param_Sequence
FUNCTION: Check the sequence of final parameterization with writing all parameters, safe them to non-volatile memory, reboot the
Cordonel and check if the parameters survived the reboot.
PRESETS: All routines referenced by production process name needed to be executed in advance to the real procedure to test
finally the "CheckFinalParametrization". The sequence of the processes will be executed in the order of the input, first
will be first:
- "ExecFinalParametrization" loading params from file for the required size and region and writes those to the simulated
MockGenesis,
- "ExecStoreConfiguration" simulates the storage of all parameters to FLAH in the MockGenesis,
- "ExecReboot" forces a reboot of the MockGenesis including the reload of the parameters, if saved to FLASH those will be
taken, else the params will be set to default values.
INPUTS: "Cordonel PcbId" essential to pass the register restorer
"Cordonel FW Version" essential to build the correct registers and region and load an according parameter file
"Cordonel Meter Size"
REMIND: Only ONE "Kill x" should be set to "X"! Leave all others as "-" to observe better the unique setup!
"Kill WritePara" forces the test to fail due to missing parameters if set to "X", else set to "-",
"Kill StoreParams" forces the test to fail due to missing store configuration if set to "X", else set to "-",
"Kill Reboot" forces the test to fail due to false reboot if set to "X", else set to "-",
"Kill VerifyParams" forces the test to fail due to one changed parameter if set to "X", else set to "-".
OUTPUT: "StatusReturn" with results "Okay" or "Failed", additional check of complete EOL progress status. The expected EOL result
will be preset with the expected values and modified with the "Kill x" to force the change from "Okay" to "Failed".
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckFinalParametrization'
And Name the public function to be checked for functionality 'ExecuteProcess'
And Name the object type holding the property 'StatusReturn'
# Name one production process to be checked in advance to the real test. This can be done more than once to fill a list with
# all required preceding production processes to check an entire sequence of processes.
And Define production process for sequencing before test 'ExecFinalParametrization'
And Define production process for sequencing before test 'ExecStoreConfiguration'
And Define production process for sequencing before test 'ExecRebootCordonel'
# Activate the entire equality EOL progress check between requirement and result
And Activate EOL progress check
##--------------------------------- Define Test-Cases ------------------------------------------------------------------------
And Input matrix defining the test cases and results
# "Kill x" forces the test to fail to check if all tests are executed. REMIND: Set only ONE to "X" and leave all others as "-"! Otherwise the test will FAIL!
| TestNo | Cordonel PcbId | Cordonel FW Version | Cordonel Meter Size | Kill WriteParams | Kill StoreParams | Kill Reboot | Kill VerifyParams | Kill Registers | StatusReturn | Comment |
| 1 | 182100041 | R1.3.0B | DN50 | - | - | - | - | - | Okay | Sequence successfully executed for EMEA DN50 |
| 2 | 212120036 | R1.3.0B | DN80 | - | - | - | - | - | Okay | Sequence successfully executed for EMEA DN80 |
| 3 | 241630065 | R2.0.06 | US2 | - | - | - | - | - | Okay | Sequence successfully executed for NA US2 (2") |
| 4 | 182100041 | R1.3.0B | DN00 | - | - | - | - | - | Failed | Parameter file for EMEA DN00 missing CSD Params |
| 5 | 182100041 | R1.3.0B | DN01 | - | - | - | - | - | Failed | Parameter file for EMEA DN01 not present |
| 6 | 182100041 | R1.3.0B | DN50 | X | - | - | - | - | Failed | Parameter write failed |
| 7 | 182100041 | R1.3.0B | DN50 | - | X | - | - | - | Failed | Store configuration failed |
| 8 | 182100041 | R1.3.0B | DN50 | - | - | X | - | - | Failed | Reboot failed |
| 9 | 182100041 | R1.3.0B | DN50 | - | - | - | X | - | Failed | Parameter verify failed |
| 10 | 182100041 | R1.3.0B | DN50 | - | - | - | - | X | Failed | Registers not loaded: invalid configuration.json |
When Execute the generic tests
Then Check the generic test results
# ===============================================================================================================================
@@ -0,0 +1,302 @@
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by SpecFlow (https://www.specflow.org/).
// SpecFlow Version:3.3.0.0
// SpecFlow Generator Version:3.1.0.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------
#region Designer generated code
#pragma warning disable
namespace Xylem.Common.Production.ProductionUiCordonel.UnitTests.Features
{
using TechTalk.SpecFlow;
using System;
using System.Linq;
[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.3.0.0")]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[NUnit.Framework.TestFixtureAttribute()]
[NUnit.Framework.DescriptionAttribute("FinalParamSequence_Write_Store_Reboot_Read_Verify")]
public partial class FinalParamSequence_Write_Store_Reboot_Read_VerifyFeature
{
private TechTalk.SpecFlow.ITestRunner testRunner;
private string[] _featureTags = ((string[])(null));
#line 1 "FinalParamSequence_Write_Store_Reboot_Verify.feature"
#line hidden
[NUnit.Framework.OneTimeSetUpAttribute()]
public virtual void FeatureSetup()
{
testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "FinalParamSequence_Write_Store_Reboot_Read_Verify", @"Test the sequence of execute final parametrization, store all settings to FLASH memory, reboot the Cordonel and finally read back
and verify the parameters which should remain persistent in non-volatile memory. This should avoid loosing of the parametrization
if the Cordonel accidentally reboots in the field. With the reboot it loads all values from FLASH, if those cannot be loaded, it
presets these values with defaults.This test used a MockGenesis and real data stored as MockRawParamsExamples for the DN50, the
DN80 and the US2.", ProgrammingLanguage.CSharp, ((string[])(null)));
testRunner.OnFeatureStart(featureInfo);
}
[NUnit.Framework.OneTimeTearDownAttribute()]
public virtual void FeatureTearDown()
{
testRunner.OnFeatureEnd();
testRunner = null;
}
[NUnit.Framework.SetUpAttribute()]
public virtual void TestInitialize()
{
}
[NUnit.Framework.TearDownAttribute()]
public virtual void TestTearDown()
{
testRunner.OnScenarioEnd();
}
public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
{
testRunner.OnScenarioInitialize(scenarioInfo);
testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs<NUnit.Framework.TestContext>(NUnit.Framework.TestContext.CurrentContext);
}
public virtual void ScenarioStart()
{
testRunner.OnScenarioStart();
}
public virtual void ScenarioCleanup()
{
testRunner.CollectScenarioErrors();
}
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("[SRS_TBF_EOL_PP_CHECK_FINAL_PARAM_001]_Check_Param_Sequence")]
[NUnit.Framework.CategoryAttribute("CheckFinalParamSequence")]
public virtual void SRS_TBF_EOL_PP_CHECK_FINAL_PARAM_001_Check_Param_Sequence()
{
string[] tagsOfScenario = new string[] {
"CheckFinalParamSequence"};
System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary();
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("[SRS_TBF_EOL_PP_CHECK_FINAL_PARAM_001]_Check_Param_Sequence", "FUNCTION: Check the sequence of final parameterization with writing all parameter" +
"s, safe them to non-volatile memory, reboot the\r\n Cordonel and check if" +
" the parameters survived the reboot.\r\nPRESETS: All routines referenced by produ" +
"ction process name needed to be executed in advance to the real procedure to tes" +
"t \r\n finally the \"CheckFinalParametrization\". The sequence of the proce" +
"sses will be executed in the order of the input, first \r\n will be first" +
":\r\n - \"ExecFinalParametrization\" loading params from file for the requi" +
"red size and region and writes those to the simulated \r\n MockGenesis," +
" \r\n - \"ExecStoreConfiguration\" simulates the storage of all parameters " +
"to FLAH in the MockGenesis,\r\n - \"ExecReboot\" forces a reboot of the Moc" +
"kGenesis including the reload of the parameters, if saved to FLASH those will be" +
" \r\n taken, else the params will be set to default values.\r\nINPUTS: " +
"\"Cordonel PcbId\" essential to pass the register restorer\r\n \"Cordonel FW" +
" Version\" essential to build the correct registers and region and load an accord" +
"ing parameter file\r\n \"Cordonel Meter Size\" \r\n REMIND: Only ONE" +
" \"Kill x\" should be set to \"X\"! Leave all others as \"-\" to observe better the un" +
"ique setup!\r\n \"Kill WritePara\" forces the test to fail due to missing p" +
"arameters if set to \"X\", else set to \"-\", \r\n \"Kill StoreParams\" forces " +
"the test to fail due to missing store configuration if set to \"X\", else set to \"" +
"-\",\r\n \"Kill Reboot\" forces the test to fail due to false reboot if set " +
"to \"X\", else set to \"-\", \r\n \"Kill VerifyParams\" forces the test to fail" +
" due to one changed parameter if set to \"X\", else set to \"-\".\r\nOUTPUT: \"Status" +
"Return\" with results \"Okay\" or \"Failed\", additional check of complete EOL progre" +
"ss status. The expected EOL result \r\n will be preset with the expected " +
"values and modified with the \"Kill x\" to force the change from \"Okay\" to \"Failed" +
"\". ", tagsOfScenario, argumentsOfScenario);
#line 11
this.ScenarioInitialize(scenarioInfo);
#line hidden
bool isScenarioIgnored = default(bool);
bool isFeatureIgnored = default(bool);
if ((tagsOfScenario != null))
{
isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any();
}
if ((this._featureTags != null))
{
isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any();
}
if ((isScenarioIgnored || isFeatureIgnored))
{
testRunner.SkipScenario();
}
else
{
this.ScenarioStart();
#line 34
testRunner.Given("Initialize new test scenario for production process \'CheckFinalParametrization\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line hidden
#line 35
testRunner.And("Name the public function to be checked for functionality \'ExecuteProcess\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 36
testRunner.And("Name the object type holding the property \'StatusReturn\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 40
testRunner.And("Define production process for sequencing before test \'ExecFinalParametrization\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 41
testRunner.And("Define production process for sequencing before test \'ExecStoreConfiguration\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 42
testRunner.And("Define production process for sequencing before test \'ExecRebootCordonel\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 45
testRunner.And("Activate EOL progress check", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
TechTalk.SpecFlow.Table table1 = new TechTalk.SpecFlow.Table(new string[] {
"TestNo",
"Cordonel PcbId",
"Cordonel FW Version",
"Cordonel Meter Size",
"Kill WriteParams",
"Kill StoreParams",
"Kill Reboot",
"Kill VerifyParams",
"Kill Registers",
"StatusReturn",
"Comment"});
table1.AddRow(new string[] {
"1",
"182100041",
"R1.3.0B",
"DN50",
"-",
"-",
"-",
"-",
"-",
"Okay",
"Sequence successfully executed for EMEA DN50"});
table1.AddRow(new string[] {
"2",
"212120036",
"R1.3.0B",
"DN80",
"-",
"-",
"-",
"-",
"-",
"Okay",
"Sequence successfully executed for EMEA DN80"});
table1.AddRow(new string[] {
"3",
"241630065",
"R2.0.06",
"US2",
"-",
"-",
"-",
"-",
"-",
"Okay",
"Sequence successfully executed for NA US2 (2\")"});
table1.AddRow(new string[] {
"4",
"182100041",
"R1.3.0B",
"DN00",
"-",
"-",
"-",
"-",
"-",
"Failed",
"Parameter file for EMEA DN00 missing CSD Params"});
table1.AddRow(new string[] {
"5",
"182100041",
"R1.3.0B",
"DN01",
"-",
"-",
"-",
"-",
"-",
"Failed",
"Parameter file for EMEA DN01 not present"});
table1.AddRow(new string[] {
"6",
"182100041",
"R1.3.0B",
"DN50",
"X",
"-",
"-",
"-",
"-",
"Failed",
"Parameter write failed"});
table1.AddRow(new string[] {
"7",
"182100041",
"R1.3.0B",
"DN50",
"-",
"X",
"-",
"-",
"-",
"Failed",
"Store configuration failed"});
table1.AddRow(new string[] {
"8",
"182100041",
"R1.3.0B",
"DN50",
"-",
"-",
"X",
"-",
"-",
"Failed",
"Reboot failed"});
table1.AddRow(new string[] {
"9",
"182100041",
"R1.3.0B",
"DN50",
"-",
"-",
"-",
"X",
"-",
"Failed",
"Parameter verify failed"});
table1.AddRow(new string[] {
"10",
"182100041",
"R1.3.0B",
"DN50",
"-",
"-",
"-",
"-",
"X",
"Failed",
"Registers not loaded: invalid configuration.json"});
#line 48
testRunner.And("Input matrix defining the test cases and results", ((string)(null)), table1, "And ");
#line hidden
#line 62
testRunner.When("Execute the generic tests", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line hidden
#line 63
testRunner.Then("Check the generic test results", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
}
this.ScenarioCleanup();
}
}
}
#pragma warning restore
#endregion
@@ -0,0 +1,551 @@
# ================================================================================================================================
Feature: CheckProductionState
Test the functionality of the executed production steps and preconditions needed for finalizing of the Cordonel and to prepare it
for shipping.
# ================================================================================================================================
@CheckEolProgress
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_001]_Check_EOL_Progress
FUNCTION: Check if an EOL-Progress has all information needed to start the check of all production states.
PRESETS: none
INPUTS: The values needed to be predefined automatically by the "Sentinel" on EOL-Progress creation:
"EOL Id" can be set to any number unequal to 0, 0 will cause an error,
"EOL StartDate" set to "UTC Now" to take the actual date, else set to "unknown" will leave the date empty
"Cordonel PcbId" needed for comparison to
"EOL PcbId".
"Partial Compare" "X" will be used to check if the string contains a substring defined in the "StringReturn".
"Partial Compare" "-" will force the string being exact as defined!
OUTPUT: "StringReturn" as test result containing the expected string or "null" as error result from the method.
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the property to be checked for 'EOLSoftwareNameAndVersion'
And Name the object type holding the property 'StringReturn'
And Name the private function to be checked for functionality 'CheckEolProgress'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | EOL Id | EOL StartDate | Cordonel PcbId | EOL PcbId | Partial Compare | StringReturn | Comment |
| 1 | 123 | UTC Now | 182100041 | 182100041 | X | Cordonel | EOL Progress successfully created |
| 2 | 123 | UTC Now | 0 | 182100041 | - | null | Unmatching Cordonel PcbId |
| 3 | 123 | UTC Now | 182100041 | 0 | - | null | Unmatching EOL PcbId |
| 4 | 123 | unknown | 182100041 | 182100041 | - | null | Start date not set |
| 5 | 0 | UTC Now | 182100041 | 182100041 | X | null | EOL Id not set |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckProductionProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_002]_Check_Requirements
FUNCTION: Check if any requirement is defined being active for this Cordonel.
PRESETS: none
INPUTS: Requirements "Standard Id" just a number or "null",
"Standard Version" just a number or "null",
"Standard IsActive" if "X" else "-",
"Special Id just a number or "null"",
"Special Version" just a number or "null" and
"Special IsActive" if "X" else "-".
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the property to be checked for 'RequirementRequestChecked'
And Name the object type holding the property 'EOLStatus'
And Name the private function to be checked for functionality 'CheckRequirements'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Standard Id | Standard Version | Standard IsActive | Standard IsApproved | Special Id | Special Version | Special IsActive | Special IsApproved | EOL Status | Comment |
| 1 | 10 | 5 | X | X | null | null | - | - | Okay | Standard Requirement is active and approved |
| 2 | 10 | 5 | X | X | 5 | 3 | X | - | Okay | Standard Requirement is active and approved, special is active but not approved |
| 3 | 10 | 5 | - | - | 5 | 3 | X | X | Okay | Special Requirement is active and approved |
| 4 | 10 | 5 | X | X | 5 | 3 | X | X | Okay | Both Requirements are active and approved |
| 5 | 10 | 5 | - | - | 5 | 3 | - | - | Failed | No Requirement is active and no requirement approved |
| 6 | 10 | 5 | X | - | 5 | 3 | - | - | Failed | Standard Requirement is active but not approved |
| 7 | 10 | 5 | - | - | 5 | 3 | X | - | Failed | Special Requirement is active but not approved |
| 8 | null | null | - | - | null | null | - | - | Failed | No Requirement is defined |
| 9 | null | null | - | - | 5 | 3 | X | X | Failed | Missing Standard Requirement |
| 10 | null | null | X | X | null | null | - | - | Failed | Standard Requirement is active but undefined |
| 11 | null | 5 | X | X | null | null | - | - | Failed | Standard Requirement is active but Id missing |
| 12 | 10 | null | X | X | null | null | - | - | Failed | Standard Requirement is active but version missing |
| 13 | 10 | 5 | X | X | null | 2 | X | X | Failed | Special Requirement is active but Id missing |
| 14 | 10 | 5 | X | X | 5 | null | X | X | Failed | Special Requirement is active but version missing |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckProductionPrperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_003]_Check_ProductionOrderNo
FUNCTION: Compare the Cordonel production-order with the production-order from the requirement to validate that the correct requirements
going to be used for the check of the actual production state.
PRESETS: none
INPUTS: "Cordonel ProdOrderNo" and "Requirement ProdOrderNo"
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckProductionOrderNumber'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'RequirementOrderChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Cordonel ProdOrderNo | Requirement ProdOrderNo | EOL Status | Comment |
| 1 | 3118323 | 3118323 | Okay | Order number is identical |
| 2 | 3118323 | 0 | Failed | Unmatching Requirement Order number |
| 3 | 0 | 3118323 | Failed | Unmatching Cordonel Order number |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCordonelProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_004]_Check_PcbId
FUNCTION: Compare the PcbId from Cordonel, EOL-progress and requirement.
PRESETS: none
INPUTS: "Cordonel PcbId", EOL PcbId and "Requirement PcbId". If the requirement-PcbId id is "null" then the requirement is order based
and not just for a single Cordonel!
OUTPUT: "EOL Status" with results "Okay", "Failed" or "Skipped"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckPcbId'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'RequirementPcbIdChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# An order based requirement needs a "null" for "Requirement PcbId"
| TestNo | Cordonel PcbId | EOL PcbId | Requirement PcbId | EOL Status | Comment |
| 1 | 182100041 | 182100041 | null | Skipped | Order based requirement |
| 2 | 182100041 | 182100041 | 182100041 | Okay | PcbId is identical |
| 3 | 182100041 | 0 | 182100041 | Failed | Unmatching EOL Progress PcbId |
| 4 | 182100041 | 182100041 | 0 | Failed | Unmatching Requirement PcbId |
| 5 | 0 | 182100041 | 182100041 | Failed | Unmatching Cordonel PcbId |
| 6 | 1821000 | 182100041 | 182100041 | Failed | Cordonel PcbId to short |
| 7 | null | 182100041 | 182100041 | Failed | Cordonel PcbId is null |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCordonelProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_005]_Check_Region
FUNCTION: Compare the Region from Cordonel and requirement.
PRESETS: none
INPUTS: "Cordonel Region" and "Requirement Region".
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckRegion'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'RequirementRegionChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Cordonel Region | Requirement Region | EOL Status | Comment |
| 1 | EMEA | EMEA | Okay | Region is identical |
| 2 | NA | NA | Okay | Region is identical |
| 3 | EMEA | NA | Failed | Unmatched Region |
| 4 | null | EMEA | Failed | Unknown Cordonel Region |
| 5 | EMEA | null | Failed | Unknown Requirement Region |
| 6 | null | null | Failed | Unspecified Region |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCordonelProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_006]_Check_LUT_CRC
FUNCTION: Check for the checksum (cyclic redundancy check - CRC) of the metrology lookup-table.
PRESETS: none
INPUTS: "Cordonel LUT CRC" and "Requirement LUT CRC" just a string representing a number in hex e.g. "0xEB7C.
OUTPUT: "StringReturn" as test result containing the expected string or "null" as error result from the method.
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckLutCrc'
And Name the object type holding the property 'StringReturn'
And Name the property to be checked for 'ProductionStatusLutCrc'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# "StringReturn" "null" is the error return from the functions based on strings as return value!
| TestNo | Cordonel LUT CRC | Requirement LUT CRC | StringReturn | Comment |
| 1 | 0xEB7C | 0xEB7C | 0xEB7C | LUT CRC is identical |
| 2 | 0x0000 | 0xEB7C | null | Wrong Cordonel LUT CRC |
| 3 | 0xEB7C | 0x0000 | null | Wrong Requirement LUT CRC |
| 4 | null | 0xEB7C | null | Cordonel LUT CRC undefined |
| 5 | 0xEB7C | null | null | Requirement LUT CRC undefined |
| 6 | null | null | null | LUT CRC's undefined |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCordonelProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_007]_Check_Meter_Size
FUNCTION: Compare the Meter Size from Cordonel and requirement.
PRESETS: none
INPUTS: "Cordonel Meter Size" and "Requirement Meter Size" as strings naming the size as e.g. "DN50", "US1_5" or "1 1/2 INC".
OUTPUT: "StringReturn" as test result containing the expected string or "null" as error result from the method.
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckMeterSize'
And Name the object type holding the property 'StringReturn'
And Name the property to be checked for 'ProductionStatusMeterSize'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# "StringReturn" "null" is the error return from the functions based on strings as return value!
| TestNo | Cordonel Meter Size | Requirement Meter Size | StringReturn | Comment |
| 1 | DN50 | DN50 | DN50 | Meter size is identical |
| 2 | US1_5 | 1 1/2 INC | 1 1/2 INC | Meter size is identical but uses different naming |
| 3 | DN80 | 1 1/2 INC | null | Wrong Requirement Meter size |
| 4 | null | DN50 | null | Cordonel Meter size undefined |
| 5 | DN50 | null | null | Requirement Meter size undefined |
| 6 | null | null | null | Meter sizes undefined |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCordonelProperties
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_008]_Check_FW_and_Apps
FUNCTION: Compare Cordonel installed Firmware and Apps with required Firmware.
All Apps will be verified for their versions and CRC.
The Core-Revision will be validated.
PRESETS: none
INPUTS: "Cordonel Core Revision" as float specifying the Breeze operating system version, it will be checked if it is compatible to
the installed FW version.
"Cordonel FW Version" this will be installed to the MockGenesis:
Check if a valid FW package is in "C:\User\[UserName]\AppData\Roaming\Genesis\MockFwExamples" like "EMEA_R130" or "NA_R2006"
Those FW package directories need to contain the "Cordonel FW Version" without the "." between major, minor and build version.
The default FW installed in the MockGenesis is hard coded to R1.3.0B.
"Requirement FW Version" this version should be installed. It will be loaded from the location "C:\User\[UserName]\AppData\
Roaming\Genesis\MockFwExamples".
"Modify AppID" any application with its decimal version which should be modified to pass the negative tests:
"Erase App" "X" for simulated erasure else "-",
"Change App Version" "X" to set this to an unspecified version else "-",
"Change App CRC" "X" to set it to an invalid CRC else "-".
OUTPUT: "StringReturn" as test result containing the expected string or "null" as error result from the method, else it returns the
valid installed FW version string.
COMMENT: NCrunch needs FW package in a specific folder "C:\User\[UserName]\AppData\Roaming\Genesis\MockFwExamples" as it copies
all assemblies to a special NCrunch working folder which is total separated from the project specific "\bin\Debug" folder!
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckFwAndApps'
And Name the object type holding the property 'StringReturn'
And Name the property to be checked for 'ProductionStatusFwVersion'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# "StringReturn" "null" is the error return from the functions based on strings as return value!
| TestNo | Cordonel Core Revision | Cordonel FW Version | Requirement FW Version | Modify AppId | Erase App | Change App Version | Change App CRC | StringReturn | Comment |
| 1 | 1.71 | R1.3.0B | R1.3.0B | null | - | - | - | R1.3.0B | FW Version is identical |
| 2 | 1.71 | R2.0.06 | R2.0.06 | null | - | - | - | R2.0.06 | FW Version is identical |
| 3 | 1.60 | R1.3.0B | R1.3.0B | null | - | - | - | null | Core revision is incompatible |
| 4 | 1.71 | R1.3.0B | R1.2.47 | null | - | - | - | null | Wrong FW version installed |
| 5 | 1.71 | R1.3.0B | R1.3.0B | 15 | X | - | - | null | Genesis flow is invalid |
| 6 | 1.71 | R1.3.0B | R1.3.0B | 15 | - | X | - | null | Genesis flow version modified |
| 7 | 1.71 | R1.3.0B | R1.3.0B | 15 | - | - | X | null | Genesis flow CRC modified |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckLifetimes
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_009]_Check_Remaining_Battery_Load
FUNCTION: Compare the remaining battery load from Cordonel and the requirement.
PRESETS: none
INPUTS: "Normal Current [uA]" multiplied with exceeded lifetime together with "Production Drained [%] for an alternative threshold,
set to "null" if the standard check based on "Max Drained [%]" shall be used.
"Production Supplement [%]" additional allowed drainage of batteries during production, set to "null" if the standard check
based on "Max Drained [%]" shall be used.
"Actual Lifetime [days]" is the time in days the Cordonel after Kitron production, set to 0 to create an error.
"Actual Drained [%]" Standard threshold for actual drained battery load based on prediction from firmware and exceeded
lifetime, set to 0 to create an error.
"Max Drained [%]" Threshold for allowed drainage at EOL in standard check. If alternative threshold is active then this is
the ABSOLUTE LIMIT.
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckRemainingBatteryLoad'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'ProductionStatusBatteryLoadChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Normal Current [uA] | Production Supplement [%] | Actual Lifetime [days] | Actual Drained [%] | Max Drained [%] | EOL Status | Comment |
| 1 | 119.8 | 1.5 | 90 | 2.0 | 5.0 | Okay | Alternative threshold okay |
| 2 | 119.8 | 1.5 | 90 | 2.5 | 5.0 | Failed | Alternative threshold exceeded |
| 3 | 119.8 | 1.5 | 180 | 2.5 | 2.0 | Failed | Absolute max drained limit exceeded |
| 4 | null | null | 90 | 2.0 | 2.0 | Okay | Normal threshold okay |
| 5 | null | null | 90 | 2.5 | 2.0 | Failed | Normal threshold exceeded |
| 6 | null | null | 0 | 2.0 | 5.0 | Failed | Lifetime of 0 days error |
| 7 | null | null | 90 | 0.0 | 5.0 | Failed | Drained load of 0 % error |
| 8 | null | null | 90 | 2.0 | null | Failed | Max remaining battery load is null |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckLifetimes
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_010]_Check_Remaining_Lifetime
FUNCTION: Compare the remaining life time reported by the Cordonel and the requirement.
PRESETS: none
INPUTS: "Actual Lifetime [days]" is the time in days the Cordonel after Kitron production,
"Actual Drained [%]" is used to calculate the drained load to forecast the remaining lifetime,
"Required Lifetime [years]" Threshold for remaining lifetime.
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckRemainingLifetime'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'ProductionStatusRemainingLifeTimeChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Actual Lifetime [days] | Required Lifetime [years] | Actual Drained [%] | EOL Status | Comment |
| 1 | 90 | 20.0 | 1.0 | Okay | Remaining lifetime as required |
| 2 | 365 | 5.0 | 10.0 | Okay | Remaining lifetime as required |
| 3 | 90 | 20.0 | 2.0 | Failed | Remaining lifetime to short |
| 4 | 0 | 20.0 | 1.0 | Failed | Actual lifetime of 0 days error |
| 5 | 90 | 20.0 | 0.0 | Failed | Actual drained load of 0 % error |
| 6 | 90 | null | 1.0 | Failed | Required lifetime is null |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckLifetimes
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_011]_Check_Storage_Months
FUNCTION: Compare the production date reported by the Cordonel with the actual date and check the requirement.
PRESETS: none
INPUTS: "Assembly Months before Now" as zero or negative floating point number or invalid if set to "null".
"Max Storage Months" floating point number of months allowed until production has to be finalized.
OUTPUT: "StatusReturn" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckStorageMonths'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'MaxStorageMonthsChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Assembly Months before Now | Max Storage Months | EOL Status | Comment |
| 1 | -3.0 | 3.0 | Okay | Storage months in range |
| 2 | -3.0 | 2.0 | Failed | Exceeded maximal allowed storage time |
| 3 | null | 2.0 | Failed | Error assembly months are null |
| 4 | -3.0 | null | Failed | Error max storage months are null |
| 5 | 3.0 | 3.0 | Failed | Error assembly time lies in the future |
| 6 | -3.0 | -3.0 | Failed | Error storage months is negative |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCalibrations
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_012]_Check_Preadjustment
FUNCTION: Check the database content reporting a flow-test preadjustment.
PRESETS: none
INPUTS: "Test Bench Fields" the array of test bench fields
"Test Bench State[4]" the field containing a number if preadjustment successfully executed, null if not.
"Skip Calib" to skip all calibration related checks
OUTPUT: "EOL Status" with results "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckPreadjustment'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'ProductionStatusZeroFlowChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Test Bench Fields | Test Bench State[4] | Skip Calib | EOL Status | Comment |
| 1 | 5 | 21 | X | Skipped | Skipped calibration checks by requirement |
| 2 | 5 | 21 | - | Okay | Test bench preadjustment succeeded |
| 3 | 5 | 0 | - | Okay | Test bench preadjustment succeeded |
| 4 | 5 | 500 | - | Okay | Test bench preadjustment succeeded |
| 5 | 4 | 21 | - | Failed | Error test bench fields too little |
| 6 | 5 | null | - | Failed | Error test bench status[4] is null |
| 7 | null | 21 | - | Failed | Error test bench fields null |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCalibrations
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_013]_Check_Flow_Test_State
FUNCTION: Check the database content reporting the flow-test state.
PRESETS: none
INPUTS: "Test Bench Fields" the array of test bench fields
"Test Bench State[5]" the field containing a number of 30 if calibration succeeded
"Skip Calib" to skip all calibration related checks if set to "X" else "-".
OUTPUT: "EOL Status" with results "Skipped", "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckFlowTestState'
And Name the object type holding the property 'EOLStatus'
And Name the property to be checked for 'ProductionStatusCalibrationChecked'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
| TestNo | Test Bench Fields | Test Bench State[5] | Skip Calib | EOL Status | Comment |
| 1 | 6 | 30 | X | Skipped | Skipped calibration checks by requirement |
| 2 | 6 | 30 | - | Okay | Test bench calibration succeeded |
| 3 | 6 | 30 | - | Okay | Test bench calibration succeeded |
| 4 | 5 | 30 | - | Failed | Error test bench fields too little |
| 5 | 6 | 29 | - | Failed | Error test bench status[5] number to small |
| 6 | 6 | 31 | - | Failed | Error test bench status[5] number to large |
| 7 | 6 | null | - | Failed | Error test bench status[5] is null |
| 8 | null | 30 | - | Failed | Error test bench fields null |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckCalibrations
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_014]_Check_Calib_Factors
FUNCTION: Compare the calibration values reported by the Cordonel with the flow-test calibration values reported by the database.
PRESETS: All calibration values not covered by this test in valid range.
INPUTS: "Meter Calib Fact 1/2/3" Meter calibration factors 1, 2 and 3 stored in and read out from the Cordonel,
"DB Calib Fact 1/2/3" Required calibration factors 1, 2 and 3 reported by the database.
"Skip Calib" to skip all calibration related checks if set to "X" else "-".
OUTPUT: "StatusReturn" with results "Skipped", "Okay" or "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the private function to be checked for functionality 'CheckCalibrationValues'
And Name the object type holding the property 'StatusReturn'
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# REMIND: The highest priority has the equality of the calibration factors between Meter and database. If those
# don't match, the check of valid range and set to default values doesn't come in focus!
| TestNo | Meter Calib Fact 1 | Meter Calib Fact 2 | Meter Calib Fact 3 | DB Calib Fact 1 | DB Calib Fact 2 | DB Calib Fact 3 | Skip Calib | StatusReturn | Comment |
| 1 | 10000 | 20000 | 30000 | 10000 | 20000 | 30000 | X | Skipped | Skipped calib checks per requirement |
| 2 | 10000 | 20000 | 30000 | 10000 | 20000 | 30000 | - | Okay | Valid calib factors |
| 3 | 15625 | 20000 | 30000 | 15625 | 20000 | 30000 | - | Okay | Valid calib factors (1 default) |
| 4 | 15625 | 15625 | 30000 | 15625 | 15625 | 30000 | - | Okay | Valid calib factors (2 defaults) |
| 5 | 15625 | 15625 | 15625 | 15625 | 15625 | 15625 | - | Warning | Invalid Meter calib factors (3 defaults) |
| 6 | 10000 | 20000 | 30000 | 10000 | 44444 | 30000 | - | Failed | Inconsistent calib factors DB to Meter |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@CheckProductionStateProcess
Scenario: [SRS_TBF_EOL_PP_CHECK_PROD_STATE_015]_Check_Complete_Flow_Production_State
FUNCTION: Check the entire production process sequence except the database calls with all underlaying sub-processes.
PRESETS: Preset all values with valid parameters to pass the test as "Okay" needed to successfully pass all underlaying tests:
- EOL Id | EOL StartDate | Cordonel PcbId | EOL PcbId
- Standard Id | Standard Version | Standard IsActive | Special Id | Special Version
- Cordonel ProdOrderNo | Requirement ProdOrderNo
- Cordonel PcbId | EOL PcbId | Requirement PcbId |
- Cordonel Region | Requirement Region
- Cordonel LUT CRC | Requirement LUT CRC
- Cordonel Meter Size | Requirement Meter Size
- Cordonel Core Revision | Cordonel FW Version | Requirement FW Version
- Actual Lifetime [days] | Actual Drained [%] | Max Drained [%]
- Actual Lifetime [days] | Required Lifetime [years] | Actual Drained [%]
- Assembly Months before Now | Max Storage Months
- Test Bench Fields | Test Bench State[4]
- Test Bench Fields | Test Bench State[5]
- Meter Calib Fact 1 | Meter Calib Fact 2 | Meter Calib Fact 3 | DB Calib Fact 1 | DB Calib Fact 2 | DB Calib Fact 3
INPUTS: "Kill x" forces the test to fail to check if all tests are executed. REMIND: Only ONE should be set to "X"!
Leave all others as "-" to observe better the unique setup!
OUTPUT: "StatusReturn" with results "Okay" or "Failed",
additional check of complete EOL progress status. The expected EOL result will be set with the preset values and
modified with the "Kill x" from "Okay" to "Failed"
#--------------------------------- Prepare Tests ----------------------------------------------------------------------------
Given Initialize new test scenario for production process 'CheckProductionState'
And Name the public function to be checked for functionality 'ExecuteProcess'
And Name the object type holding the property 'StatusReturn'
# Activate the entire equality EOL progress check between requirement and result
And Activate EOL progress check
# The input initializations don't have a "TestNo" nor a result (EOLStatus, StringReturn or StatusReturn) as those are commonly used for
# all tests specified in "Input matrix defining the test cases and results". All these inputs have a setup to pass the overall test with
# positive feedback (all are good).
And Input initializations for the test matrix and presets to pass the preceding tests
| EOL Id | EOL StartDate | Cordonel PcbId | EOL PcbId | Comment |
| 123 | UTC Now | 182100041 | 182100041 | EOL Progress successfully created |
And Input initializations for the test matrix and presets to pass the preceding tests
| Standard Id | Standard Version | Standard IsActive | Standard IsApproved | Special Id | Special Version | Comment |
| 10 | 5 | X | X | null | null | Standard Requirement is active and approved |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel ProdOrderNo | Requirement ProdOrderNo | Comment |
| 3118323 | 3118323 | Order number is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel PcbId | EOL PcbId | Requirement PcbId | Comment |
| 182100041 | 182100041 | 182100041 | PcbId is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel Region | Requirement Region | Comment |
| EMEA | EMEA | Region is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel LUT CRC | Requirement LUT CRC | Comment |
| 0xEB7C | 0xEB7C | LUT CRC is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel Meter Size | Requirement Meter Size | Comment |
| DN50 | DN50 | Meter size is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Cordonel Core Revision | Cordonel FW Version | Requirement FW Version | Comment |
| 1.71 | R1.3.0B | R1.3.0B | FW Version is identical |
And Input initializations for the test matrix and presets to pass the preceding tests
| Max Drained [%] | Comment |
| 2.0 | Normal threshold okay |
And Input initializations for the test matrix and presets to pass the preceding tests
| Actual Lifetime [days] | Required Lifetime [years] | Actual Drained [%] | Comment |
| 90 | 20.0 | 1.0 | Remaining lifetime as required |
And Input initializations for the test matrix and presets to pass the preceding tests
| Assembly Months before Now | Max Storage Months | Comment |
| -3.0 | 3.0 | Storage months in range |
And Input initializations for the test matrix and presets to pass the preceding tests
| Test Bench Fields | Test Bench State[4] | Comment |
| 5 | 21 | Test bench preadjustment succeeded |
And Input initializations for the test matrix and presets to pass the preceding tests
| Test Bench Fields | Test Bench State[5] | Comment |
| 6 | 30 | Test bench calibration succeeded |
And Input initializations for the test matrix and presets to pass the preceding tests
| Meter Calib Fact 1 | Meter Calib Fact 2 | Meter Calib Fact 3 | DB Calib Fact 1 | DB Calib Fact 2 | DB Calib Fact 3 | Comment |
| 10000 | 20000 | 30000 | 10000 | 20000 | 30000 | Valid calib factors |
#--------------------------------- Define Test-Cases -------------------------------------------------------------------------
And Input matrix defining the test cases and results
# "Kill x" forces the test to fail to check if all tests are executed. REMIND: Set only ONE to "X" and leave all others as "-"! Otherwise the test will FAIL!
| TestNo | Kill EOL | Kill Requ | Kill Order | Kill PcbId | Kill Region | Kill LUT | Kill Size | Kill FW | Kill Load | Kill LifeTime | Kill StoreTime | Kill PreAdj | Kill FlowTest | Kill Calib | StatusReturn | Comment |
| 1 | - | - | - | - | - | - | - | - | - | - | - | - | - | - | Okay | Test sequence complete |
| 2 | X | - | - | - | - | - | - | - | - | - | - | - | - | - | Failed | Detected kill EOL generation |
| 3 | - | X | - | - | - | - | - | - | - | - | - | - | - | - | Failed | Detected kill requirement |
| 4 | - | - | X | - | - | - | - | - | - | - | - | - | - | - | Failed | Detected kill production order check |
| 5 | - | - | - | X | - | - | - | - | - | - | - | - | - | - | Failed | Detected kill PcbId check |
| 6 | - | - | - | - | X | - | - | - | - | - | - | - | - | - | Failed | Detected kill region check |
| 7 | - | - | - | - | - | X | - | - | - | - | - | - | - | - | Failed | Detected kill LUT check |
| 8 | - | - | - | - | - | - | X | - | - | - | - | - | - | - | Failed | Detected kill meter size check |
| 9 | - | - | - | - | - | - | - | X | - | - | - | - | - | - | Failed | Detected kill FW check |
| 10 | - | - | - | - | - | - | - | - | X | - | - | - | - | - | Failed | Detected kill battery load check |
| 11 | - | - | - | - | - | - | - | - | - | X | - | - | - | - | Failed | Detected kill life time check |
| 12 | - | - | - | - | - | - | - | - | - | - | X | - | - | - | Failed | Detected kill storage time check |
| 13 | - | - | - | - | - | - | - | - | - | - | - | X | - | - | Failed | Detected kill preadjustment check |
| 14 | - | - | - | - | - | - | - | - | - | - | - | - | X | - | Failed | Detected kill flow test check |
| 15 | - | - | - | - | - | - | - | - | - | - | - | - | - | X | Failed | Detected kill calibrations check |
When Execute the generic tests
Then Check the generic test results
# ================================================================================================================================
@@ -0,0 +1,64 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net481</TargetFramework>
<Nullable>enable</Nullable>
<AssemblyName>Xylem.Common.Production.ProductionUiCordonel.UnitTests</AssemblyName>
<RootNamespace>Xylem.Common.Production.ProductionUiCordonel.UnitTests</RootNamespace>
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Folder Include="Drivers\" />
<Folder Include="Hooks\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.3.0" />
<PackageReference Include="NCrunch.Framework" Version="5.11.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="SpecFlow.NUnit" Version="3.3.57" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\MockGenesis\MockGenesis.csproj" />
<ProjectReference Include="..\..\LaaPackages\LaaPackages.Extensions\LaaPackages.Extensions.csproj" />
<ProjectReference Include="..\..\LaaPackages\LaaPackages.Features.Cordonel\LaaPackages.Features.Cordonel.csproj" />
<ProjectReference Include="..\..\LaaPackages\LaaProductionWeb.Resources\LaaProductionWeb.Resources.csproj" />
<ProjectReference Include="..\..\Utils\ClassAccess\ClassAccess.csproj" />
<ProjectReference Include="..\ProductionUiCordonel\ProductionUiCordonel.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Features\FinalParamSequence_Write_Store_Reboot_Verify.feature.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FinalParamSequence_Write_Store_Reboot_Verify.feature</DependentUpon>
</Compile>
<Compile Update="Steps\CommonProductionProcessSteps.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Features\ProductionProcessCheckProductionState.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>ProductionProcessCheckProductionState.feature.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<SpecFlowFeatureFiles Update="Features\FinalParamSequence_Write_Store_Reboot_Verify.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>FinalParamSequence_Write_Store_Reboot_Verify.feature.cs</LastGenOutput>
</SpecFlowFeatureFiles>
<SpecFlowFeatureFiles Update="Features\ProductionProcessCheckProductionState.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>ProductionProcessCheckProductionState.feature.cs</LastGenOutput>
</SpecFlowFeatureFiles>
</ItemGroup>
</Project>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="Mode" value="1" /> <!--1 = EOL ; 2 = Picking-->
<add key="SirtComport433" value="14" /> <!--Individual for Thomas-->
<add key="SirtComport868" value="15" /> <!--Individual for Thomas-->
<add key="SirtBoxNr" value="2" /> <!--Roland and Thomas, box for development, box at shipping station is 1-->
<add key="StationID" value="1" /> <!--Roland and Thomas, shipping station is 1-->
</appSettings>
</configuration>
@@ -0,0 +1,8 @@
<Application x:Class="Xylem.Common.Production.ProductionUiCordonel.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
<Application.Resources>
</Application.Resources>
</Application>
@@ -0,0 +1,41 @@
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
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
private void Application_Startup(Object sender, StartupEventArgs e)
{
switch (ConfigurationManager.AppSettings["Mode"])
{
case "1":
var ProductionWindow = new ProductionWindow(
ProductionUiCordonel.Properties.Resources.StrEolSoftwareNameVersion,
"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,
"ProductionUiCordonel", new EolProcessStateDef());
ProductionWindow.Show();
break;
}
}
}
}
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace Xylem.Common.Production.ProductionUiCordonel
{
/// <summary>
/// Configuration of ProductionUi
/// </summary>
public class ApplicationSettings
{
public class Pos
{
public Double Left { get; set; }
public Double Top { get; set; }
public Double Height { get; set; }
public Double Width { get; set; }
}
public class MultiFormChilds
{
public Type ViewModelType { get; set; }
public Boolean Visisble { get; set; }
public String Name { get; set; }
public Int32 Slot { get; set; }
public String TextPort { get; set; }
public Pos StartPos { get; set; }
}
public Int32 Slot { get; set; }
public Int32? PressureStationId { get; set; }
public Boolean AutoDetect { get; set; }
public Boolean AccessToPresuretestTab { get; set; }
public Boolean AccessToMarrieTab { get; set; }
public Boolean AccessToFinalcheckTab { get; set; }
public Boolean AccessToSettingTab { get; set; }
public Boolean AccessToStatusTab { get; set; }
public Boolean AccessToPickingTab { get; set; }
public Int32? SirtComport433 { get; set; }
public Int32? SirtComport866 { get; set; }
public Int32? SirtBoxNr { get; set; }
public Int32? StationId { get; set; }
public List<MultiFormChilds> ChildForms { get; set; }
/// <summary>
/// ctor
/// </summary>
public ApplicationSettings()
{
Slot = 0;
AutoDetect = false;
AccessToPresuretestTab = false;
AccessToMarrieTab = false;
AccessToFinalcheckTab = false;
AccessToSettingTab = false;
AccessToPickingTab = false;
AccessToStatusTab = false;
PressureStationId = null;
SirtComport433 = null;
SirtComport866 = null;
SirtBoxNr = null;
ChildForms = new List<MultiFormChilds>();
StationId = null;
}
/// <inheritdoc />
public ApplicationSettings(String file)
{
//var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
//nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file);
//if (!File.Exists(configFile))
//{
var configFile = Path.Combine(file);
if (!File.Exists(configFile))
{
Slot = 0;
AutoDetect = false;
AccessToPresuretestTab = false;
AccessToMarrieTab = false;
AccessToFinalcheckTab = false;
AccessToSettingTab = false;
AccessToPickingTab = false;
AccessToStatusTab = false;
PressureStationId = null;
ChildForms = new List<MultiFormChilds>();
StationId = null;
return;
}
//}
using (var tr = new StreamReader(configFile))
{
var meterConfig = JsonConvert.DeserializeObject<ApplicationSettings>(tr.ReadToEnd());
if (meterConfig != null)
{
Slot = meterConfig.Slot;
AutoDetect = meterConfig.AutoDetect;
AccessToPresuretestTab = meterConfig.AccessToPresuretestTab;
AccessToMarrieTab = meterConfig.AccessToMarrieTab;
AccessToFinalcheckTab = meterConfig.AccessToFinalcheckTab;
AccessToSettingTab = meterConfig.AccessToSettingTab;
AccessToPickingTab = meterConfig.AccessToPickingTab;
AccessToStatusTab = meterConfig.AccessToStatusTab;
PressureStationId = meterConfig.PressureStationId;
ChildForms = meterConfig.ChildForms;
StationId = meterConfig.StationId;
}
}
}
public void Update(String file)
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
nameof(Hardware.WaterMeter.Genesis), file);
if (!File.Exists(configFile))
{
configFile = Path.Combine(file);
}
File.WriteAllText(file, JsonConvert.SerializeObject(this));
}
}
}
@@ -0,0 +1,102 @@
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;
//using System.Windows.Forms;
//namespace ProductionUiCordonel.BarcodeInput
//{
// public class Listener : NativeWindow
// {
// /// <summary>
// /// Initializes a new instance of the BarcodeScannerListener
// /// class. The raw input devices that this class will listen to are
// /// registered with the given window handle.
// /// </summary>
// /// <param name="form">the form that should listen for
// /// barcode scans</param>
// /// <exception cref="ArgumentNullException">if the form is null</exception>
// /// <exception cref="ApplicationException">if we are unable to register
// /// for raw input devices</exception>
// /// <exception cref="ConfigurationErrorsException">if an error occurs
// /// during configuration</exception>
// public Listener(Form form)
// {
// IntPtr hwnd;
// if (form == null)
// {
// throw new ArgumentNullException("form");
// }
// hwnd = form.Handle;
// HookRawInput(hwnd); // We'll take a look at this in a minute
// this.HookHandleEvents(form);
// this.AssignHandle(hwnd);
// }
// /// <summary>
// /// Registers ourselves to listen to raw input from keyboard-like devices.
// /// </summary>
// /// <param name="hwnd">the handle of the form that will receive the raw
// /// input messages</param>
// /// <exception cref="InvalidOperationException">if the call to register with the
// /// raw input API fails for some reason</exception>
// private static void HookRawInput(IntPtr hwnd)
// {
// NativeMethods.RAWINPUTDEVICE[] rid;
// rid = new NativeMethods.RAWINPUTDEVICE[1];
// rid[0].usUsagePage = 0x01; // USB HID Generic Desktop Page
// rid[0].usUsage = 0x06; // Keyboard Usage ID
// rid[0].dwFlags = NativeMethods.RawInputDeviceFlags.RIDEV_INPUTSINK;
// rid[0].hwndTarget = hwnd;
// if (!NativeMethods.RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
// {
// InvalidOperationException e;
// e = new InvalidOperationException(
// "The barcode scanner listener could not register for raw input devices.",
// new Win32Exception());
// throw e;
// }
// }
// /// <summary>
// /// Hooks into the form's HandleCreated and HandleDestoryed events
// /// to ensure that we start and stop listening at appropriate times.
// /// </summary>
// /// <param name="form">the form to listen to</param>
// private void HookHandleEvents(Form form)
// {
// form.HandleCreated += this.OnHandleCreated;
// form.HandleDestroyed += this.OnHandleDestroyed;
// }
// /// <summary>
// /// When the form's handle is created, let's hook into it so we can see
// /// the WM_INPUT event.
// /// </summary>
// /// <param name="sender">the form whose handle was created</param>
// /// <param name="e">the event arguments</param>
// private void OnHandleCreated(object sender, EventArgs e)
// {
// this.AssignHandle(((Form)sender).Handle);
// }
// /// <summary>
// /// When the form's handle is destroyed, let's unhook from it so we stop
// /// listening and allow the OS to free up its resources.
// /// </summary>
// /// <param name="sender">the form whose handle was destroyed</param>
// /// <param name="e">the event arguments</param>
// private void OnHandleDestroyed(object sender, EventArgs e)
// {
// this.ReleaseHandle();
// }
// }
//}
@@ -0,0 +1,101 @@
namespace ProductionUiCordonel.Data
{
/// <summary>
/// Genesis meter final test states
/// </summary>
public enum FinalTestState
{
/// <summary>
/// Idle (nothing to do)
/// </summary>
Idle,
/// <summary>
/// Start the initial execution
/// </summary>
StartInitial,
/// <summary>
/// Error handling
/// </summary>
Error,
/// <summary>
/// Single processing step failed
/// </summary>
StepFailed,
/// <summary>
/// Repeat last step
/// </summary>
RepeatStep,
/// <summary>
/// Repeat the entire final test
/// </summary>
RepeatFinalTest,
/// <summary>
/// Final test failed
/// </summary>
FinalTestFailed,
/// <summary>
/// Check the actual shipping state
/// </summary>
CheckShippingState,
/// <summary>
/// Check the attachments
/// </summary>
CheckAttachments,
/// <summary>
/// Check the order number
/// </summary>
CheckOrderNumber,
/// <summary>
/// Check the serial number
/// </summary>
CheckSerialNumber,
/// <summary>
/// Connect to Cordonel
/// </summary>
ConnectCordonel,
/// <summary>
/// Store all configurations to FLASH memory
/// </summary>
StoreAllConfigurations,
/// <summary>
/// Reboot the Cordonel to check all parameters being stored to FLASH memory
/// </summary>
RebootCordonel,
/// <summary>
/// Finalize process
/// </summary>
FinalizeProcess,
/// <summary>
/// Final parametrization fixed values
/// </summary>
FinalParametrizationFix,
/// <summary>
/// Final parametrization with VAKO and CSD
/// </summary>
FinalParametrizationVakoCsd,
/// <summary>
/// Read out all parametrization values
/// </summary>
FullReadOutParametrization,
/// <summary>
/// Check all settings for against required values
/// </summary>
ConfigurationCheck,
/// <summary>
/// Acquire, process set the password file
/// </summary>
ProcessPasswordFile,
/// <summary>
/// Process special configuration
/// </summary>
ProcessSpecialConfig,
/// <summary>
/// Prepare shipping mode
/// </summary>
PrepareShippingMode,
/// <summary>
/// Delimiter for list
/// </summary>
StateListDelimiter
}
}
@@ -0,0 +1,52 @@
using System;
using System.Linq;
using ProductionUiCordonel.Properties;
namespace ProductionUiCordonel.Data
{
/// <summary>
/// Concatenation of state and string
/// </summary>
public static class FinalTestStateInfo
{
/// <summary>
/// Build state information
/// </summary>
private static readonly FinalTestStateText[] StateInfos;
/// <summary>
/// Ctor
/// </summary>
static FinalTestStateInfo()
{
StateInfos = new FinalTestStateText[(Int32)FinalTestState.StateListDelimiter];
StateInfos[(Int32)FinalTestState.Idle].State = FinalTestState.Idle;
StateInfos[(Int32)FinalTestState.Idle].Message = Resources.StrStateIdle;
StateInfos[(Int32)FinalTestState.StartInitial].State = FinalTestState.StartInitial;
StateInfos[(Int32)FinalTestState.StartInitial].Message = Resources.StrStateStartInitial;
}
/// <summary>
/// Get the current information
/// </summary>
/// <param name="state">state to search for information text</param>
public static String GetTextFromState(FinalTestState state)
{
return (from stateInfo in StateInfos
where stateInfo.State == state
select stateInfo.Message).FirstOrDefault();
}
/// <summary>
/// Get the state of the text
/// </summary>
/// <param name="text">information text to search if state is assigned</param>
public static FinalTestState GetStateFromText(String text)
{
return (from stateInfo in StateInfos
where stateInfo.Message == text
select stateInfo.State).FirstOrDefault();
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace ProductionUiCordonel.Data
{
/// <summary>
/// Concatenation between final test state and text
/// </summary>
public struct FinalTestStateText
{
/// <summary>
/// The state of the final test
/// </summary>
public FinalTestState State;
/// <summary>
/// The message text of the final test state
/// </summary>
public String Message;
}
}
@@ -0,0 +1,120 @@
using System;
using System.Windows.Media;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
namespace Xylem.Common.Production.ProductionUiCordonel.Data
{
public class PickingItemCheckReturn
{
public Boolean? Result;
public String Msg;
}
public abstract class PickingResultBaseData : IPickingResultData
{
public String Ident { get; set; }
public Int32 Index { get; set; }
public String CheckText { get; set; }
public String CurrentValueText { get; set; }
public Boolean? Result { get; set; }
public Boolean HasError { get; set; }
public Brush ResultColor => getColorFromResult();
public abstract Func<PickingItemCheckReturn> Action { get; }
private Brush getColorFromResult()
{
var converter = new BrushConverter();
var brush = (Brush)converter.ConvertFromString("#000000");
if (Result.HasValue) // && a.Result.Value ))
{
if (!Result.Value)
{
brush = (Brush)converter.ConvertFromString("#FF8000");
}
else
{
brush = (Brush)converter.ConvertFromString("#419C14");
}
}
return brush;
}
}
public interface IPickingResultData
{
String Ident { get; set; }
Int32 Index { get; set; }
String CheckText { get; set; }
String CurrentValueText { get; set; }
Boolean? Result { get; set; }
Boolean HasError { get; set; }
Brush ResultColor { get; }
Func<PickingItemCheckReturn> Action { get; }
}
public class PickingResultRegisterData : PickingResultBaseData
{
public String RegisterIdent { get; set; }
public GenesisMeter ReadMeter { get; set; }
public Object CompareObject { get; set; }
public override Func<PickingItemCheckReturn> Action => staticReadAndCompareAction();
public Func<PickingItemCheckReturn> staticReadAndCompareAction()
{
return delegate
{
var retComp = new PickingItemCheckReturn();
retComp.Result = null;
retComp.Msg = "-";
if (ReadMeter != null && ReadMeter.IsLoggedOn)
{
var r = ReadMeter.ReadRegister(RegisterIdent);
if (CompareObject is UInt32 intNumber)
{
retComp.Result = intNumber.Equals(RegisterConverter.ByteArrayToValue<UInt32>(r));
retComp.Msg = intNumber.ToString();
}
else if (CompareObject is Boolean boolCompare)
{
retComp.Result = boolCompare.Equals(RegisterConverter.ByteArrayToValue<Boolean>(r));
retComp.Msg = boolCompare.ToString();
}
}
return retComp;
};
}
}
public class PickingResultCustomCheckData : PickingResultBaseData
{
public PickingResultCustomCheckData(Func<PickingItemCheckReturn> action)
{
_action = action;
}
public readonly Func<PickingItemCheckReturn> _action;
public override Func<PickingItemCheckReturn> Action => _action;
}
}
@@ -0,0 +1,162 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Xylem.Common.Logic.ProductionOrderCore.TestResults;
namespace Xylem.Common.Production.ProductionUiCordonel.Data
{
public class PressureResultDataNotifyable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public PressureResultDataNotifyable(PressureResultData baseData)
{
LeakRate = baseData.LeakRate;
Passed = baseData.Passed;
PcbId = baseData.PcbId;
Remark = baseData.Remark;
Rev = baseData.Rev;
TesterName = baseData.TesterName;
Testpoints = new ObservableCollection<PressureTestPoints>();
baseData.TestPoints.ForEach(o => Testpoints.Add(o));
TestPressure = baseData.TestPressure;
}
public PressureResultDataNotifyable()
{
}
public void InternalPropertyChanged(String propertyName = "")
{
NotifyPropertyChanged(propertyName);
}
public String PcbId;
private Int32 rev;
public Int32 Rev
{
get => rev;
set
{
rev = value;
InternalPropertyChanged(nameof(Rev));
}
}
private Boolean? passed = false;
public Boolean? Passed
{
get => passed;
set
{
passed = value;
InternalPropertyChanged(nameof(Passed));
}
}
private String testerName = "C. Röhl";
public String TesterName
{
get => testerName;
set
{
testerName = value;
InternalPropertyChanged(nameof(TesterName));
}
}
private String testPressure = "6";
public String TestPressure
{
get => testPressure;
set
{
testPressure = value;
InternalPropertyChanged(nameof(TestPressure));
}
}
private String leakRate = " 0,00001";
public String LeakRate
{
get => leakRate;
set
{
leakRate = value;
InternalPropertyChanged(nameof(LeakRate));
}
}
private String remark = "";
public String Remark
{
get => remark;
set
{
remark = value;
InternalPropertyChanged(nameof(Remark));
}
}
private ObservableCollection<PressureTestPoints> testpoints = new ObservableCollection<PressureTestPoints>
{
new PressureTestPoints { Ident = 1, Input = "2E-07" },
new PressureTestPoints { Ident = 2, Input = "2E-07" },
new PressureTestPoints { Ident = 3, Input = "2E-07" },
new PressureTestPoints { Ident = 4, Input = "2E-07" },
new PressureTestPoints { Ident = 5, Input = "2E-07" },
new PressureTestPoints { Ident = 6, Input = "2E-07" },
new PressureTestPoints { Ident = 7, Input = "2E-07" },
new PressureTestPoints { Ident = 8, Input = "2E-07" },
new PressureTestPoints { Ident = 9, Input = "2E-07" },
new PressureTestPoints { Ident = 10, Input = "2E-07" }
};
public ObservableCollection<PressureTestPoints> Testpoints
{
get => testpoints;
set
{
testpoints = value;
InternalPropertyChanged(nameof(Testpoints));
}
}
internal void SetAllTestPoints(String input)
{
foreach (var item in Testpoints)
{
item.Input = input;
}
InternalPropertyChanged(nameof(Testpoints));
}
public PressureResultData ToBase()
{
var baseData = new PressureResultData();
baseData.LeakRate = LeakRate;
baseData.Passed = Passed;
baseData.PcbId = PcbId;
baseData.Remark = Remark;
baseData.Rev = Rev;
baseData.TesterName = TesterName;
baseData.TestPoints = Testpoints.ToList();
baseData.TestPressure = TestPressure;
return baseData;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
using System;
namespace Xylem.Common.Production.ProductionUiCordonel.Event
{
public class StringArgs : EventArgs
{
public readonly String Content;
public StringArgs(String content)
{
Content = content;
}
}
}
@@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Net;
using LaaPackages.Features.Cordonel.Models;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Logic.SoftwareAccessHelper;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
/// <summary>
/// Check all parameters needed as preparation for shipping.
/// </summary>
public static class EOLHandler
{
/// <summary>
/// Prepare EOL progress for new run:
/// - Delete old EOL progress,
/// - Create new EOL progress.
/// </summary>
/// <returns></returns>
/// <param name="pcbId"></param>
/// <param name="eolProgress"></param>
/// <param name="errorMessage">error message on failed access</param>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean GetNewEolProgress(String pcbId, EOLProgressModel eolProgress, out String errorMessage)
{
errorMessage = "";
if (!DeleteEolProgress(pcbId, out errorMessage))
return false;
return GetOrCreateEolProgress(pcbId, eolProgress, out errorMessage);
}
/// <summary>
/// After the POST the Sentinel can be triggered. This will check all executed EOL progress
/// steps and feeds back the result.
/// </summary>
/// <returns></returns>
/// <param name="pcbId"></param>
/// <param name="feedback"></param>
/// <param name="errorMessage">error message on failed access</param>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean TriggerSentinel(String pcbId, out Dictionary<String, String> feedback, out String errorMessage)
{
errorMessage = "";
feedback = null;
try
{
var url = ServiceUrls.EolDataForSentinelServiceUrl() + $"/Check/{pcbId}";
// ReSharper disable once UnusedVariable used for DEBUG purposes
var response = LocalWebRequest.GetRequestWithError(url, out var errorCode, 30000);
// Take the response as additional information even if the check failed
if (errorCode == (Int32)HttpStatusCode.OK)
{
return true;
}
feedback = JsonConvert.DeserializeObject<Dictionary<String, String>>(response);
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
return false;
}
/// <summary>
/// POST EOL progress to DB:
/// - Check if all parameters have been set to avoid posting of failed EOL process.
/// </summary>
/// <returns></returns>
/// <param name="eolProgress"></param>
/// <param name="feedback"></param>
/// <param name="errorMessage">error message on failed access</param>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean PostEolProgress(EOLProgressModel eolProgress, out Dictionary<String, String> feedback,
out String errorMessage)
{
errorMessage = "";
feedback = null;
try
{
var url = ServiceUrls.EolDataForSentinelServiceUrl();
var response = LocalWebRequest.PostRequestAsync(url, 30000, out var statusCode, eolProgress);
if (statusCode == HttpStatusCode.OK)
{
return true;
}
feedback = JsonConvert.DeserializeObject<Dictionary<String, String>>(response);
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
return false;
}
/// <summary>
/// Get the EOL progress information needed for the sentinel
/// </summary>
/// <returns></returns>
/// <param name="pcbId"></param>
/// <param name="eolProgress"></param>
/// <param name="errorMessage">error message on failed access</param>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static Boolean GetOrCreateEolProgress(String pcbId, EOLProgressModel eolProgress, out String errorMessage)
{
errorMessage = "";
try
{
var url = ServiceUrls.EolDataForSentinelServiceUrl() + pcbId;
var response = LocalWebRequest.GetRequest(url, 30000);
if (response != null)
{
var result = JsonConvert.DeserializeObject<EOLProgressModel>(response);
//ClassAccess.CopyAllProperties(eolProgress, result);
var type = typeof(EOLProgressModel);
foreach (var prop in type.GetProperties())
{
if (prop.CanWrite)
prop.SetValue(eolProgress, prop.GetValue(result, null), null);
}
return true;
}
errorMessage = "Missing Web response";
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
return false;
}
/// <summary>
/// Delete the EOL progress from a preceding run as a new one is needed if this has been POSTed.
/// After the POST this is locked for further edit. The indication is the <see cref="EOLProgressModel.EOLCompleteDate"/>
/// is set to a value as this is the last step before pushing.
/// </summary>
/// <returns></returns>
/// <param name="pcbId"></param>
/// <param name="errorMessage">error message on failed access</param>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static Boolean DeleteEolProgress(String pcbId, out String errorMessage)
{
errorMessage = "";
try
{
var url = ServiceUrls.EolDataForSentinelServiceUrl() + pcbId;
var msg = LocalWebRequest.DeleteRequestWithError(url, out var errorCode, 30000);
if (errorCode == (Int32)HttpStatusCode.OK)
return true;
errorMessage = msg;
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
return false;
}
/// <summary>
/// Check if all EOL progresses are
/// - Executed (!= null),
/// - Succeeded (EOLStatus.OK) or
/// - Skipped (EOLStatus.NA).
/// - Ignore fields set by the sentinel after test!
/// Excluded:
/// - SAPExportDone,
/// - SentinelCompleted,
/// - SentinelCompletedDate,
/// - SpecialRequirementId,
/// - SpecialRequirementVersion.
/// </summary>
/// <returns>false if any process failed or is not executed</returns>
/// <param name="eolProgress"></param>
/// <param name="errorMessage"></param>
/// <remarks date="2024-Sep-05..10" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-18" author="Thomas Wiedebusch">
/// - Accepted null for SpecialId and SpecialVersion.
/// </remarks>
public static Boolean AcknowledgeAllEolProgresses(EOLProgressModel eolProgress, out String errorMessage)
{
errorMessage = "";
var retVal = true;
try
{
var type = typeof(EOLProgressModel);
foreach (var prop in type.GetProperties())
{
// Will be changed by the sentinel
if (prop.Name.Equals("SAPExportDone") ||
prop.Name.Equals("SentinelCompleted") ||
prop.Name.Equals("SentinelCompletedDate") ||
// Set to NULL if standard requirement active
prop.Name.Equals("SpecialRequirementId") ||
prop.Name.Equals("SpecialRequirementVersion"))
continue;
if (prop.GetValue(eolProgress) == null)
{
errorMessage += $"{prop.Name} ";
retVal = false;
}
if (prop.PropertyType == typeof(String) &&
string.IsNullOrEmpty((String)prop.GetValue(eolProgress)))
{
errorMessage += $"{prop.Name} ";
retVal = false;
}
if (prop.PropertyType == typeof(Int32) && (Int32)prop.GetValue(eolProgress) == 0)
{
errorMessage += $"{prop.Name} ";
retVal = false;
}
if (prop.PropertyType == typeof(EOLStatus) &&
(EOLStatus)prop.GetValue(eolProgress) == EOLStatus.FAIL)
{
errorMessage += $"{prop.Name} ";
retVal = false;
}
}
return retVal;
}
catch (Exception ex)
{
errorMessage += $"{ex.Message}";
}
return false;
}
/// <summary>
/// Compare two EOL progresses are equal, ignore fields set by the sentinel after test!
/// Excluded:
/// - SAPExportDone,
/// - SentinelCompleted,
/// - SentinelCompletedDate,
/// - ProductionStatusDrainedBatteryLoad,
/// - ProductionStatusRemainingLifeTime,
/// - EOLSoftwareNameAndVersion.
/// </summary>
/// <returns>false if any process is unequal</returns>
/// <param name="eolProgress1"></param>
/// <param name="eolProgress2"></param>
/// <param name="errorMessage"></param>
/// <remarks date="2024-Dec-09" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean CompareEolProgresses(EOLProgressModel eolProgress1, EOLProgressModel eolProgress2,
out String errorMessage)
{
errorMessage = "";
try
{
var type = typeof(EOLProgressModel);
foreach (var prop in type.GetProperties())
{
// Will be changed by the sentinel
if (prop.Name.Equals("SAPExportDone") ||
prop.Name.Equals("SentinelCompleted") ||
prop.Name.Equals("SentinelCompletedDate") ||
// Cannot be compared
prop.Name.Equals("ProductionStatusDrainedBatteryLoad") ||
prop.Name.Equals("ProductionStatusRemainingLifeTime") ||
prop.Name.Equals("EOLSoftwareNameAndVersion"))
continue;
// If booth values are equal, even if null, this is okay
if (prop.GetValue(eolProgress1) == null && prop.GetValue(eolProgress2) == null)
{
continue;
}
if (prop.PropertyType == typeof(String) &&
string.IsNullOrEmpty((String)prop.GetValue(eolProgress1)) &&
string.IsNullOrEmpty((String)prop.GetValue(eolProgress2)))
{
continue;
}
if ((prop.GetValue(eolProgress1) != null && prop.GetValue(eolProgress2) == null) ||
(prop.GetValue(eolProgress1) == null && prop.GetValue(eolProgress2) != null))
{
return false;
}
if (prop.GetValue(eolProgress1) != null && prop.GetValue(eolProgress2) != null)
{
if (prop.GetValue(eolProgress1).Equals(prop.GetValue(eolProgress2)))
{
continue;
}
return false;
}
}
return true;
}
catch (Exception ex)
{
errorMessage += $"{ex.Message}";
}
return false;
}
}
}
@@ -0,0 +1,187 @@
using System;
using System.Threading;
using ProductionUiCordonel.Data;
using Xylem.Common.Utils.ProcessExec.EventArguments;
namespace ProductionUiCordonel.Model
{
/// <summary>
/// Final test model 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.
/// </summary>
class FinalTestModel
{
#region Variables
/// <summary>
/// Port scan result event for message dispatcher to caller
/// </summary>
public event EventHandler<ProcessExecEventArgs> OnProcessUpdate;
// Text for caller to inform about actual process being carried out
private String _actualOperation;
// states of the state machine, the backup state locks against repeated execution
private FinalTestState _finalTestState;
private FinalTestState _finalTestBackupState;
// Process counter for caller to monitor actual progress
private Int32 _overallProcessedStepsCtr;
private Int32 _singleProcessedStepsCtr;
// maximum processing steps for calculation of progress bar
private const Int32 MaxOverallProcessSteps = 20;
private Int32 _maxSingleProcessSteps;
// processing retries
private const Int32 MaxProcessRetries = 2;
private Int32 _processRetryCtr;
#endregion
#region Events
/// <summary>
/// Process event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public virtual void ProcessUpdate_Event(Object sender, ProcessExecEventArgs e)
{
// copy text from overall messages to actual, because this is from sub-routine
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(GetActualStateText(),
OverallProcessPercent, _actualOperation, e.OverallProcessPercent));
}
#endregion
#region ProcessInformation
/// <summary>
/// Process counter in percent
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double OverallProcessPercent => 100.0d *
(_overallProcessedStepsCtr + _singleProcessedStepsCtr) / MaxOverallProcessSteps;
/// <summary>
/// Single file process counter in percent
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double SingleFileProcessPercent => 100.0d * _singleProcessedStepsCtr / _maxSingleProcessSteps;
/// <summary>
/// Returns the final test state text information for display
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public String GetActualStateText()
{
// search text assigned to current state
var text = FinalTestStateInfo.GetTextFromState(_finalTestState);
if (_finalTestState == FinalTestState.Idle)
{
return text;
}
text += _processRetryCtr > 0 ? $"{_processRetryCtr}" : "";
if (_overallProcessedStepsCtr > 0)
{
text += $"{_overallProcessedStepsCtr}/{MaxOverallProcessSteps}";
}
return text;
}
#endregion
#region CtorAssignment
/// <summary>
/// Ctor
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public FinalTestModel()
{
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
}
#endregion
#region StateMachine
/// <summary>
/// State machine of FinalTestModel.
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void StateMachine()
{
// avoid repeated execution of the state machine of identical state
if (_finalTestState == _finalTestBackupState)
{
Thread.Sleep(1);
}
else
{
// remind backup state to avoid repeated execution and side effects
_finalTestBackupState = _finalTestState;
switch (_finalTestState)
{
case FinalTestState.Idle:
break;
case FinalTestState.StartInitial:
_overallProcessedStepsCtr = 0;
_singleProcessedStepsCtr = 0;
_finalTestState = FinalTestState.Idle;
break;
case FinalTestState.StepFailed:
_finalTestState = _processRetryCtr++ < MaxProcessRetries
? FinalTestState.RepeatFinalTest
: FinalTestState.FinalTestFailed;
break;
case FinalTestState.RepeatFinalTest:
_overallProcessedStepsCtr = 0;
_singleProcessedStepsCtr = 0;
_finalTestState = FinalTestState.Idle;
break;
default:
_finalTestState = FinalTestState.Idle;
break;
}
}// lock repeated execution of identical state
#endregion
}
}
}
@@ -0,0 +1,12 @@
using System;
using System.ComponentModel;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public interface IMultiModeChildViewModel : INotifyPropertyChanged, IDisposable
{
String GetChildIdent();
void InputText(String text);
}
}
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
using Xylem.Common.Logic.SoftwareAccessHelper;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public class MarriageMultiModeViewModel : BaseMultiModeChildViewModel
{
private Boolean canMarry;
public Boolean CanMarry
{
get => canMarry;
set
{
canMarry = value;
NotifyPropertyChanged();
}
}
private Int32 serialNr;
public Int32 SerialNr
{
get => serialNr;
set
{
serialNr = value;
NotifyPropertyChanged();
}
}
private Boolean locked = false;
public MarriageMultiModeViewModel(String ctrName, Int32 slotNR, List<String> barcodeTrigger, Int32? stationID = null) :
base(ctrName, slotNR, barcodeTrigger, stationID)
{
VisibilityMarriage = Visibility.Visible;
MeterDetectedHandler += PickingMultiModeViewModel_MeterDetectedHandler;
}
internal async Task<Boolean> Store()
{
var url = $"{ServiceUrls.MarriageServiceUrl()}SetPcbToSerial?PcbId={CurrentPcbID}&SerialNumber={SerialNr}&returnMetroData=0";
return await Task.Run(() =>
{
try
{
//url = "http://10.49.40.25/MeterProcessState/api/Marriage/SetPcbToSerial?PcbId=201420242&SerialNumber=20728820&returnMetroData=1";
var resp = LocalWebRequest.GetRequest(url);
if (!string.IsNullOrEmpty(resp))
{
if (CurrentMeter != null)
{
CurrentMeter.Login();
//if (resp.Trim() != "store")
//{
// var myJsonResponse = JsonConvert.DeserializeObject<IList<Programming_programming_parameters>>(resp);
// foreach (var item in myJsonResponse)
// {
// CurrentMeter.WriteRegister(item.RegisterName, item.RegisterValue);
// }
//}
CurrentMeter.SetProcessState(DisplayCodes.Mounted);
CurrentMeter.GetLastProcessState();
}
return true;
}
Log($"No result for {url} on Store");
return false;
}
catch (Exception ex)
{
if (ex is WebException)
{
Log($"Error on Store {url}: {Environment.NewLine} {((WebException)ex).Message}");
}
else
{
Log(ex.Message);
}
}
return false;
});
}
private void PickingMultiModeViewModel_MeterDetectedHandler(Object sender, EventArgs e)
{
CheckBtnEnable();
}
private void CheckBtnEnable()
{
if (SerialNr > 0 && !string.IsNullOrEmpty(CurrentPcbID))
{
CanMarry = true;
}
else
{
CanMarry = false;
}
}
public override void InputText(String text)
{
try
{
SerialNr = 0;
text = text.Replace(" ", "");
var preFix = "8SEN20";
if (text.Contains(preFix) && text.Contains(","))
{
var tmpSerialNr = 0;
var srText = text.Substring(text.IndexOf(preFix) + preFix.Length, text.IndexOf(",") - preFix.Length);
if (int.TryParse(srText, out tmpSerialNr))
{
SerialNr = tmpSerialNr;
}
}
CheckBtnEnable();
}
catch (Exception ex)
{
Log(ex.Message);
}
finally
{
CheckBtnEnable();
}
}
public override Boolean SlotIsLocked()
{
return locked;
}
}
}
@@ -0,0 +1,332 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using System.Windows;
using NLog;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Utils.Logging;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public abstract class BaseMultiModeChildViewModel : IMultiModeChildViewModel
{
private readonly ILogger _logger;
private Timer tmrAutoDetect;
public EventHandler OnPcbIdDetect;
public List<String> BarcodeTrigger = new List<String>();
private Int32 intervalWithoutDetect = 1000;
private Int32 intervalWithDetect = 10000;
private String _currentPcbID;
private MeterBatch Batch = new MeterBatch();
private Visibility visibilityMarriage = Visibility.Collapsed;
public Visibility VisibilityMarriage
{
get => visibilityMarriage;
set
{
visibilityMarriage = value;
NotifyPropertyChanged();
}
}
private Visibility visibilityPicking = Visibility.Collapsed;
public Visibility VisibilityPicking
{
get => visibilityPicking;
set
{
visibilityPicking = value;
NotifyPropertyChanged();
}
}
private Visibility visibilityPressure = Visibility.Collapsed;
public Visibility VisibilityPressure
{
get => visibilityPressure;
set
{
visibilityPressure = value;
NotifyPropertyChanged();
}
}
public String currentSlotStateText = "none";
public String CurrentSlotStateText
{
get => currentSlotStateText;
set
{
currentSlotStateText = value;
NotifyPropertyChanged();
}
}
private SlotState currentSlotState = SlotState.None;
public SlotState CurrentSlotState
{
get => currentSlotState;
set
{
currentSlotState = value;
switch (currentSlotState)
{
case SlotState.None:
CurrentSlotStateText = "Keiner";
break;
case SlotState.IsDetecting:
CurrentSlotStateText = "Warte auf Zähler Kontakt";
break;
case SlotState.Detected:
CurrentSlotStateText = "Zähler detektiert";
break;
case SlotState.InProgress:
CurrentSlotStateText = "Zähler wird bearbeitet";
break;
case SlotState.Done:
CurrentSlotStateText = "Zähler wurde bearbeitet";
break;
case SlotState.ConnectionLost:
CurrentSlotStateText = "Verbindung getrennt";
break;
case SlotState.Error:
CurrentSlotStateText = "Fehler beim bearbeitet";
break;
case SlotState.Fatal:
CurrentSlotStateText = "Schwerwiegender Fehler";
break;
}
NotifyPropertyChanged();
}
}
private String slotInfo = "-";
public String SlotInfo
{
get => slotInfo;
set
{
slotInfo = value;
NotifyPropertyChanged();
}
}
public enum SlotState
{
None,
IsDetecting,
Detected,
InProgress,
Done,
ConnectionLost,
Error,
Fatal
}
private String childName = "asd";
public String ChildName
{
get => childName;
set
{
childName = value;
NotifyPropertyChanged();
}
}
private String order;
public String Order
{
get => order;
set
{
order = value;
NotifyPropertyChanged();
}
}
public String CurrentPcbID
{
get => _currentPcbID;
set
{
_currentPcbID = value;
NotifyPropertyChanged();
}
}
public GenesisMeter CurrentMeter { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public String GetChildIdent()
{
return ChildName;
}
public Int32 SlotNr;
public BaseMultiModeChildViewModel(String ctrName, Int32 slotNR,List<String> barcodeTrigger, Int32? StationId = null, Boolean useTimer = true)
{
_logger = NLogHelper.CreateOrGetLogger("ProductionUI");
ChildName = ctrName;
SlotNr = slotNR;
SlotInfo = SlotNr.ToString();
if (useTimer)
{
tmrAutoDetect = new Timer(intervalWithoutDetect);
tmrAutoDetect.Elapsed += TmrAutoDetect_Elapsed;
tmrAutoDetect.Enabled = true;
}
BarcodeTrigger = barcodeTrigger;
}
public void TmrAutoDetect_Elapsed(Object sender, EventArgs e)
{
CurrentSlotState = SlotState.IsDetecting;
tmrAutoDetect.Enabled = false;
var hasDetect = Detect(SlotNr);
if (hasDetect)
{
CurrentSlotState = SlotState.Detected;
tmrAutoDetect.Interval = intervalWithDetect;
}
else
{
tmrAutoDetect.Interval = intervalWithoutDetect;
}
tmrAutoDetect.Enabled = true;
}
public void TextForceUpdate()
{
var hasDetect = Detect(SlotNr);
if (hasDetect)
{
CurrentSlotState = SlotState.Detected;
}
}
public virtual void InputText(String text)
{
Order = text;
}
public void Log(String txt)
{
_logger?.Info(txt);
}
public abstract Boolean SlotIsLocked();
public event EventHandler MeterDetectedHandler;
private Boolean Detect(Int32 slot)
{
if (!SlotIsLocked())
{
Log("Start Detect");
Boolean hasDetect = false;
if (string.IsNullOrEmpty(CurrentPcbID) || CurrentMeter == null)
{
Batch = new MeterBatch();
var tryMeter = new GenesisMeter();
try
{
tryMeter.SetupFromConfigFile(slot);
Batch.AddMeter(tryMeter);
tryMeter.Logout();
Log("Try to get PcbID");
CurrentPcbID = tryMeter.GetPcbId();
hasDetect = !string.IsNullOrEmpty(CurrentPcbID);
CurrentMeter = tryMeter;
if (hasDetect)
{
Log($"New PcbID detected {CurrentPcbID}");
MeterDetectedHandler?.Invoke(CurrentMeter, EventArgs.Empty);
}
else
{
Log("No Meter found");
DisposeAllConnections();
}
}
catch (Exception e)
{
Log($"Error occur.{ Environment.NewLine} {e.Message}");
}
}
else
{
Log("Check if Meter is still connected");
var ret = CurrentMeter.GetPcbId();
if (string.IsNullOrEmpty(ret) || ret != CurrentPcbID)
{
Log("Meter connection lost");
hasDetect = false;
DisposeAllConnections();
CurrentPcbID = "";
}
else
{
Log("Meter is still connected");
hasDetect = true;
}
}
return hasDetect;
}
Log("Meter is still connected (locked)");
return true;
}
private void DisposeAllConnections()
{
MeterDetectedHandler?.Invoke(null, EventArgs.Empty);
if (Batch != null)
{
Batch.RemoveAllMeters();
if (CurrentMeter != null)
{
CurrentMeter.Dispose();
}
Batch.Dispose();
}
}
public void Dispose()
{
DisposeAllConnections();
}
}
}
@@ -0,0 +1,516 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using Logic.ProductionToProductMapper.Cordonel;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
using Xylem.Common.Logic.SoftwareAccessHelper;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
internal class MultiModeMasterViewModel : INotifyPropertyChanged
{
private String pickingState = "Warte auf Scanner";
private String pickingScanner = "keine Verbindung";
private String pickingSlot = "-";
private String pickingResult = "-";
private Brush pickingResultColor;
public Brush PickingResultColor
{
get => pickingResultColor;
set
{
pickingResultColor = value;
NotifyPropertyChanged();
}
}
public String PickingState
{
get => pickingState;
set
{
pickingState = value;
NotifyPropertyChanged();
}
}
public String PickingScanner
{
get => pickingScanner;
set
{
pickingScanner = value;
NotifyPropertyChanged();
}
}
public String PickingSlot
{
get => pickingSlot;
set
{
pickingSlot = value;
NotifyPropertyChanged();
}
}
public String PickingResult
{
get => pickingResult;
set
{
pickingResult = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void StartPicking(CancellationTokenSource cancle)
{
var s = new Task(() => { PickingLoop(cancle.Token); }, cancle.Token);
s.Start();
}
public enum PickingStateMachine
{
Init,
WaitForScanning,
ScanningDone,
WaitForDetect,
GetOrderDetails,
Compare,
MeterDone,
MeterError,
StartCom,
WaitForBuildingMeterReady,
WaitForBuildingPressureReady,
}
private PickingStateMachine sm = PickingStateMachine.Init;
private class BarScanner : IDisposable
{
private Boolean isRuning;
public String Input = "";
private String Port;
private CancellationToken Cancellation;
private Task WaitForScannerTask;
public BarScanner(String port, CancellationToken cancellation)
{
Port = port;
Cancellation = cancellation;
}
public void Start()
{
if (!isRuning)
{
if (Port == null)
{
return;
}
WaitForScannerTask = new Task(() =>
{
using (var a = new SerialPort(Port))
{
a.Open();
a.NewLine = "\r";
while (true)
{
if (Cancellation.IsCancellationRequested)
{
isRuning = false;
break;
}
Input = a.ReadLine();
a.Close();
isRuning = false;
break;
}
}
}, Cancellation);
WaitForScannerTask.Start();
}
}
public void Dispose()
{
if (WaitForScannerTask.Status != TaskStatus.Faulted && WaitForScannerTask.Status != TaskStatus.RanToCompletion && WaitForScannerTask.Status != TaskStatus.Canceled)
{
WaitForScannerTask.Dispose();
}
}
}
public Boolean Compare(Dictionary<UInt32, UInt32> checkAppsIdVersion, UInt32 Frequency, UInt32 MeterSize, Boolean PressurePresent)
{
using (var Batch = new MeterBatch())
{
using (var meter = new GenesisMeter())
{
meter.SetupFromConfigFile(PickingSlotNr);
Batch.AddMeter(meter);
PickingState = "Am Zähler anmelden";
if (meter.Login())
{
meter.SetProcessState(DisplayCodes.PickingStarted);
var ret = RunCheck(checkAppsIdVersion, Frequency, MeterSize, PressurePresent, meter);
if (ret)
{
meter.SetProcessState(DisplayCodes.PickingTested);
var url = $"{ServiceUrls.MeterProcessStateUrl()}SetPickingState?PcbID={meter.PcbId}&FertigungsauftragsNr={PickingOrder}";
var RetString = LocalWebRequest.PostRequestAsyncAndGetContent(url);
var result = false;
if (bool.TryParse(RetString, out result))
{
return result;
}
}
else
{
meter.SetProcessState(DisplayCodes.PickingFailed);
}
}
}
}
return false;
}
private Boolean RunCheck(Dictionary<UInt32, UInt32> checkAppsIdVersion, UInt32 Frequency, UInt32 MeterSize, Boolean PressurePresent, GenesisMeter meter)
{
PickingState = "Üperprüfe Parameter";
var FrequencyReadOut = RegisterConverter.ByteArrayToValue<UInt32>(meter.ReadRegister("SENSUSRADIO_FrequencyIndicator"));
if (FrequencyReadOut != Frequency)
{
PickingState = $"Falsche Frequenz! soll {Frequency}MHz ist {FrequencyReadOut}MHz";
return false;
}
var MeterSizeReadOut = RegisterConverter.ByteArrayToValue<UInt32>(meter.ReadRegister("GENESISFLOW_MeterSize"));
if (MeterSizeReadOut != MeterSize)
{
PickingState = $"Meter Dn falsch! soll {MeterSize}DN ist {MeterSizeReadOut}DN";
return false;
}
var PressureReadOut = RegisterConverter.ByteArrayToValue<Boolean>(meter.ReadRegister("METROLOGYASST_PressurePresent"));
if (PressureReadOut != PressurePresent)
{
if (PressurePresent)
{
PickingState = "Pcb mit Drucksensor. Laut Auftrag keine verbaut";
}
else
{
PickingState = "Pcb ohne Drucksensor. Laut Auftrag soll eine verbaut werden";
}
//todo raus
return false;
}
foreach (var item in checkAppsIdVersion)
{
try
{
var MeterFWApp = meter.MeterAppListVersion.First(a => item.Key == a.AppId);
if (MeterFWApp == null || MeterFWApp.Version != item.Value)
{
PickingState = "Falsche FW, bitte Updaten";
return false;
}
}
catch (Exception)
{
PickingState = "Fehler beim auslesen der FW";
return false;
}
}
try
{
meter.WriteRegister("METROLOGYASST_StoreConfiguration", 1);
Thread.Sleep(100);
var storeRet = meter.ReadRegister("METROLOGYASST_StoreConfiguration");
if (!storeRet.All(a => a.Equals(0)))
{
PickingState = "Filesystem corrupt";
return false;
}
}
catch (Exception)
{
PickingState = "Fehler beim überprüfen des Filesystems";
return false;
}
return true;
}
private PickingCompareItem PickingOrderDetails;
private BarScanner PickingBarScanner;
private String PickingOrder;
private String PickingPcbId;
private String PickingScannerPort = "COM6";
private Int32 PickingSlotNr = 1;
//PickingState,PickingScanner,PickingSlot,PickingResult
private void PickingLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
switch (sm)
{
case PickingStateMachine.Init:
PickingState = "Initialisierung";
PickingSlot = $"Inaktiv EP {PickingSlotNr}";
PickingScanner = $"Geschlossen {PickingScannerPort}";
//CheckAllPorts?
sm = PickingStateMachine.StartCom;
break;
case PickingStateMachine.StartCom:
PickingOrderDetails = null;
PickingScanner = $"Öfffne {PickingScannerPort}";
//CheckAllPorts?
PickingBarScanner = new BarScanner(PickingScannerPort, token);
PickingBarScanner.Start();
sm = PickingStateMachine.WaitForScanning;
break;
case PickingStateMachine.WaitForScanning:
PickingScanner = $"Offen {PickingScannerPort}";
PickingState = $"Warte auf Scannereingabe({PickingScannerPort})";
if (PickingBarScanner != null && !string.IsNullOrEmpty(PickingBarScanner.Input))
{
PickingOrder = PickingBarScanner.Input;
PickingScanner = $"Geschlossen {PickingScannerPort}";
PickingBarScanner = null;
sm = PickingStateMachine.WaitForDetect;
}
break;
case PickingStateMachine.WaitForDetect:
using (var Batch = new MeterBatch())
{
using (var tryMeter = new GenesisMeter())
{
try
{
PickingState = "Warte auf Pcb";
PickingSlot = $"Öffne EP {PickingSlotNr}";
tryMeter.SetupFromConfigFile(PickingSlotNr);
Batch.AddMeter(tryMeter);
tryMeter.Logout();
Log("Try to get PcbID");
PickingPcbId = tryMeter.GetPcbId();
var hasDetect = !string.IsNullOrEmpty(PickingPcbId);
if (hasDetect)
{
PickingState = $"Pcb gefunden {PickingPcbId}";
Log($"New PcbID detected {PickingPcbId}");
sm = PickingStateMachine.GetOrderDetails;
}
else
{
PickingSlot = $"Keine Konnektivität EP {PickingSlotNr}";
Log("No Meter found");
}
}
catch (Exception e)
{
Log($"Error occur.{ Environment.NewLine} {e.Message}");
}
}
}
break;
case PickingStateMachine.GetOrderDetails:
PickingState = $"Lade Auftragsdaten {PickingOrder}";
var retCode = 0;
var str = LocalWebRequest.GetRequestWithError($"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetOrderOverview?ProductionOrderNumber={PickingOrder}&CheckPcbId={PickingPcbId}", out retCode, 15000);
if (retCode == 417)
{
sm = PickingStateMachine.MeterError;
PickingResult = "Keine Radio Parameter";
}
else
{
var retStr = JsonConvert.DeserializeObject<OrderOverViewCordonelWithPicking>(str);
PickingOrderDetails = retStr.PickingCompareItem;
if (retStr.OrderOverViewCordonel.Count == retStr.OrderOverViewCordonel.AssignedPicking)
{
PickingResult = "Auftrag schon komplett";
sm = PickingStateMachine.MeterError;
}
else
{
PickingState = $"Auftragsdaten geladen {PickingOrder}";
sm = PickingStateMachine.Compare;
}
}
break;
case PickingStateMachine.Compare:
//Compare
PickingState = $"Vergleiche Auftragsdaten {PickingOrder} mit Pcb Eigenschaften";
var converter = new BrushConverter();
if (Compare(PickingOrderDetails.CheckAppsIdVersion, PickingOrderDetails.FrequencyIndicator, PickingOrderDetails.MeterSize, PickingOrderDetails.PressurePresent))
{
//erfolg anzeigen und weiter
var b = (Brush)converter.ConvertFromString("#419C14");
b.Freeze();
PickingResultColor = b;
sm = PickingStateMachine.MeterDone;
}
else
{
sm = PickingStateMachine.MeterError;
var b = (Brush)converter.ConvertFromString("#FF8000");
b.Freeze();
PickingResultColor = b;
}
break;
case PickingStateMachine.MeterError:
PickingOrderDetails = null;
PickingState = "Vergleich abgeschlossen";
PickingResult = "Vergleich fertig fehlgeschlagen";
Thread.Sleep(10000);
sm = PickingStateMachine.StartCom;
break;
case PickingStateMachine.MeterDone:
PickingState = "Vergleich abgeschlossen warten auf Montage Rückmeldung";
PickingResult = $"Pcb {PickingPcbId} wurde dem Auftrag {PickingOrder} hinzugefügt";
Thread.Sleep(10000);
PickingScanner = $"Öfffne {PickingScannerPort}";
PickingBarScanner = new BarScanner(PickingScannerPort, token);
PickingBarScanner.Start();
sm = PickingStateMachine.WaitForBuildingMeterReady;
//CheckAllPorts?
break;
case PickingStateMachine.WaitForBuildingMeterReady:
if (PickingBarScanner != null && !string.IsNullOrEmpty(PickingBarScanner.Input))
{
if (PickingBarScanner.Input == "%Cordonel_SAP_100%")
{
var url = ServiceUrls.MeterProcessStateUrl();
url = url + $"setSapState?PcbID={PickingPcbId}&code=100&version=1";
try
{
LocalWebRequest.PostRequestAsync(url);
}
catch (Exception ex)
{
}
if (PickingOrderDetails.PressurePresent)
{
PickingBarScanner = new BarScanner(PickingScannerPort, token);
PickingBarScanner.Start();
PickingState = "Montage Rückmeldung erhalten, Montage des Drucksensors bestätigen";
sm = PickingStateMachine.WaitForBuildingPressureReady;
}
else
{
PickingState = "Montage Rückmeldung erhalten, auftrag Scannen";
sm = PickingStateMachine.StartCom;
}
}
}
break;
case PickingStateMachine.WaitForBuildingPressureReady:
if (PickingBarScanner != null && !string.IsNullOrEmpty(PickingBarScanner.Input))
{
if (PickingBarScanner.Input == "%Cordonel_SAP_101%")
{
var url = ServiceUrls.MeterProcessStateUrl();
url = url + $"setSapState?PcbID={PickingPcbId}&code=101&version=1";
try
{
LocalWebRequest.PostRequestAsync(url);
}
catch (Exception ex)
{
}
PickingState = "Montage Rückmeldung erhalten, auftrag Scannen";
sm = PickingStateMachine.StartCom;
}
}
break;
}
Thread.Sleep(1000);
}
}
private void Log(String v)
{
//throw new NotImplementedException();
}
}
}
@@ -0,0 +1,440 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Logic.ProductionToProductMapper.Cordonel;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Production.ProductionUiCordonel.Data;
using MessageBox = System.Windows.Forms.MessageBox;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public class PickingMultiModeViewModel : BaseMultiModeChildViewModel
{
private Boolean inputIsEnabled;
public Boolean InputIsEnabled
{
get => inputIsEnabled;
set
{
inputIsEnabled = value;
NotifyPropertyChanged();
}
}
private Boolean locked;
public PickingMultiModeViewModel(String ctrName, Int32 slotNR, List<String> barcodeTrigger, Int32? stationID = null) : base(ctrName, slotNR, barcodeTrigger, stationID, false)
{
VisibilityPicking = Visibility.Visible;
MeterDetectedHandler += PickingMultiModeViewModel_MeterDetectedHandler;
}
private void PickingMultiModeViewModel_MeterDetectedHandler(Object sender, EventArgs e)
{
}
public override Boolean SlotIsLocked()
{
return locked;
}
public override void InputText(String text)
{
Order = text;
ReloadData();
TextForceUpdate();
}
internal void ReloadData()
{
Task.Run(() =>
{
try
{
if (!locked && !string.IsNullOrEmpty(Order) && Order.Length > 6 && int.TryParse(Order, out var orderNR))
{
var str = LocalWebRequest.GetRequest($"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetOrderOverview?ProductionOrderNumber={orderNR}");
var OrderOverView = JsonConvert.DeserializeObject<OrderOverViewCordonel>(str);
var doneCount = OrderOverView.AssignedPicking;
if (OrderOverView.Count == OrderOverView.AssignedPicking)
{
UpdatePickingUi("Order is already completed", StatusColor, "Input a new order number", 0, 0);
locked = false;
return;
}
str = LocalWebRequest.GetRequestWithError($"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetOrderDetails?ProductionOrderNumber={orderNR}&CheckPcbId={CurrentMeter.PcbId}", out var retCode, 15000);
if (retCode == 417)
{
MessageBox.Show(@"NO radio parameter found");
}
if (OrderOverView.Count == OrderOverView.AssignedPicking)
{
UpdatePickingUi("Order is already completed", StatusColor, "Input a new order number", 0, 0);
locked = false;
return;
}
var OrderDteails = JsonConvert.DeserializeObject<PickingCompareItem>(str);
if (CurrentMeter != null)
{
try
{
locked = true;
//Init(CurrentMeter, new Dictionary<uint, uint>(), OrderDteails.FrequencyIndicator, OrderDteails.MeterSize, OrderDteails.PressurePresent);
var a = new Dictionary<UInt32, UInt32>();
a.Add(0, 486);
a.Add(15, 287);
if (OrderDteails.FrequencyIndicator == 868)
{
a.Add(24, 1067);
}
else
{
a.Add(24, 1066);
}
Init(CurrentMeter, a, OrderDteails.FrequencyIndicator, OrderDteails.MeterSize, OrderDteails.PressurePresent);
var r = Run(CurrentMeter.PcbId, orderNR.ToString());
if (r)
{
doneCount = doneCount + 1;
}
locked = false;
UpdatePickingUi("Start checking meter", StatusColor, orderNR.ToString(), OrderOverView.Count, doneCount);
}
catch (Exception ex)
{
Log(ex.Message);
}
finally
{
locked = false;
}
}
}
else
{
UpdatePickingUi("Please input a valid order number", StatusColor, "no valid order!", 0, 0);
}
}
catch (Exception ex)
{
Log(ex.Message);
}
});
}
private void UpdatePickingUi(String state, Brush StateColor, String order, Int32 toDo, Int32 done)
{
Message = state;
Order = order;
ToDo = toDo;
Done = done;
OnUpdateUi?.Invoke(null, EventArgs.Empty);
//if (true)
//{
//}
// lblStatus.Content = state;
// lblStatus.Background = StateColor;
// lblPickingOrder.Content = $"{Order}({Done}/{toDo}";
// txtHiddenInput.Focus();
// if (Done > 0 && Done >= toDo)
// {
// MessageBox.Show("Order is Finish");
// txtOrderNr.Text = "";
// lblStatus.Content = "Please input a new order number";
// }
//}));
}
//private void RefreshPicking()
//{
// tbarOrder.Visibility = Visibility.Visible;
// if (!string.IsNullOrEmpty(txtOrderNr.Text))
// {
// lblPickingOrder.Content = orderString;
// }
// else
// {
// UpdatePickingUi("Please input a valid order number", PickingData.StatusColor, "no valid order!", 0, 0);
// }
//}
public event EventHandler OnDataChanged;
public event EventHandler OnUpdateUi;
public new event PropertyChangedEventHandler PropertyChanged;
private GenesisMeter currentMeter;
public ObservableCollection<IPickingResultData> Data
{
get; set;
}
private OverallStatus status = OverallStatus.NoDetect;
private OverallStatus Status
{
get => status;
set
{
status = value;
OnDataChanged?.Invoke(value.GetHashCode(), EventArgs.Empty);
}
}
public String Message
{
get; set;
}
private enum OverallStatus
{
NoDetect,
CheckingIsRunning,
CheckingIsDone,
CheckingIsNoMatch,
CheckingIsHasError,
}
public String StatusText
{
get
{
switch (Status)
{
case OverallStatus.NoDetect:
return "Wait for meter";
case OverallStatus.CheckingIsRunning:
return "Is checking, please wait";
case OverallStatus.CheckingIsDone:
return "Succsesfull check next Pcb";
case OverallStatus.CheckingIsNoMatch:
return "Check has mismatch, this PCB cannot be used!";
default:
return "Check has errors, this PCB cannot be used!";
}
}
}
public Brush StatusColor
{
get
{
switch (Status)
{
case OverallStatus.NoDetect:
return (SolidColorBrush)new BrushConverter().ConvertFromString("White");
case OverallStatus.CheckingIsRunning:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Yellow");
case OverallStatus.CheckingIsDone:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Green");
case OverallStatus.CheckingIsNoMatch:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Red");
default:
return (SolidColorBrush)new BrushConverter().ConvertFromString("DarkRed");
}
}
}
public Int32 ToDo
{
get; private set;
}
public Int32 Done
{
get; private set;
}
public void Init(GenesisMeter meter, Dictionary<UInt32, UInt32> checkAppsIdVersion, UInt32 Frequency, UInt32 MeterSize, Boolean PressurePresent)
{
currentMeter = meter;
var data = new List<IPickingResultData>();
data.Add(new PickingResultCustomCheckData(delegate
{
meter.Login();
meter.SetProcessState(DisplayCodes.PickingStarted);
return new PickingItemCheckReturn { Result = true, Msg = "done" };
})
{
Index = data.Count, Ident = "Login", CheckText = "Try to access Meter", HasError = false, Result = null,
CurrentValueText = "-"
});
data.Add(new PickingResultRegisterData
{
ReadMeter = meter, Index = data.Count, Ident = "Check Radio", CheckText = $"{Frequency}MHz",
CompareObject = Frequency, CurrentValueText = "none", HasError = false, Result = null,
RegisterIdent = "SENSUSRADIO_FrequencyIndicator"
});
data.Add(new PickingResultRegisterData
{
ReadMeter = meter, Index = data.Count, Ident = "MeterSize",
CheckText = "DN40-50", CompareObject = MeterSize, CurrentValueText = "none", HasError = false,
Result = null, RegisterIdent = "GENESISFLOW_MeterSize"
});
data.Add(new PickingResultRegisterData
{
ReadMeter = meter, Index = data.Count, Ident = "Pressure",
CheckText = "Pressure Sensor", CompareObject = PressurePresent, CurrentValueText = "-", HasError = false,
Result = null, RegisterIdent = "METROLOGYASST_PressurePresent"
});
foreach (var item in checkAppsIdVersion)
{
data.Add(new PickingResultCustomCheckData(delegate
{
try
{
var MeterFWApp = meter.MeterAppListVersion.First(a => item.Key == a.AppId);
if (MeterFWApp.Version != item.Value)
{
return new PickingItemCheckReturn { Result = false, Msg = MeterFWApp.StrVersion };
}
return new PickingItemCheckReturn { Result = true, Msg = MeterFWApp.StrVersion };
}
catch (Exception)
{
return new PickingItemCheckReturn { Result = false, Msg = "error" };
}
})
{
Index = data.Count, Ident = $"Check AppId {item.Key}", CheckText = $"Version {item.Value}",
HasError = false, Result = null, CurrentValueText = "-"
});
}
data.Add(new PickingResultCustomCheckData(delegate
{
try
{
meter.WriteRegister("METROLOGYASST_StoreConfiguration", 1);
Thread.Sleep(100);
var storeRet = meter.ReadRegister("METROLOGYASST_StoreConfiguration");
if (storeRet.All(a => a.Equals(0)))
{
return new PickingItemCheckReturn { Result = true, Msg = "Filesystem OK" };
}
return new PickingItemCheckReturn { Result = false, Msg = "Filesystem corrupt" };
}
catch (Exception)
{
return new PickingItemCheckReturn { Result = false, Msg = "error" };
}
})
{
Index = data.Count, Ident = "Check Filesystem", CheckText = "Check Filesystem", HasError = false,
Result = null, CurrentValueText = "checking"
});
Status = OverallStatus.CheckingIsRunning;
ChangeDataData(data);
}
private void ChangeDataData(List<IPickingResultData> data)
{
Data = new ObservableCollection<IPickingResultData>(data);
OnDataChanged?.Invoke(Data, EventArgs.Empty);
}
public Boolean Run(String pcbID, String OrderNr)
{
var tmpData = Data.ToList();
foreach (var item in tmpData.Where(a => !a.Result.HasValue).OrderBy(a => a.Index))
{
var result = item.Action.Invoke();
item.Result = result.Result;
item.CurrentValueText = result.Msg;
ChangeDataData(tmpData);
}
if (tmpData.All(a => !a.HasError && a.Result.HasValue && a.Result.Value))
{
currentMeter.SetProcessState(DisplayCodes.PickingTested);
var url = $"{ServiceUrls.MeterProcessStateUrl()}SetPickingState?PcbID={pcbID}&FertigungsauftragsNr={OrderNr}";
var RetString = LocalWebRequest.PostRequestAsyncAndGetContent(url);
if (bool.TryParse(RetString, out var result))
{
Status = OverallStatus.CheckingIsDone;
return result;
}
Status = OverallStatus.CheckingIsHasError;
return false;
}
currentMeter.SetProcessState(DisplayCodes.PickingFailed);
if (tmpData.All(a => a.HasError))
{
Status = OverallStatus.CheckingIsHasError;
}
else
{
Status = OverallStatus.CheckingIsNoMatch;
}
return false;
}
internal void Empty()
{
Status = OverallStatus.NoDetect;
ChangeDataData(new List<IPickingResultData>());
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
}
@@ -0,0 +1,221 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Media;
using Logic.ProductionToProductMapper.Cordonel;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Production.ProductionUiCordonel.Data;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public class PickingViewModel : INotifyPropertyChanged
{
public event EventHandler OnDataChanged;
public event PropertyChangedEventHandler PropertyChanged;
private GenesisMeter currentMeter;
private PickingCompareItem baseFile;
public ObservableCollection<IPickingResultData> Data { get; set; }
private OverallStatus status = OverallStatus.NoDetect;
private OverallStatus Status
{
get => status;
set
{
status = value;
OnDataChanged?.Invoke(value.GetHashCode(), EventArgs.Empty);
}
}
private enum OverallStatus
{
NoDetect,
CheckingIsRunning,
CheckingIsDone,
CheckingIsNoMatch,
CheckingIsHasError,
}
public String StatusText
{
get
{
switch (Status)
{
case OverallStatus.NoDetect:
return "Wait for meter";
case OverallStatus.CheckingIsRunning:
return "Is checking, please wait";
case OverallStatus.CheckingIsDone:
return "Succsesfull check next Pcb";
case OverallStatus.CheckingIsNoMatch:
return "Check has mismatch, this PCB cannot be used!";
default:
return "Check has errors, this PCB cannot be used!";
}
}
}
public Brush StatusColor
{
get
{
switch (Status)
{
case OverallStatus.NoDetect:
return (SolidColorBrush)new BrushConverter().ConvertFromString("White");
case OverallStatus.CheckingIsRunning:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Yellow");
case OverallStatus.CheckingIsDone:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Green");
case OverallStatus.CheckingIsNoMatch:
return (SolidColorBrush)new BrushConverter().ConvertFromString("Red");
default:
return (SolidColorBrush)new BrushConverter().ConvertFromString("DarkRed");
}
}
}
public void Init(GenesisMeter meter, Dictionary<UInt32, UInt32> checkAppsIdVersion, UInt32 Frequency, UInt32 MeterSize, Boolean PressurePresent)
{
currentMeter = meter;
var data = new List<IPickingResultData>();
data.Add(new PickingResultCustomCheckData(delegate
{
var r = meter.Login();
meter.SetProcessState(DisplayCodes.PickingStarted);
return new PickingItemCheckReturn { Result = true, Msg = "done" };
})
{ Index = data.Count, Ident = "Login", CheckText = "Try to access Meter", HasError = false, Result = null, CurrentValueText = "-" });
if (Frequency != 0)
{
data.Add(new PickingResultRegisterData { ReadMeter = meter, Index = data.Count, Ident = "Check Radio", CheckText = "433", CompareObject = Frequency, CurrentValueText = "none", HasError = false, Result = null, RegisterIdent = "SENSUSRADIO_FrequencyIndicator" }); ;
data.Add(new PickingResultRegisterData { ReadMeter = meter, Index = data.Count, Ident = "MeterSize", CheckText = "DN40-50", CompareObject = MeterSize, CurrentValueText = "none", HasError = false, Result = null, RegisterIdent = "GENESISFLOW_MeterSize" });
}
foreach (var item in checkAppsIdVersion)
{
data.Add(new PickingResultCustomCheckData(delegate
{
try
{
var MeterFWApp = meter.MeterAppListVersion.First(a => item.Key == a.AppId);
if (MeterFWApp == null || MeterFWApp.Version != item.Value)
{
return new PickingItemCheckReturn { Result = false, Msg = MeterFWApp.StrVersion };
}
return new PickingItemCheckReturn { Result = true, Msg = MeterFWApp.StrVersion };
}
catch (Exception)
{
return new PickingItemCheckReturn { Result = false, Msg = "error" };
}
})
{ Index = data.Count, Ident = $"Check AppId {item.Key}", CheckText = $"Version {item.Value}", HasError = false, Result = null, CurrentValueText = "-" }); ;
}
data.Add(new PickingResultCustomCheckData(delegate
{
try
{
meter.WriteRegister("METROLOGYASST_StoreConfiguration", 1);
Thread.Sleep(100);
var storeRet = meter.ReadRegister("METROLOGYASST_StoreConfiguration");
if (storeRet.All(a => a.Equals(0)))
{
return new PickingItemCheckReturn { Result = true, Msg = "Filesystem OK" };
}
return new PickingItemCheckReturn { Result = false, Msg = "Filesystem corrupt" };
}
catch (Exception)
{
return new PickingItemCheckReturn { Result = false, Msg = "error" };
}
})
{ Index = data.Count, Ident = "Check Filesystem", CheckText = "Check Filesystem", HasError = false, Result = null, CurrentValueText = "checking" }); ;
Status = OverallStatus.CheckingIsRunning;
ChangeDataData(data);
}
private void ChangeDataData(List<IPickingResultData> data)
{
Data = new ObservableCollection<IPickingResultData>(data);
OnDataChanged?.Invoke(Data, EventArgs.Empty);
}
public Boolean Run(String pcbID, String OrderNr)
{
var tmpData = Data.ToList();
foreach (var item in tmpData.Where(a => !a.Result.HasValue).OrderBy(a => a.Index))
{
var result = item.Action.Invoke();
item.Result = result.Result;
item.CurrentValueText = result.Msg;
ChangeDataData(tmpData);
}
if (tmpData.All(a => !a.HasError && a.Result.HasValue && a.Result.Value))
{
currentMeter.SetProcessState(DisplayCodes.PickingTested);
var url = $"{ServiceUrls.MeterProcessStateUrl()}SetPickingState?PcbID={pcbID}&FertigungsauftragsNr={OrderNr}";
var RetString = LocalWebRequest.PostRequestAsyncAndGetContent(url);
var result = false;
if (bool.TryParse(RetString, out result))
{
Status = OverallStatus.CheckingIsDone;
return result;
}
Status = OverallStatus.CheckingIsHasError;
return false;
}
currentMeter.SetProcessState(DisplayCodes.PickingFailed);
if (tmpData.All(a => a.HasError))
{
Status = OverallStatus.CheckingIsHasError;
}
else
{
Status = OverallStatus.CheckingIsNoMatch;
}
return false;
}
internal void Empty()
{
Status = OverallStatus.NoDetect;
ChangeDataData(new List<IPickingResultData>());
}
}
}
@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
using Xylem.Common.Logic.ProductionOrderCore.ProcessState;
using Xylem.Common.Logic.ProductionOrderCore.TestResults;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Production.ProductionUiCordonel.Data;
using Xylem.Common.Production.ProductionUiCordonel.Event;
namespace Xylem.Common.Production.ProductionUiCordonel.Model
{
public class PressureMultiModeViewModel : BaseMultiModeChildViewModel
{
public event EventHandler<StringArgs> OnStoreDone;
private PressureResultDataNotifyable result = new PressureResultDataNotifyable();
public PressureResultDataNotifyable Result
{
get => result;
set
{
result = value;
NotifyPropertyChanged();
}
}
private Boolean inputIsEnabled;
public Boolean InputIsEnabled
{
get => inputIsEnabled;
set
{
inputIsEnabled = value;
NotifyPropertyChanged();
}
}
public Boolean Locked { get; set; } = false;
public Int32? StationID { get; set; }
public PressureMultiModeViewModel(String ctrName, Int32 slotNR, List<String> barcodeTrigger, Int32? stationID = null) : base(ctrName, slotNR, barcodeTrigger, stationID)
{
VisibilityPressure = Visibility.Visible;
StationID = stationID;
}
public Boolean isHydraulicStaion()
{
if (!StationID.HasValue || StationID.Value == 1)
{
return false;
}
return true;
}
public override Boolean SlotIsLocked()
{
return false;
}
internal void ReloadData(Boolean reset)
{
if (Result != null && Result.PcbId != CurrentPcbID)
{
InputIsEnabled = false;
if (true)
{
}
Result = new PressureResultDataNotifyable { Remark = "loading", Testpoints = new ObservableCollection<PressureTestPoints>(), PcbId = "" };
Task.Run(() =>
{
try
{
var url = ServiceUrls.MeterProcessStateUrl();
url = url + $"GetPressureStateDetailed?PcbId={CurrentPcbID}&TopCount=1";
var retStr = LocalWebRequest.GetRequest(url);
var pressureResultData = JsonConvert.DeserializeObject<List<PressureResultData>>(retStr);
if (!pressureResultData.Any())
{
return new PressureResultDataNotifyable();
}
return new PressureResultDataNotifyable(pressureResultData.First());
}
catch (Exception ex)
{
Log(ex.Message);
return new PressureResultDataNotifyable();
}
}).ContinueWith(a =>
{
var res = a.Result;
res.PcbId = CurrentPcbID;
Result = res;
InputIsEnabled = true;
}
);
}
}
internal void StoreData(Boolean successfull)
{
if (Result != null && Result.PcbId == CurrentPcbID)
{
InputIsEnabled = false;
var isStored = false;
var status = DisplayCodes.PressureTestedFailed;
Task.Run(() =>
{
var url = ServiceUrls.MeterProcessStateUrl();
if (successfull)
{
status = DisplayCodes.PressureTested;
}
try
{
if (StationID == 1)
{
var addResult = new PressureTestResult
{
FreeText = Result.Remark,
Passed = successfull,
StationId = StationID
};
url = url + $"SetPressureState?PcbId={CurrentPcbID}&FertigungsauftragsNr=0";
if (!LocalWebRequest.PostRequestAsync(url, json: addResult))
{
throw new ApplicationException("PressureState: NOT successful");
}
}
else
{
url = url + "SetPressureStateDetailed";
var mutedResult = Result.ToBase();
isStored = LocalWebRequest.PostRequestAsync(url, json: mutedResult);
if (!isStored)
{
OnStoreDone?.Invoke(null, new StringArgs("Can not store Result"));
status = DisplayCodes.PressureTestedFailed;
}
}
CurrentMeter.Login();
CurrentMeter.SetProcessState(status);
}
catch (Exception ex)
{
Log(ex.Message);
status = DisplayCodes.PressureTestedFailed;
OnStoreDone?.Invoke(null, new StringArgs("Can not store Result"));
}
if (status == DisplayCodes.PressureTested)
{
OnStoreDone?.Invoke(null, new StringArgs("Pressure test stored successfully. Meter can passed"));
return;
}
OnStoreDone?.Invoke(null, new StringArgs("Pressure test stored successfully. But Meter is marked with a leakage. Please inform QS"));
});
}
}
}
}
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
<!-- optional, add some variables
https://github.com/nlog/NLog/wiki/Configuration-file#variables
-->
<variable name="myvar" value="myvalue"/>
<!--
See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs.
-->
<targets>
<!--
add your targets here
See https://github.com/nlog/NLog/wiki/Targets for possible targets.
See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
-->
<!--
Write events to a file with the date in the filename.
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
</targets>
<rules>
<!-- add your logging rules here -->
<!--
Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f"
<logger name="*" minlevel="Debug" writeTo="f" />
-->
</rules>
</nlog>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
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
{
/// <summary>
/// Concatenation for all production process states:
/// </summary>
/// <remarks date="2025-Apr-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public class BaseProcessStatesDef : IProcessStateDef
{
/// <summary>
/// Collection of state information
/// </summary>
public List<ProcessStateStruct> StateContainer { get; }
/// <summary>
/// Ctor:
/// Initialize all hidden standard generic states
/// </summary>
protected BaseProcessStatesDef()
{
StateContainer = new List<ProcessStateStruct>
{
new ProcessStateStruct(
productionProcess: null,
processState: ProcessState.Idle,
processInfo: Resources.StrStateIdle,
productionProcessNo: null,
nextStateOnSuccess: ProcessState.Idle),
new ProcessStateStruct(
productionProcess: new ErrorHandler(null),
processState: ProcessState.Error,
processInfo: Resources.StrProcessStateError,
productionProcessNo: null,
nextStateOnSuccess: ProcessState.Idle,
startAlways: true),
new ProcessStateStruct(
productionProcess: null,
processState: ProcessState.RepeatFailedTests,
processInfo: Resources.StrStateRepeatFinalTest,
productionProcessNo: null,
nextStateOnSuccess: BaseProductionProcess.DoNotChangeStateAfterCompletion),
new ProcessStateStruct(
productionProcess: null,
processState:ProcessState.AbortTest,
processInfo:Resources.StrStateSkipping,
productionProcessNo:null,
nextStateOnSuccess:ProcessState.Idle),
new ProcessStateStruct(
productionProcess: null,
processState:ProcessState.Stop,
processInfo:Resources.StrStateUserStop,
productionProcessNo:null,
nextStateOnSuccess:ProcessState.Idle),
new ProcessStateStruct(
productionProcess: null,
processState:ProcessState.PrintSuccessReport,
processInfo:Resources.StrStatePrintSuccessReport,
productionProcessNo:null,
nextStateOnSuccess:ProcessState.Idle),
new ProcessStateStruct(
productionProcess: null,
processState:ProcessState.PrintReturnReport,
processInfo:Resources.StrStatePrintReturnReport,
productionProcessNo:null,
nextStateOnSuccess:ProcessState.Idle)
};
}
///<inheritdoc/>
public ProcessStateStruct GetProcessStateStructOfState(ProcessState stateMachineState)
{
return (from stateContainer in StateContainer
where stateContainer.ProcessState == stateMachineState
select stateContainer).FirstOrDefault();
}
///<inheritdoc/>
public IProductionProcess GetProcessOfState(ProcessState stateMachineState)
{
return (from stateContainer in StateContainer
where stateContainer.ProcessState == stateMachineState
select stateContainer.ProductionProcess).FirstOrDefault();
}
///<inheritdoc/>
public String GetStateInfoOfState(ProcessState? stateMachineState)
{
if (stateMachineState == null)
stateMachineState = ProcessState.Error;
return (from stateContainer in StateContainer
where stateContainer.ProcessState == stateMachineState
select stateContainer.ProcessInfo).FirstOrDefault();
}
///<inheritdoc/>
public String GetStateInfoOfProcess(IProductionProcess process)
{
return (from stateContainer in StateContainer
where stateContainer.ProductionProcess == process
select stateContainer.ProcessInfo).FirstOrDefault();
}
///<inheritdoc/>
public ProcessState GetStateOfInfoText(String text)
{
return (from stateContainer in StateContainer
where stateContainer.ProcessInfo == text
select stateContainer.ProcessState).FirstOrDefault();
}
///<inheritdoc/>
public ProcessState GetStateOfProcess(IProductionProcess process)
{
return (from stateContainer in StateContainer
where stateContainer.ProductionProcess == process
select stateContainer.ProcessState).FirstOrDefault();
}
///<inheritdoc/>
public Int32 GetNumberOfInitializedProcesses()
{
return StateContainer.Count(stateContainer => stateContainer.ProductionProcessNo != null);
}
///<inheritdoc/>
public List<IProductionProcess> GetAllProductionProcesses()
{
var sortedList = StateContainer
.Where(y => y.ProductionProcessNo != null)
.OrderBy(x => x.ProductionProcessNo);
return (from state in sortedList select state.ProductionProcess).ToList();
}
}
}
@@ -0,0 +1,530 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using LaaPackages.Features.Cordonel.Models;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Logic.ProductionOrderCore;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs;
using Xylem.Common.Production.ProductionUiCordonel.UserControls;
using Xylem.Common.Utils.ProcessExec;
using Xylem.Common.Utils.ProcessExec.EventArguments;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
{
/// <summary>
/// The base production process defines the tools needed to initiate, start and finalize a process. It holds the
/// statistic counters.
/// </summary>
public abstract class BaseProductionProcess : IProductionProcess
{
#region ------------------------------------------ Variables --------------------------------------------------
// remind manually changed culture setting
private static readonly CultureInfo _cultureInfo = Thread.CurrentThread.CurrentCulture;
// user defined control for main screen
protected UserControl UserControl
{
get; set;
}
/// <summary>
/// Avoid state change after successfully completion of production process. This will be used if multiple
/// processes are executed and the state has been changed manually.
/// </summary>
public static readonly ProcessState? DoNotChangeStateAfterCompletion = null;
/// <summary>
/// Number of processing steps for this actual process progress
/// </summary>
private Int32 _maxActualProcessSteps;
private Int32 MaxActualProcessProgress
{
get => _maxActualProcessSteps;
set
{
_maxActualProcessSteps = value;
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxActualProcessSteps,
ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps));
}
}
/// <summary>
/// Progress of actual process
/// </summary>
private Int32 _actualProcessProgress;
protected Int32 ActualProcessProgress
{
get => _actualProcessProgress;
set
{
_actualProcessProgress = value;
if (_actualProcessProgress > _maxActualProcessSteps)
_actualProcessProgress = _maxActualProcessSteps;
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_actualProcessProgress,
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
}
}
///<inheritdoc/>
public SingleProcessState SingleProcessState
{
get; private set;
}
/// <summary>
/// The forced exit state will be used by e.g. the error process to keep the standard handling
/// intact but switch to a dynamic (e.g. based on a dialog input) exit which is not predictable
/// by the standard state machine implementation.
/// </summary>
protected ProcessState? ForcedSuccessExitState
{
set; get;
}
///<inheritdoc/>
public String ProcessName
{
get;
}
///<inheritdoc/>
public Version Version => _version;
/// <inheritdoc />
public GenesisStatus ProductionStatus
{
set; protected get;
}
/// <inheritdoc />
public CordonelRequirements ProductionRequirements
{
set; protected get;
}
/// <inheritdoc />
public EOLProgressModel EolProgress
{
set; protected get;
}
/// <inheritdoc />
public IGenesisMeter Meter
{
protected get; set;
}
/// <inheritdoc />
public RegisterRestorer RegisterRestorer
{
protected get; set;
}
/// <inheritdoc />
public List<String> RadioEncryptionKey
{
protected get; set;
}
/// <inheritdoc />
public MeterPwdDb PwdContainer
{
set; protected get;
}
/// <summary>
/// Is for processes which do not want to log the execution or state change like ERROR process,
/// </summary>
protected Boolean IsSilentProcess
{
set; get;
}
// assembly version information
private static readonly Version _version = Assembly.GetExecutingAssembly().GetName().Version;
/// <inheritdoc/>
public CancellationToken CancellationToken { set; get; }
/// <inheritdoc/>
public event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
///<inheritdoc/>
public event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
///<inheritdoc/>
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
///<inheritdoc/>
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
#endregion --------------------------------------- Variables --------------------------------------------------
#region ------------------------------------------ Init -------------------------------------------------------
/// <summary>
/// Ctor:
/// - Init.
/// </summary>
/// <param name="name">name for process to display a message</param>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
protected BaseProductionProcess(String name)
{
ProcessName = name;
SingleProcessState = SingleProcessState.Waiting;
}
#endregion --------------------------------------- Init -------------------------------------------------------
#region ------------------------------------------ Tools ------------------------------------------------------
/// <summary>
/// Set overall process to 100 %
/// </summary>
protected void SetOverallProcessTo100Percent()
{
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(Int32.MaxValue,
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
}
/// <inheritdoc/>
public UserControl GetUserControl()
{
// Invoker needed for UserControls
Application.Current.Dispatcher.Invoke(InitUserControl);
// Null as return is going to be used to remove the userControl from main screen
return UserControl;
}
/// <summary>
/// Common routine for single register write, log of result and increase process counter.
/// </summary>
/// <param name="proPar"></param>
/// <returns>true if successfully</returns>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
/// - Inverse raw register bytes before conversion to text using the
/// <see cref="RegisterConverter.GetRegisterContentText"/>.
/// </remarks>
protected Boolean WriteRegisterLogAndProcessCtr(ProgrammingParameters proPar)
{
var retVal = false;
var msg = "?";
try
{
// write without check
retVal = Meter.WriteRegister(proPar.RegisterName, proPar.RegisterValue);
// convert to readable value
var registerDefinitions = Meter.GetConfigRegistersDefinitions();
var regDef = registerDefinitions.GetRegisterDefinitionByName(proPar.RegisterName);
msg = RegisterConverter.GetRegisterContentText(regDef, proPar.RegisterValue);
}
catch (Exception ex)
{
Meter.WriteLog(ex.Message);
}
finally
{
if (retVal)
{
SuccessMsgDispatcher($"{Resources.StrSuccessMsgWrite}: {msg}");
}
else
{
ErrorMsgDispatcher($"{Resources.StrErrorMsgWrite}: {msg}");
}
}
ActualProcessProgress++;
return retVal;
}
/// <summary>
/// Action for cancellation token to abort process
/// </summary>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
protected StatusReturn CancellationProcedure()
{
AbortProcess();
return StatusReturn.Failed;
}
/// <summary>
/// Set new single process state and fire event from base class:
/// - First set new SingleProcessState,
/// - Last fire the SingleProcessState.
///
/// ATTENTION: Made it private to avoid INTERRUPTION of state machine!
/// This is caused by the eventual removal of the process events using the OnSingleProcessStateChanged.
///
/// </summary>
/// <param name="sender"></param>
/// <param name="state"></param>
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Set single process state for calling production process,
/// - fire single process state event.
/// </remarks>
/// <remarks date="2024-Nov-08" author="Thomas Wiedebusch">
/// - PreExecuteProcess.
/// </remarks>
private void SetSingleProcessStateAndFireEvent(Object sender, SingleProcessState state)
{
SingleProcessState = state;
OnSingleProcessStateChanged?.Invoke(sender, new SingleProcessStateArgs(state));
}
/// <inheritdoc cref="IProductionProcess"/>
public void StartProcess(ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.AbortTest)
{
if (Meter == null)
{
// first signal overall process state change because the single process state change uninstalls
// all events from process
OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(ProcessState.Error));
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Error);
}
var userControl = (IUserControl)UserControl;
userControl?.Clear();
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitProcess, PreExecuteProcess, ExecuteProcess, FinalizeProcess,
_cultureInfo, exitProcessStateObjects, CancellationToken);
}
/// <inheritdoc cref="IProductionProcess"/>
public virtual void InitProcess()
{
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Waiting);
}
/// <inheritdoc cref="IProductionProcess"/>
public virtual void IdleProcess()
{
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Idle);
}
/// <inheritdoc cref="IProductionProcess"/>
public virtual StatusReturn PreExecuteProcess()
{
return StatusReturn.Okay;
}
/// <inheritdoc cref="IProductionProcess"/>
public virtual void AbortProcess()
{
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Abort);
}
/// <inheritdoc cref="IProductionProcess"/>
public virtual void RetryProcess()
{
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Retry);
}
/// <inheritdoc cref="IProductionProcess"/>
public abstract StatusReturn ExecuteProcess();
/// <inheritdoc cref="IProductionProcess"/>
public virtual void FinalizeProcess(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
ActualProcessProgress = MaxActualProcessProgress;
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState);
if (ForcedSuccessExitState != null)
successExitState = ForcedSuccessExitState;
// Catch the abort request or command to idle on process exit
if (SingleProcessState == SingleProcessState.Abort || SingleProcessState == SingleProcessState.Idle)
{
// Log user abort for process before uninstalling the events for this process
ErrorMsgDispatcher($"{Resources.StrErrorMsgAbortProcess}: {ProcessName}");
return;
}
if (success == StatusReturn.Okay || success == StatusReturn.Warning || success == StatusReturn.Skipped)
{
// Change state only if allowed or on abort or idle request
if (successExitState != DoNotChangeStateAfterCompletion &&
SingleProcessState != SingleProcessState.Abort &&
SingleProcessState != SingleProcessState.Idle)
{
// Signal state change to state machine
OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(successExitState));
}
if (success == StatusReturn.Okay)
{
// log successfully execution of process before uninstalling the events for this process
SuccessMsgDispatcher($"{Resources.StrSuccessMsgProcess}: {ProcessName}");
// ATTENTION: This will uninstall all events for this process!
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Succeeded);
}
else if (success == StatusReturn.Skipped)
{
// log successfully execution of process before uninstalling the events for this process
SuccessMsgDispatcher($"{Resources.StrSkippedMsgProcess}: {ProcessName}");
// ATTENTION: This will uninstall all events for this process!
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Skipped);
}
else
{
// log successfully execution of process before uninstalling the events for this process
WarningMsgDispatcher($"{Resources.StrWarningMsgProcess}: {ProcessName}");
// ATTENTION: This will uninstall all events for this process!
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Warning);
}
return;
}
// first signal overall process state change because the single process state change uninstalls
// all events from process, signal to process controller
OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(errorExitState));
// set message for error of process before uninstalling the events for this process
ErrorMsgDispatcher($"{Resources.StrErrorMsgProcess}: {ProcessName}");
// ATTENTION: This will uninstall all events for this process!
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Error);
}
/// <summary>
/// Common routine to convert objects to process states.
/// Objects are sorted:
/// 1. successState
/// 2. errorState
/// 3. breakState
/// </summary>
/// <param name="objects">input of object array</param>
/// <param name="successState">success state, default set to null meaning leave state as is</param>
/// <param name="errorState">error state, default set to error</param>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private static void ObjectsToProcessStates(IReadOnlyList<Object> objects, out ProcessState? successState,
out ProcessState errorState)
{
successState = DoNotChangeStateAfterCompletion;
errorState = ProcessState.Error;
// cast objects to process states sorted as success, error, break
if (objects.Count > 0 && objects[0] != null)
successState = (ProcessState)objects[0];
if (objects.Count > 1 && objects[1] != null)
errorState = (ProcessState)objects[1];
}
/// <summary>
/// New status for logging
/// </summary>
/// <param name="statusMessage"></param>
/// <param name="solidColorBrush">Brushes color to make the output colorful</param>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
internal void LogNewStatusMsg(String statusMessage, SolidColorBrush solidColorBrush = null)
{
if (!IsSilentProcess)
OnProcessLogRequest?.Invoke(this, new ProcessLogArgs(statusMessage, solidColorBrush));
Meter?.WriteLog(statusMessage);
}
/// <inheritdoc/>
public virtual void ErrorMsgDispatcher(String errorMsg)
{
LogNewStatusMsg(errorMsg, Brushes.Red);
}
/// <inheritdoc/>
public virtual void WarningMsgDispatcher(String warningMsg)
{
LogNewStatusMsg(warningMsg, Brushes.Orange);
}
/// <inheritdoc/>
public virtual void SuccessMsgDispatcher(String successMsg)
{
LogNewStatusMsg(successMsg, Brushes.Green);
}
/// <summary>
/// Common routine to signal the successfully started running state and inform the UI.
/// </summary>
/// <param name="maxActualProcessProgressSteps"></param>
/// <remarks date="2023-Mar-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
protected void SignalRunningState(Int32 maxActualProcessProgressSteps = 1)
{
// set process state for this process and signal it to final controller
SetSingleProcessStateAndFireEvent(this, SingleProcessState.Running);
// set the progress bar on GUI
MaxActualProcessProgress = maxActualProcessProgressSteps;
ActualProcessProgress = 0;
// log and text to the GUI
LogNewStatusMsg($"{Resources.StrStartMsgProcess}: {ProcessName}");
}
/// <summary>
/// Process update event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
protected void ProcessUpdate_Handler(Object sender, ProcessExecEventArgs e)
{
ActualProcessProgress++;
LogNewStatusMsg(e.ActualProcessMessage);
}
/// <summary>
/// Implementation of user control getter in inherited class
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
protected virtual void InitUserControl()
{
}
#endregion --------------------------------------- Tools ------------------------------------------------------
}
}
@@ -0,0 +1,37 @@
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum
{
/// <summary>
/// Dialog results for user control
/// </summary>
public enum DialogResult
{
/// <summary>
/// Undefined
/// </summary>
Undef,
/// <summary>
/// Aborted
/// </summary>
Abort,
/// <summary>
/// Successfully executed
/// </summary>
Done,
/// <summary>
/// Skipped
/// </summary>
Skipped,
/// <summary>
/// Print report
/// </summary>
PrintReport,
/// <summary>
/// Retry action
/// </summary>
Retry,
/// <summary>
/// Error
/// </summary>
Error
}
}
@@ -0,0 +1,25 @@
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum
{
/// <summary>
/// Display states for images
/// </summary>
public enum ImgState
{
/// <summary>
/// Do not display
/// </summary>
Off,
/// <summary>
/// Execution ongoing
/// </summary>
Active,
/// <summary>
/// Successfully executed
/// </summary>
Success,
/// <summary>
/// Error
/// </summary>
Error
}
}
@@ -0,0 +1,73 @@
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum
{
/// <summary>
/// Genesis meter final test states
/// </summary>
public enum PickingProcessState
{
/// <summary>
/// Idle (nothing to do)
/// </summary>
Idle,
/// <summary>
/// Idle (nothing to do)
/// </summary>
ScanOrder,
/// <summary>
/// New meter detected for marker to clear all process contents like production status
/// and programming parameters.
/// </summary>
CheckNewMeter,
/// <summary>
/// Error handling
/// </summary>
Error,
/// <summary>
/// Abort final test execution
/// </summary>
Abort,
/// <summary>
/// Repeat the entire final test
/// </summary>
RepeatFailedTests,
/// <summary>
/// Final test failed
/// </summary>
PickingFailed,
/// <summary>
/// User stopped process
/// </summary>
Stop,
/// <summary>
/// Check the actual production state if all steps are executed
/// </summary>
DetectCordonel,
/// <summary>
/// Connect to Cordonel
/// </summary>
ConnectCordonel,
/// <summary>
/// Request the serial number and radio address from DB
/// </summary>
GetPickingParameters,
/// <summary>
/// Check the radio functionality
/// </summary>
CompareMeterDetails,
InstallLut,
//todo ad excisting processstates
PrintReturnReport,
/// <summary>
/// Print success report
/// </summary>
PrintSuccessReport,
/// <summary>
/// Delimiter for list
/// </summary>
StateListDelimiter
}
}
@@ -0,0 +1,114 @@
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum
{
/// <summary>
/// Genesis meter final test states
/// </summary>
public enum ProcessState
{
/// <summary>
/// Idle (nothing to do)
/// </summary>
Idle,
/// <summary>
/// New meter detected for marker to clear all process contents like production status
/// and programming parameters.
/// </summary>
CheckNewMeter,
/// <summary>
/// Error handling
/// </summary>
Error,
/// <summary>
/// Abort test execution
/// </summary>
AbortTest,
/// <summary>
/// Repeat the entire test
/// </summary>
RepeatFailedTests,
/// <summary>
/// User stopped process
/// </summary>
Stop,
/// <summary>
/// Check the actual production state if all steps are executed
/// </summary>
CheckProductionState,
/// <summary>
/// Check the order number
/// </summary>
CheckOrderNumber,
/// <summary>
/// Check the serial number
/// </summary>
CheckSerialNumber,
/// <summary>
/// Detect Cordonel
/// </summary>
DetectCordonel,
/// <summary>
/// Connect to Cordonel
/// </summary>
ConnectCordonel,
/// <summary>
/// Request the serial number and radio address from DB
/// </summary>
GetSerialNumberAndRadioAddress,
/// <summary>
/// Request the serial number and radio address from DB
/// </summary>
GetProgrammingParameters,
/// <summary>
/// Check the radio functionality
/// </summary>
CheckRadio,
/// <summary>
/// Reboot the Cordonel to check all parameters being stored to FLASH memory
/// </summary>
RebootCordonel,
/// <summary>
/// Final parametrization with VAKO and CSD
/// </summary>
ExecFinalParametrization,
/// <summary>
/// Safe all settings to non-volatile memory to survive a reboot of the Cordonel
/// </summary>
ExecStoreConfiguration,
/// <summary>
/// Visual inspection of Display regarding the accumulator and alarms before shipping
/// </summary>
ExecVisualInspection,
/// <summary>
/// Check for required attachments
/// </summary>
CheckAttachments,
/// <summary>
/// Read out all parametrization values
/// </summary>
CheckFinalParametrization,
/// <summary>
/// Acquire, process set the password file
/// </summary>
ProcessPasswordFile,
/// <summary>
/// Prepare shipping mode
/// </summary>
PrepareShippingMode,
/// <summary>
/// Completed for shipping
/// </summary>
ReadyForShipping,
/// <summary>
/// Print return report
/// </summary>
PrintReturnReport,
/// <summary>
/// Print success report
/// </summary>
PrintSuccessReport,
/// <summary>
/// Delimiter for list
/// </summary>
StateListDelimiter
}
}
@@ -0,0 +1,33 @@
using System;
namespace ProductionUiCordonel.ProductionProcesses.Enum
{
public enum ProductionProcessState
{
Waiting,
Started,
Done,
Error
}
public static class ProductionProcessStateHelper
{
public static String GetTextFromProductionProcessState(ProductionProcessState sate)
{
switch (sate)
{
case ProductionProcessState.Waiting:
return "Warte";
case ProductionProcessState.Started:
return "Läuft";
case ProductionProcessState.Done:
return "Fertig";
default:
return "Fehler";
}
}
}
}
@@ -0,0 +1,82 @@
using System;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum
{
public enum SingleProcessState
{
/// <summary>
/// Single process is in processes list but idle.
/// </summary>
Idle,
/// <summary>
/// Single process is prepared and waits for execution,
/// this state should be set on adding process to processes list
/// </summary>
Waiting,
/// <summary>
/// Single process is in active running state
/// </summary>
Running,
/// <summary>
/// Single process has successfully finished
/// </summary>
Succeeded,
/// <summary>
/// Single process executed with warning
/// </summary>
Warning,
/// <summary>
/// Single process executed with error
/// </summary>
Error,
/// <summary>
/// Single process skipped due to processing step skip
/// </summary>
Skipped,
/// <summary>
/// Single process aborted after start due to preceding error or user request
/// </summary>
Abort,
/// <summary>
/// Single process retry after start due to preceding error manual set in error routine
/// </summary>
Retry
}
public static class ProductionProcessStateHelper
{
public static String GetTextFromProductionProcessState(SingleProcessState singleProcessState)
{
switch (singleProcessState)
{
case SingleProcessState.Retry:
return Resources.StrStateRetry;
case SingleProcessState.Idle:
return Resources.StrStateIdle;
case SingleProcessState.Waiting:
return Resources.StrProcessStateWaiting;
case SingleProcessState.Running:
return Resources.StrProcessStateRunning;
case SingleProcessState.Succeeded:
return Resources.StrProcessStateDone;
case SingleProcessState.Warning:
return Resources.StrProcessStateWarning;
case SingleProcessState.Abort:
return Resources.StrProcessSateAbort;
case SingleProcessState.Skipped:
return Resources.StrProcessStateSkipped;
default:
return Resources.StrProcessStateError;
}
}
}
}
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Threading;
using LaaPackages.Features.Cordonel.Models;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.CommonCore.Consts;
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;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public class CheckAttachments : BaseProductionProcess
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// Use the sleep 250 to create a 60 seconds timeout;
/// </summary>
private const Int32 TimeoutLoop_ms = 250;
private const Int32 Timeout_min = 10;
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_min * 60;
/// <summary>
/// Test finished can be good or abort by done button to exit this production process
/// </summary>
private Boolean _waitOnUserFeedback;
private DialogResult _result = DialogResult.Undef;
/// <summary>
/// Ctor
/// </summary>
public CheckAttachments(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcDialog
{
lblHeadLine = { Content = Resources.StrStateCheckAttachments },
tbxMessage = { Text = "" }
};
}
/// <summary>
/// Check for attachments needed to add to the Cordonel as last step before shipment
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Aug-27" author="Thomas Wiedebusch">
/// - Redesigned.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
_waitOnUserFeedback = true;
SignalRunningState(MaxActualProgressSteps);
// Uncheck executed process to leave the result open needed for retries
EolProgress.AttachmentsChecked = null;
StatusReturn retVal;
var ucDialog = (UcDialog)UserControl;
try
{
var a = LocalWebRequest.GetRequest(
$"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetSingleVakoKey?ProductionOrderNumber=" +
$"{Meter.OrderNumber}&KeyName=C_GEN_PULS");
var response = JsonConvert.DeserializeObject<VakoKeyValue>(a);
// Without response the order paper have to be checked for a text indicating a CPA needed to
// be attached or assembled
if (response == null)
{
ucDialog.SetMessage(Resources.StrMsgCheckOrderForPulseModule);
}
// The DB feeds back what has to be done
else if (response.Charcode.ToLower() == "q" || response.Charcode.ToLower() == "k")
{
ucDialog.SetMessage(Resources.StrMsgAssemblePulseModule);
}
else
{
ucDialog.SetMessage(Resources.StrMsgCheckOrderForPulseModule);
}
}
catch (Exception)
{
ucDialog.SetMessage(Resources.StrMsgCheckOrderForPulseModule);
}
// Initialize the ucDialog
var OptionList = new List<UcDialog.DialogOption>();
var done = new UcDialog.DialogOption(Resources.StrBtnDone, Resources.StrMsgDoneCheckAttachment,
() =>
{
_result = DialogResult.Done;
_waitOnUserFeedback = false;
}, isPositiveAcknBtn: true);
OptionList.Add(done);
var impossible = new UcDialog.DialogOption(Resources.StrBtnImpossible, Resources.StrMsgImpossibleCheckAttachment,
() =>
{
_result = DialogResult.Error;
_waitOnUserFeedback = false;
}, isPositiveAcknBtn: false);
OptionList.Add(impossible);
ucDialog.SetDialogOptions(OptionList);
// Wait on user input
var exitTime = DateTime.Now + TimeSpan.FromMinutes(Timeout_min);
var actualTime = DateTime.Now;
// wait on result or timeout
while (_waitOnUserFeedback && exitTime > actualTime && SingleProcessState != SingleProcessState.Idle)
{
Thread.Sleep(TimeoutLoop_ms);
actualTime = DateTime.Now;
ActualProcessProgress++;
}
// On timeout return failed step
if (_waitOnUserFeedback)
{
retVal = StatusReturn.Failed;
}
else
{
switch (_result)
{
case DialogResult.Done:
retVal = StatusReturn.Okay;
EolProgress.AttachmentsChecked = EOLStatus.OK;
break;
default:
retVal = StatusReturn.Failed;
EolProgress.AttachmentsChecked = EOLStatus.FAIL;
break;
}
}
return retVal;
}
}
}
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using LaaPackages.Features.Cordonel.Models;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
//using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
//using Xylem.Common.Logic.ProductionOrderCore;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public class CheckFinalParametrization : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public CheckFinalParametrization(String name) : base( name)
{
}
/// <summary>
/// Read and compare programming parameters for Cordonel which had been written before the reboot.
///
/// Precondition:
/// - The <see cref="ExecFinalParametrization"/> with trailing
/// <see cref="ExecRebootCordonel"/> processes have to be completed as this guarantees
/// that all parameters are written to non-volatile memory and survive a reset/reboot sequence
/// of the Cordonel.
///
/// Sequence:
/// - Generate time stamp for seconds since 01.Jan 2000 to set up the internal meter clock and IRDA.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Jan-23" author="Roland Drahbesh/Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
/// <remarks date="2025-Jan-30" author="Thomas Wiedebusch">
/// - Reset reboot counter.
/// </remarks>
/// <remarks date="2025-Mar-31" author="Thomas Wiedebusch">
/// - Reset sensus radio and periodic log reset counters.
/// </remarks>
/// <remarks date="2025-Apr-03" author="Thomas Wiedebusch">
/// - Moved setting of time and reset counters from here to <see cref="ExecRebootCordonel"/>.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
// Uncheck executed process to leave the result open needed for retries
EolProgress.ParametrizationCompareChecked = null;
// Check if anything has to be programmed
if (Meter == null || RegisterRestorer == null)
{
return StatusReturn.Failed;
}
// Use temporary registers to create a counter value for processing steps
var meterRegisters = new List<RecoveryRegisterItem>();
RegisterRestorer.BuildReadableRegisterDictionary(ref meterRegisters);
SignalRunningState(RegisterRestorer.RecoveryRegisterCount + meterRegisters.Count);
try
{
// Access to meter requires login
if (!Meter.IsLoggedOn)
{
Meter.Login();
if (!Meter.IsLoggedOn)
{
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
EolProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
// Execute final read after reboot
RegisterRestorer.OnProcessUpdate += ProcessUpdate_Handler;
var retValBol = RegisterRestorer.FinalReadRegisters(CancellationToken);
RegisterRestorer.OnProcessUpdate -= ProcessUpdate_Handler;
// Exit if meanwhile cancellation is required
if (CancellationToken.IsCancellationRequested)
return CancellationProcedure();
RegisterRestorer.OnProcessUpdate += ProcessUpdate_Handler;
retValBol &= RegisterRestorer.CompareRegisters(CancellationToken);
RegisterRestorer.OnProcessUpdate -= ProcessUpdate_Handler;
if (retValBol)
{
EolProgress.ParametrizationCompareChecked = EOLStatus.OK;
return StatusReturn.Okay;
}
EolProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgParameterCompare);
EolProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
}
}
@@ -0,0 +1,821 @@
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
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// Use the sleep 250 to create x seconds timeout;
/// </summary>
private const Int32 TimeoutLoop_ms = 250;
private const Int32 Timeout_s = 10;
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s;
/// <summary>
/// Remind the ID of the radio result as feedback from the DB.
/// </summary>
private Int64 _radioResultCheckId;
/// <summary>
/// Final state of radio system.
/// </summary>
private Int32 _finalRadioSystemState;
/// <summary>
/// Reminder for specific user control
/// </summary>
private UcRadioCheck _checkRadioUserControl;
/// <summary>
/// Signal level successfully tested
/// </summary>
private Boolean _successSignalLevel;
/// <summary>
/// Radio encryption key converted to string
/// </summary>
private String _radioEncryptionKey;
/// <summary>
/// Test finished can be good or abort by done button to exit this production process
/// </summary>
private Boolean _testFinished;
/// <summary>
/// SIRT communication okay
/// </summary>
private Boolean _sirtComOkay;
private DataContainer _dataContainer;
private Statemashine _stateMachine;
/// <summary>
/// Minimum PAM signal level of SIRT got from DB
/// </summary>
private Byte? _rssiPamSirtDbMin;
/// <summary>
/// Maximum PAM signal level of SIRT got from DB
/// </summary>
private Byte? _rssiPamSirtDbMax;
/// <summary>
/// Minimum SEMI signal level of Cordonel response got from DB
/// </summary>
private Byte? _rssiSemiCordonelDbMin;
/// <summary>
/// Maximum SEMI signal level of Cordonel response got from DB
/// </summary>
private Byte? _rssiSemiCordonelDbMax;
/// <summary>
/// Measured from SIRT the signal strength of the Cordonel SEMI answer
/// </summary>
private Byte? _rssiSemiCordonelMeasuredBySirt;
/// <summary>
/// Measured from Cordonel the signal strength of the SIRT PAM received
/// </summary>
private Byte? _rssiPamSirtMeasuredByCordonel;
/// <summary>
/// Number of the SIRT box as these are evaluated for their signal strength
/// </summary>
private Byte _sirtBoxNr;
/// <summary>
/// Comport number of SIRT
/// </summary>
private Byte _sirtComport;
/// <summary>
/// Radio address Cordonel
/// </summary>
private UInt32 _radioAddressCordonel;
/// <summary>
/// Ctor
/// </summary>
public CheckRadio(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcRadioCheck();
_checkRadioUserControl = (UcRadioCheck)UserControl;
_checkRadioUserControl.OnResetRequested += delegate
{
Retry();
};
//_checkRadioUserControl.OnDoneRequested += delegate
//{
// SaveRssiLevelsToDb();
//};
}
/// <summary>
/// Check of radio signal strength, skips process on special permission or if NA region
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-22" author="Thomas Wiedebusch/Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-27" author="Thomas Wiedebusch">
/// - Skip process on special permission or if NA region,
/// - Remove userControl from main screen if skipped.
/// </remarks>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Fire SingleProcessState event with argument.
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Assignment of stateMachine and dataContainer.
/// </remarks>
/// <remarks date="2024-Jul-20..22" author="Thomas Wiedebusch">
/// - Get encryption key moved to process <see cref="ExecFinalParametrization"/> to speed up
/// this process as this process needs to be run exclusively.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Logout at finalization,
/// - Log all write accesses.
/// </remarks>
/// <remarks date="2025-Jan-21" author="Thomas Wiedebusch">
/// - SkipRadioCheck not nullable.
/// </remarks>
/// <remarks date="2025-Feb-11" author="Thomas Wiedebusch">
/// - SetTestInit as bool.
/// </remarks>
/// <remarks date="2025-Feb-20" author="Thomas Wiedebusch">
/// - Separate TestInit and SirtInit (only if test shouldn't be skipped).
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
/// <remarks date="2025-Mai-07" author="Thomas Wiedebusch">
/// - Removed change and read back of MainAlarmMask and ExtendedAlarmMask.
/// </remarks>
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<UInt32>(
Meter.ReadRegister("SENSUSRADIO_FrequencyIndicator"));
_radioAddressCordonel =
RegisterConverter.ByteArrayToValue<UInt32>(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
// set system state back to shipping mode
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
ActualProcessProgress++;
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_SystemState"));
ActualProcessProgress++;
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)))
{
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);
}
}
/// <summary>
/// Common for test Init
/// </summary>
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;
}
/// <summary>
/// Initialize the SIRT
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Common to prepare exit for all status returns
/// </summary>
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;
}
/// <summary>
/// Common for test failed
/// </summary>
/// <param name="errorMsg">error message to dispatch and log</param>
/// <remarks><see cref="StatusReturn.Failed"/></remarks>
private StatusReturn SetTestFailed(String errorMsg)
{
EolProgress.FinalRadioSystemStateCheck = EOLStatus.FAIL;
ErrorMsgDispatcher(errorMsg);
PrepareExit();
return StatusReturn.Failed;
}
/// <summary>
/// Common for test succeeded
/// </summary>
/// <remarks><see cref="StatusReturn.Okay"/></remarks>
private StatusReturn SetTestSucceeded()
{
EolProgress.FinalRadioSystemStateCheck = EOLStatus.OK;
PrepareExit();
return StatusReturn.Okay;
}
/// <summary>
/// Disable all buttons of user control.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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));
}
/// <summary>
/// Check of radio signal strength.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
/// <summary>
/// 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!
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
private void ClearRssiMeasurement()
{
_sirtComOkay = false;
_rssiPamSirtMeasuredByCordonel = null;
_rssiSemiCordonelMeasuredBySirt = null;
}
/// <summary>
/// Retry collection of RSSI values (entire process with sending PAM and receiving SEMI).
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
/// <summary>
/// ATTENTION:
/// As those definitions are directly used as DB definitions for pushing the result:
///
/// LEAVE THOSE NAMING AS DEFINED HERE!
///
/// </summary>
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;
}
}
/// <summary>
/// Save RSSI values to DB.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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;
}
}
/// <summary>
/// Get RSSI values from DB.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
/// <summary>
/// Send PAM to force a BUP and a SEMI and start the RSSI observer to measure the signal strength.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
/// - Optimized based on new documentations.
/// </remarks>
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++;
}
/// <summary>
/// Get RSSI (Received Signal Strength Indicator) values from SIRT (Sensus Interface Radio Tool).
/// Set the <see cref="_successSignalLevel"/>
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-09-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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();
}
}
/// <summary>
/// State machine state change handler.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
}
}
@@ -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
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// Use the sleep 250 to create x seconds timeout;
/// </summary>
private const Int32 TimeoutLoop_ms = 250;
private const Int32 Timeout_s = 10;
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s;
/// <summary>
/// Remind the ID of the radio result as feedback from the DB.
/// </summary>
private Int64 _radioResultCheckId;
/// <summary>
/// Final state of radio system.
/// </summary>
private Int32 _finalRadioSystemState;
/// <summary>
/// Reminder for specific user control
/// </summary>
private UcRadioCheck _checkRadioUserControl;
/// <summary>
/// Signal level successfully tested
/// </summary>
private Boolean _successSignalLevel;
/// <summary>
/// Radio encryption key converted to string
/// </summary>
private String _radioEncryptionKey;
/// <summary>
/// Test finished can be good or abort by done button to exit this production process
/// </summary>
private Boolean _testFinished;
/// <summary>
/// SIRT communication okay
/// </summary>
private Boolean _sirtComOkay;
private DataContainer _dataContainer;
private Statemashine _stateMachine;
/// <summary>
/// Minimum PAM signal level of SIRT got from DB
/// </summary>
private Byte? _rssiPamSirtDbMin;
/// <summary>
/// Maximum PAM signal level of SIRT got from DB
/// </summary>
private Byte? _rssiPamSirtDbMax;
/// <summary>
/// Minimum SEMI signal level of Cordonel response got from DB
/// </summary>
private Byte? _rssiSemiCordonelDbMin;
/// <summary>
/// Maximum SEMI signal level of Cordonel response got from DB
/// </summary>
private Byte? _rssiSemiCordonelDbMax;
/// <summary>
/// Measured from SIRT the signal strength of the Cordonel SEMI answer
/// </summary>
private Byte? _rssiSemiCordonelMeasuredBySirt;
/// <summary>
/// Measured from Cordonel the signal strength of the SIRT PAM received
/// </summary>
private Byte? _rssiPamSirtMeasuredByCordonel;
/// <summary>
/// Number of the SIRT box as these are evaluated for their signal strength
/// </summary>
private Byte _sirtBoxNr;
/// <summary>
/// Comport number of SIRT
/// </summary>
private Byte _sirtComport;
/// <summary>
/// Radio address Cordonel
/// </summary>
private UInt32 _radioAddressCordonel;
/// <summary>
/// Ctor
/// </summary>
public CheckRadio(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcRadioCheck();
_checkRadioUserControl = (UcRadioCheck)UserControl;
_checkRadioUserControl.OnResetRequested += delegate
{
Retry();
};
//_checkRadioUserControl.OnDoneRequested += delegate
//{
// SaveRssiLevelsToDb();
//};
}
/// <summary>
/// Check of radio signal strength, skips process on special permission or if NA region
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-22" author="Thomas Wiedebusch/Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-27" author="Thomas Wiedebusch">
/// - Skip process on special permission or if NA region,
/// - Remove userControl from main screen if skipped.
/// </remarks>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Fire SingleProcessState event with argument.
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Assignment of stateMachine and dataContainer.
/// </remarks>
/// <remarks date="2024-Jul-20..22" author="Thomas Wiedebusch">
/// - Get encryption key moved to process <see cref="ExecFinalParametrization"/> to speed up
/// this process as this process needs to be run exclusively.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Logout at finalization,
/// - Log all write accesses.
/// </remarks>
/// <remarks date="2025-Jan-21" author="Thomas Wiedebusch">
/// - SkipRadioCheck not nullable.
/// </remarks>
/// <remarks date="2025-Feb-11" author="Thomas Wiedebusch">
/// - SetTestInit as bool.
/// </remarks>
/// <remarks date="2025-Feb-20" author="Thomas Wiedebusch">
/// - Separate TestInit and SirtInit (only if test shouldn't be skipped).
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
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<UInt32>(
Meter.ReadRegister("SENSUSRADIO_FrequencyIndicator"));
_radioAddressCordonel =
RegisterConverter.ByteArrayToValue<UInt32>(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<Byte>(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask"));
var extendedAlarmMask =
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask"));
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(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);
}
}
/// <summary>
/// Common for test Init
/// </summary>
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;
}
/// <summary>
/// Initialize the SIRT
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Common to prepare exit for all status returns
/// </summary>
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;
}
/// <summary>
/// Common for test failed
/// </summary>
/// <param name="errorMsg">error message to dispatch and log</param>
/// <remarks><see cref="StatusReturn.Failed"/></remarks>
private StatusReturn SetTestFailed(String errorMsg)
{
EolProgress.FinalRadioSystemStateCheck = EOLStatus.FAIL;
ErrorMsgDispatcher(errorMsg);
PrepareExit();
return StatusReturn.Failed;
}
/// <summary>
/// Common for test succeeded
/// </summary>
/// <remarks><see cref="StatusReturn.Okay"/></remarks>
private StatusReturn SetTestSucceeded()
{
EolProgress.FinalRadioSystemStateCheck = EOLStatus.OK;
PrepareExit();
return StatusReturn.Okay;
}
/// <summary>
/// Disable all buttons of user control.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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));
}
/// <summary>
/// Check of radio signal strength.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
/// <summary>
/// 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!
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
private void ClearRssiMeasurement()
{
_sirtComOkay = false;
_rssiPamSirtMeasuredByCordonel = null;
_rssiSemiCordonelMeasuredBySirt = null;
}
/// <summary>
/// Retry collection of RSSI values (entire process with sending PAM and receiving SEMI).
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
/// <summary>
/// ATTENTION:
/// As those definitions are directly used as DB definitions for pushing the result:
///
/// LEAVE THOSE NAMING AS DEFINED HERE!
///
/// </summary>
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;
}
}
/// <summary>
/// Save RSSI values to DB.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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;
}
}
/// <summary>
/// Get RSSI values from DB.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
/// <summary>
/// Send PAM to force a BUP and a SEMI and start the RSSI observer to measure the signal strength.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
/// - Optimized based on new documentations.
/// </remarks>
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++;
}
/// <summary>
/// Get RSSI (Received Signal Strength Indicator) values from SIRT (Sensus Interface Radio Tool).
/// Set the <see cref="_successSignalLevel"/>
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-09-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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();
}
}
/// <summary>
/// State machine state change handler.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
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);
}
}
}
}
@@ -0,0 +1,304 @@
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
{
/// <summary>
/// Concatenation for all production process states:
/// - Production process class (representing a production process step), null if hidden process,
/// - attached state,
/// - state information to display a text message,
/// - production process number to define where to display in the overview,
/// - next state which should be executed after this succeeded,
/// [optional]:
/// - own error state,
/// - own break state,
/// - kick off state for parallel execution after starting this process in a separate task,
/// - wait for single process to finish successfully,
/// - wait for all processes to finish successfully,
/// - start always independent of any result, e.g. the error state needs to be started if called!
/// </summary>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public class EolProcessStateDef : BaseProcessStatesDef
{
/// <summary>
/// State-machine definitions of production-processes which feeds the state-machine.
///
///
/// INSTRUCTION FOR PREPARATION:
/// -----------------------------
/// STEP 1: Define some states for your processes <see cref="ProcessState"/>
/// STEP 2: Implement your processes like <see cref="DetectCordonel"/>
/// STEP 3: Register and chain your states here
///
///
/// INSTRUCTION FOR NORMAL USAGE:
/// -----------------------------
/// Means one state automatically switched on successfully execution to the next required state.
///
/// Define the (e.g.):
/// productionProcess: new DetectCordonel(Resources.StrStateDetectCordonel),
/// processState: ProcessState.DetectCordonel,
/// processInfo: Resources.StrStateDetectCordonel,
/// productionProcessNo: 0,
/// nextStateOnSuccess: ProcessState.CheckNewMeter),
///
/// 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).
///
/// Define the (e.g.):
///
/// 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 after starting the "ConnectCordonel() task"
/// kickOffParallelState: ProcessState.GetSerialNumberAndRadioAddress),
///
///
/// INSTRUCTION TO DELAY THE START OF A PROCESS AND WAIT ON RESULTS OF OTHER PROCESS(ES):
/// -------------------------------------------------------------------------------------
/// 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 state of the process needed to wait for (e.g.):
///
/// 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 after starting
/// kickOffParallelState: ProcessState.ProcessPasswordFile,
/// // Wait until this process has been successfully completed
/// waitForSingleProcess: ProcessState.ConnectCordonel),
///
/// Wait for all preceding processes to complete to be sure to get all data and all processed are succeeded:
/// Set the "waitForAllProcesses" to true (e.g.):
/// productionProcess: new ExecPasswordFile(Resources.StrStatePasswordFile),
/// processState: ProcessState.ProcessPasswordFile,
/// processInfo: Resources.StrStatePasswordFile,
/// productionProcessNo: 5,
/// nextStateOnSuccess: ProcessState.ExecFinalParametrization,
/// waitForAllProcesses: true),
///
/// </summary>
public EolProcessStateDef()
{
var stateContainer = new List<ProcessStateStruct>
{
#region ----------------------------------- GENERIC-STATES---------------------------------------------
// 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),
// 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,
// Repeat detect as a new meter has been connected
nextStateOnSuccess: ProcessState.DetectCordonel,
// Used to go ahead as new meter returns "false" meaning error state
errorExitState: 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!
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),
// 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),
// This scans the order number, it has to be executed exclusively with automatic
new ProcessStateStruct(
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(
productionProcess: new CheckSerialNumber( Resources.StrStateCheckSerialNumber),
processState: ProcessState.CheckSerialNumber,
processInfo: Resources.StrStateCheckSerialNumber,
productionProcessNo: 12,
nextStateOnSuccess: ProcessState.CheckAttachments),
#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),
// 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),
// 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),
// 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),
// 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),
// 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),
// 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(
productionProcess: new CheckFinalParametrization(Resources.StrStateCheckParametrization),
processState: ProcessState.CheckFinalParametrization,
processInfo: Resources.StrStateCheckParametrization,
productionProcessNo: 9,
nextStateOnSuccess: 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
new ProcessStateStruct(
productionProcess: new CheckRadio(Resources.StrStateRadioCheck),
processState: ProcessState.CheckRadio,
processInfo: Resources.StrStateRadioCheck,
productionProcessNo: 10,
nextStateOnSuccess: ProcessState.CheckOrderNumber),
// Precondition: - All checks have to be executed with positive result
new ProcessStateStruct(
productionProcess: new CheckAttachments(Resources.StrStateCheckAttachments),
processState: ProcessState.CheckAttachments,
processInfo: Resources.StrStateCheckAttachments,
productionProcessNo: 13,
nextStateOnSuccess: ProcessState.ExecVisualInspection),
// Precondition: - All checks have to be executed with positive result
new ProcessStateStruct(
productionProcess: new VisualInspection(Resources.StrStateVisualInspection),
processState: ProcessState.ExecVisualInspection,
processInfo: Resources.StrStateVisualInspection,
productionProcessNo: 14,
nextStateOnSuccess: ProcessState.PrepareShippingMode),
// Precondition: - All checks have to be executed with positive result
new ProcessStateStruct(
productionProcess: new PrepareShipping(Resources.StrStatePrepareShipping),
processState: ProcessState.PrepareShippingMode,
processInfo: Resources.StrStatePrepareShipping,
productionProcessNo: 15,
nextStateOnSuccess: ProcessState.ReadyForShipping),
new ProcessStateStruct(
productionProcess: null,
processState: ProcessState.ReadyForShipping,
processInfo: Resources.StrStateReadyForShipping,
productionProcessNo: null,
nextStateOnSuccess: ProcessState.Idle),
#endregion -------------------------------- EOL-STATES ------------------------------------------------
};
StateContainer.AddRange(stateContainer);
}
}
}
@@ -0,0 +1,117 @@
using System;
using System.Linq;
using LaaPackages.Features.Cordonel.Models;
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
{
/// <summary>
/// Write all configurations to Cordonel previously collected in <see cref="GetFinalParametrization"/>
/// </summary>
public class ExecFinalParametrization : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public ExecFinalParametrization(String name) : base(name)
{
}
/// <summary>
/// Write programming parameters for Cordonel including VAKO and CSD and some special pre- and post-
/// programmings.
///
/// Precondition:
/// - The <see cref="GetFinalParametrization"/> process has to be completed
/// as this collects all parameters from DB.
///
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Jun-29" author="Roland Drahbesh/Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/>
/// as these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2023-Jul-20" author="Thomas Wiedebusch">
/// - Moved timer setup to process after reboot <see cref="CheckFinalParametrization"/>.
/// </remarks>
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
/// - Changed from programming parameters to register restorer
/// </remarks>
/// <remarks date="2024-Jan-17..22" author="Thomas Wiedebusch">
/// - Moved database access and handling to register restorer as common interface for parameter handling,
/// - Read all registers BEFORE EOL parameter programming as a restorable register list has to be generated
/// being able to compare the contents.
/// </remarks>
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
/// </remarks>
/// <remarks date="2024-Feb-20" author="Thomas Wiedebusch">
/// - Removed initial register read as this gives not any useful information.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
// Uncheck executed process to leave the result open needed for retries
EolProgress.ParametrizationDoneChecked = null;
// check if anything has to be programmed
if (Meter == null || RegisterRestorer?.RecoveryRegisters == null)
{
return StatusReturn.Failed;
}
// Check if radio has been set up properly and programming parameters are present
if ((!Meter.RadioFrequencyMhz.HasValue && RegisterRestorer.RecoveryRegisters.Any(a =>
a.RegisterIdent.Contains("RADIO"))) ||
(Meter.RadioFrequencyMhz.HasValue && !RegisterRestorer.RecoveryRegisters.Any(a =>
a.RegisterIdent.Contains("RADIO"))))
{
ErrorMsgDispatcher(Resources.StrErrorMsgRadioInconsistent);
EolProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
// Set the process bar limits including approximately 10 additional steps for preparation and finalization
SignalRunningState(RegisterRestorer.RecoveryRegisterCount + 10);
try
{
// access to meter requires login
if (!Meter.IsLoggedOn)
{
Meter.Login();
if (!Meter.IsLoggedOn)
{
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
EolProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
RegisterRestorer.OnProcessUpdate += ProcessUpdate_Handler;
var retVal = RegisterRestorer.EolProgrammingRegisters(RegisterRestorer.RecoveryRegisters,
CancellationToken);
RegisterRestorer.OnProcessUpdate -= ProcessUpdate_Handler;
EolProgress.ParametrizationDoneChecked = retVal == StatusReturn.Okay ? EOLStatus.OK : EOLStatus.FAIL;
return retVal;
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgParameterWrite);
EolProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
}
}
@@ -0,0 +1,294 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using LaaPackages.Features.Cordonel.Models;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
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;
//using Xylem.Common.Ui.ProductionUiCordonel.UserControls.FinalTest;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public class ExecPasswordFile : BaseProductionProcess
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// </summary>
private const Int32 MaxActualProgressSteps = 6;
/// <summary>
/// That is the password file which needs to be installed to the Cordonel.
/// </summary>
private MeterPwdFile _meterPwdFile;
/// <summary>
/// Ctor
/// </summary>
public ExecPasswordFile(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcPasswordFile();
}
/// <summary>
/// Password generation and password setting in production database:
/// - Request a password list from LuDB and fill the PwdContainer, if the password file does not exist,
/// it will be generated including the hashed contents,
/// - If the password file already exists, verified by the production DB entry for the password equals the
/// level 8 password, try to log in with password level 8 to validate the file installation,
/// - If password file is new, change password filed in production DB from skeleton key to password level 8.
/// - Check password level 3 has been installed correctly using the EXIT state
/// Precondition: - The detection process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Mai-15" author="Roland Drahbesh">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Mar-13" author="Thomas Wiedebusch">
/// - Adapted to new production process structure.
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/>
/// as these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Speed up to quick return if passwords are already set.
///
/// ATTENTION: This requires to remove those on new PcbId <see cref="ProcessController"/>
/// for CheckMeterChange!
///
/// </remarks>
/// <remarks date="2023-Dec-04" author="Thomas Wiedebusch">
/// - Used MeterPwdFile as base for password file generation. This includes check routines for the correct
/// password file assembly and content.
/// </remarks>
/// <remarks date="2024-Jan-22" author="Thomas Wiedebusch">
/// - Used PwdContainer property.
/// </remarks>
/// <remarks date="2024-Aug-27" author="Thomas Wiedebusch">
/// - Check password level 3.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Retries for installation of password file.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
// Uncheck executed process to leave the result open needed for retries
EolProgress.PasswordFileInstalledCheck = null;
if (Meter == null || PwdContainer == null)
{
return StatusReturn.Failed;
}
// Start with statistics on GUI and internal process update
SignalRunningState(MaxActualProgressSteps);
// Read the passwords from DB including the actual password used for production and the skeleton-key.
// These passwords are not encrypted (hashed)!
var ucPwd = (UcPasswordFile)UserControl;
ucPwd.UpdateImage(ucPwd.imgRequestPassword, ImgState.Active);
Boolean retValBol;
// First check if the Password container contains a list of passwords as this is a retry of the production
// process and the password needn't be required again from the LuDB.
if (PwdContainer?.ListOfPasswords?.Count > 0)
{
// If the password file is installed the "Password" equals the list of passwords at "login level 8".
// The "Password" is the current login password for production. Initially the "Password" is the
// "Skeleton-key" which can not be part of the list of passwords.
if (Encoding.UTF8.GetString(PwdContainer.ListOfPasswords.Last()) == PwdContainer.Password)
{
LogNewStatusMsg(Resources.StrPasswordFileExists);
// Check if the password file is installed in Cordonel with login password level 8
if (Meter.IsLoggedOn)
Meter.Logout();
retValBol = Meter.Login(Encoding.UTF8.GetString(PwdContainer.ListOfPasswords.Last()));
if (retValBol)
{
// password has been validated as Level 8 Password, all following steps can be skipped
EolProgress.PasswordFileInstalledCheck = EOLStatus.OK;
return StatusReturn.Okay;
}
}
}
try
{
retValBol = MeterPwdHandlerDb.RequestPwdFileFromDb(Meter.PcbId, out var pwdContainer);
ActualProcessProgress++;
// Exit if meanwhile cancellation is required
if (CancellationToken.IsCancellationRequested)
return CancellationProcedure();
if (!retValBol)
{
ucPwd.UpdateImage(ucPwd.imgRequestPassword, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordFileGenerationFailed);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
ucPwd.UpdateImage(ucPwd.imgRequestPassword, ImgState.Success);
if (PwdContainer?.ListOfHashes == null || PwdContainer.ListOfPasswords == null)
{
ucPwd.UpdateImage(ucPwd.imgCreateFile, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordFileGenerationFailed);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
ucPwd.UpdateImage(ucPwd.imgCreateFile, ImgState.Active);
PwdContainer.OrderNumber = pwdContainer.OrderNumber;
PwdContainer.RadioAddress = pwdContainer.RadioAddress;
PwdContainer.Skeleton = pwdContainer.Skeleton;
PwdContainer.Password = pwdContainer.Password;
PwdContainer.PasswordFileIdent = pwdContainer.PasswordFileIdent;
PwdContainer.KeySetId = pwdContainer.KeySetId;
PwdContainer.ListOfHashes.AddRange(pwdContainer.ListOfHashes);
PwdContainer.ListOfPasswords.AddRange(pwdContainer.ListOfPasswords);
// Build password file
_meterPwdFile = new MeterPwdFile(Meter);
retValBol = _meterPwdFile.BuildPwdFile(PwdContainer.ListOfPasswords, 1, PwdContainer.Skeleton);
if (!retValBol)
{
ucPwd.UpdateImage(ucPwd.imgCreateFile, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordFileGenerationFailed);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
// Write the password file to the Cordonel, read it back and make a binary verification
ucPwd.UpdateImage(ucPwd.imgCreateFile, ImgState.Success);
ucPwd.UpdateImage(ucPwd.imgWriteToMeter, ImgState.Active);
if (!Meter.IsLoggedOn)
Meter.Login();
// Install and check password file
var retryCtr = 2;
do
{
retValBol = _meterPwdFile.UnlockEraseWriteMeterPwdFile();
retValBol &= _meterPwdFile.WriteMeterPwdFile() && _meterPwdFile.VerifyMeterPwdFile();
} while (retryCtr-- > 0 && !retValBol);
ActualProcessProgress++;
if (!retValBol)
{
ucPwd.UpdateImage(ucPwd.imgWriteToMeter, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgWriteVerifyCordonelPasswordFile);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
// Check if new password is in the meter with login level 8... even if the file write wasn't successful
Meter.Logout();
retValBol = Meter.Login(Encoding.UTF8.GetString(PwdContainer.ListOfPasswords.Last()));
// Password in Production DB can be updated (overwrite Skeleton-key in Password column with
// password level 8 from LuDB) because the login with installed password file succeeded,
// this is the indicator, that the password file is valid.
if (retValBol)
{
ucPwd.UpdateImage(ucPwd.imgWriteToMeter, ImgState.Success);
ucPwd.UpdateImage(ucPwd.imgUpdatePassword, ImgState.Active);
retValBol = MeterPwdHandlerDb.SetPwdFromSkeletonToLvl8inDd(Meter.PcbId, PwdContainer);
ActualProcessProgress++;
if (!retValBol)
{
ucPwd.UpdateImage(ucPwd.imgUpdatePassword, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordDbOverwrite);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
ActualProcessProgress++;
// Check password level 3 as all applications need to use this
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SYSTEM_ExitReason",
new Byte[] { 21 }, ProgrammingSource.ProgramInternal));
// Meter.WriteRegister("SYSTEM_ExitReason", 21);
var exitResult = RegisterConverter.ByteArrayToValue<Int16>(Meter.ReadRegister("SYSTEM_ExitReason"));
if (exitResult != 0)
{
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordLevel3Wrong);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
ucPwd.UpdateImage(ucPwd.imgUpdatePassword, ImgState.Success);
ucPwd.UpdateImage(ucPwd.imgLogout, ImgState.Active);
ActualProcessProgress++;
Meter.Logout();
// Clear intermediate stored password to force password collection from DB on simple login without
// given new password. Then the password will be taken from the DB or offline file.
Meter.ClearPassword();
ucPwd.UpdateImage(ucPwd.imgLogout, ImgState.Success);
ucPwd.UpdateImage(ucPwd.imgCheckNewPassword, ImgState.Active);
ActualProcessProgress++;
// If the password is not set (Meter.ClearPassword) the actual valid production password will be
// requested from the DB in the GenesisMeter Login() without parameter.
Thread.Sleep(1000);
retValBol = Meter.Login();
if (!retValBol)
{
ucPwd.UpdateImage(ucPwd.imgCheckNewPassword, ImgState.Error);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordDbOverwrite);
}
else
{
ucPwd.UpdateImage(ucPwd.imgCheckNewPassword, ImgState.Success);
}
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgPasswordFileGenerationFailed);
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
if (retValBol)
{
EolProgress.PasswordFileInstalledCheck = EOLStatus.OK;
return StatusReturn.Okay;
}
EolProgress.PasswordFileInstalledCheck = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,146 @@
using System;
using LaaPackages.Features.Cordonel.Models;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
using Xylem.Common.Logic.ProductionOrderCore;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public sealed class ExecRebootCordonel : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public ExecRebootCordonel(String name) : base(name)
{
}
/// <summary>
/// Reboot the Cordonel being able to check later if all settings.
/// Precondition:
/// - The <see cref="ExecStoreConfiguration"/> process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jul-05" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2024-Mar-06" author="Thomas Wiedebusch">
/// - Moved StoreConfiguration to separate production process to get logging information.
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
/// <remarks date="2025-Apr-03" author="Thomas Wiedebusch">
/// - Moved setting of time and reset counters from <see cref="CheckFinalParametrization"/> to here.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
// Uncheck executed process to leave the result open needed for retries
EolProgress.RebootDoneChecked = null;
if (Meter == null)
{
return StatusReturn.Failed;
}
// assume a counter value just a magic number
SignalRunningState(30);
try
{
// access to meter requires login
if (!Meter.IsLoggedOn)
{
Meter.Login();
if (!Meter.IsLoggedOn)
{
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
EolProgress.RebootDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
// reboot the meter and avoid doubled storage of configurations
var _meterResetPsu = new MeterResetPsu(Meter)
{
StoreConfigEnable = false
};
_meterResetPsu.OnProcessUpdate += ProcessUpdate_Handler;
var rebootSucceeded = _meterResetPsu.MeterResetPsuExecute(CancellationToken);
_meterResetPsu.OnProcessUpdate -= ProcessUpdate_Handler;
if (!rebootSucceeded)
{
EolProgress.RebootDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
// access to meter requires login
if (!Meter.IsLoggedOn)
{
Meter.Login();
if (!Meter.IsLoggedOn)
{
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
EolProgress.RebootDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
// Get the time in UTC to program the time for the Cordonel in seconds since 01. Jan 2000
// to set the new UTC after reboot
var teaTime = new TimeT();
// Build byte array
var rawSeconds = RegisterConverter.ValueToByteArray(teaTime.UtcNowToSecondsSince2000);
var timerSetup = new ProgrammingParameters("SYSTEM_CalendarSeconds", rawSeconds,
ProgrammingSource.ProgramInternal);
// This is the manufacturing date of the Cordonel
var irdaMfgDate = new ProgrammingParameters("IRDA_MfgDate", rawSeconds,
ProgrammingSource.ProgramInternal);
// Reset reboot counter
var nullRebootCtr = new ProgrammingParameters("CUSTOMER_RebootCount", new Byte[] { 0x00 },
ProgrammingSource.ProgramInternal);
var retValBol = WriteRegisterLogAndProcessCtr(timerSetup);
retValBol &= WriteRegisterLogAndProcessCtr(irdaMfgDate);
retValBol &= WriteRegisterLogAndProcessCtr(nullRebootCtr);
if (Meter.RadioFrequencyMhz != null)
{
// Reset periodic log reset counter
var periodicLogResetCtr = new ProgrammingParameters("PERIODICLOG_ResetCounter", new Byte[] { 0x00 },
ProgrammingSource.ProgramInternal);
retValBol &= WriteRegisterLogAndProcessCtr(periodicLogResetCtr);
// Reset sensus radio reset counter
var sensusRadioResetCtr = new ProgrammingParameters("SENSUSRADIO_ResetCounter", new Byte[] { 0x00 },
ProgrammingSource.ProgramInternal);
retValBol &= WriteRegisterLogAndProcessCtr(sensusRadioResetCtr);
}
if (!retValBol)
{
EolProgress.RebootDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
EolProgress.RebootDoneChecked = EOLStatus.OK;
return StatusReturn.Okay;
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgReboot);
EolProgress.RebootDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
}
}
@@ -0,0 +1,80 @@
using System;
using LaaPackages.Features.Cordonel.Models;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public sealed class ExecStoreConfiguration : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public ExecStoreConfiguration(String name) : base(name)
{
}
/// <summary>
/// Store all configurations to safe the data in non-volatile memory.
/// Precondition:
/// - The <see cref="ExecFinalParametrization"/> process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Mar-06" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
/// - EOL progress signaled.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
// Uncheck executed process to leave the result open needed for retries
EolProgress.StoreConfigurationDoneChecked = null;
if (Meter == null)
{
return StatusReturn.Failed;
}
// assume a counter value just a magic number
SignalRunningState(2);
try
{
// access to meter requires login
if (!Meter.IsLoggedOn)
{
Meter.Login();
if (!Meter.IsLoggedOn)
{
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
EolProgress.StoreConfigurationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
// exclusively store configurations and signal status
if (Meter.StoreAllConfigurations())
{
SuccessMsgDispatcher(Resources.StrSuccessMsgStoreConfiguration);
EolProgress.StoreConfigurationDoneChecked = EOLStatus.OK;
}
else
{
ErrorMsgDispatcher(Resources.StrErrorMsgStoreConfiguration);
EolProgress.StoreConfigurationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
return StatusReturn.Okay;
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgStoreConfiguration);
EolProgress.StoreConfigurationDoneChecked = EOLStatus.FAIL;
return StatusReturn.Failed;
}
}
}
}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using Xylem.Common.CommonCore.Configuration;
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.GenericProcesses;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
/// <summary>
/// Request for parameters for final programming including VAKO and CSD
/// </summary>
public class GetFinalParametrization : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public GetFinalParametrization(String name) : base(name)
{
}
/// <summary>
/// Get programming parameters for Cordonel including VAKO and CSD
/// Precondition:
/// - The <see cref="DetectCordonel"/> process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Mai-15" author="Roland Drahbesh">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Apr-18" author="Thomas Wiedebusch">
/// - Adapted to new production process.
/// </remarks>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Speed up to quick return if programming parameters are already set.
///
/// ATTENTION: This requires to remove those on new PcbId <see cref="ProcessController"/>
/// for CheckMeterChange!
/// </remarks>
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
/// - Logging of received programming parameters.
/// </remarks>
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
/// - Changed from programming parameters to register restorer
/// </remarks>
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
/// - Moved database access and handling to register restorer as common interface for parameter handling.
/// </remarks>
/// <remarks date="2024-Jul-22" author="Thomas Wiedebusch">
/// - Get encryption key moved from process <see cref="CheckRadio"/> to speed up
/// that process as this process needs to be run exclusively.
/// </remarks>
/// <remarks date="2025-Jan-21" author="Thomas Wiedebusch">
/// - Output error message from register restorer..
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
SignalRunningState();
if (Meter == null || RegisterRestorer == null || RadioEncryptionKey == null)
return StatusReturn.Failed;
// If programming parameters had been collected in the last call for this pcbId then skip this step as those
// will not have changed between subsequent calls
if (RegisterRestorer.RecoveryRegisters?.Count > 0 && RadioEncryptionKey.Count > 0)
return StatusReturn.Okay;
// skip request to radio encryption key if meter doesn't report a frequency
if (Meter.RadioFrequencyMhz != null)
{
// Get the encryption key, this can be accessed soon the order has been generated as this also creates
// encryption key for all EMEA Cordonels
var retVal = false;
try
{
var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetEncryptionKey?PcbID={Meter.PcbId}";
var stringResponse = LocalWebRequest.GetRequest(url, 30000, out var httpStatus);
if (httpStatus == HttpStatusCode.OK && !string.IsNullOrEmpty(stringResponse))
{
retVal = true;
RadioEncryptionKey.Clear();
// remove all special characters from string
var strResult = Regex.Replace(stringResponse, "[^0-9,A-F,a-f]", "");
RadioEncryptionKey.Add(strResult);
}
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgRadioKey);
return StatusReturn.Failed;
}
if (!retVal)
return StatusReturn.Failed;
}
// Exit if meanwhile cancellation is required
if (CancellationToken.IsCancellationRequested)
return CancellationProcedure();
RegisterRestorer.RecoveryRegisters = new List<RecoveryRegisterItem>();
if (StatusReturn.Okay != RegisterRestorer.DownloadProgrammingParametersFromDb(Meter.PcbId,
RegisterRestorer.RecoveryRegisters, out var errorMsg))
{
Meter?.WriteLog(errorMsg);
ErrorMsgDispatcher(errorMsg);
return StatusReturn.Failed;
}
return StatusReturn.Okay;
}
}
}
@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
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;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public class PrepareShipping : BaseProductionProcess
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// Use the sleep 250 to create x seconds timeout;
/// </summary>
private const Int32 TimeoutLoop_ms = 250;
private const Int32 Timeout_s = 30;
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s + 5;
/// <summary>
/// Test finished can be good or abort by done button to exit this production process
/// </summary>
private Boolean _waitOnUserFeedback;
/// <summary>
/// Ctor
/// </summary>
public PrepareShipping(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcDialog
{
lblHeadLine = { Content = Resources.StrStatePrepareShipping },
tbxMessage = { Text = "" }
};
}
/// <summary>
/// Store all information to DB and wait for sentinel to complete the action.
/// </summary>
/// <returns></returns>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Aug-30" author="Thomas Wiedebusch">
/// - Redesigned.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
try
{
_waitOnUserFeedback = true;
SignalRunningState(MaxActualProgressSteps);
var ucDialog = (UcDialog)UserControl;
// Push progress will publish all application versions and the core version
var retVal = Meter.PushProgress("Shipping", Version.ToString());
if (retVal == StatusReturn.Failed)
{
return retVal;
}
ActualProcessProgress++;
// Post EOL progress for sentinel
EolProgress.EOLCompleteDate = DateTime.Now;
// Check all processes for EOLStatus.NA or EOLStatus.OK except those fields filled by the sentinel
if (!EOLHandler.AcknowledgeAllEolProgresses(EolProgress, out var errorMessage))
{
ErrorMsgDispatcher(Resources.StrErrorMsgMissingEolProgress);
ErrorMsgDispatcher($"{Resources.StrErrorMsg}: {errorMessage}");
return StatusReturn.Failed;
}
if (!EOLHandler.PostEolProgress(EolProgress, out var feedback, out var error))
{
if (!string.IsNullOrEmpty(error))
Meter.WriteLog(error);
ErrorMsgDispatcher(Resources.StrErrorMsgUploadEolProgress);
return StatusReturn.Failed;
}
ActualProcessProgress++;
// Trigger sentinel and wait for feedback
var eolTriggerReturn = false;
var dataBaseCallIsFinished = false;
LogNewStatusMsg(Resources.StrMsgSentinelProcesses);
Task.Factory.StartNew(() =>
{
eolTriggerReturn = EOLHandler.TriggerSentinel(Meter.PcbId, out feedback, out error);
dataBaseCallIsFinished = true;
}, CancellationToken);
// Keep progress bar alive
while (!dataBaseCallIsFinished)
{
Thread.Sleep(TimeoutLoop_ms);
// Exit if meanwhile cancellation is required
if (CancellationToken.IsCancellationRequested)
return CancellationProcedure();
ActualProcessProgress++;
}
if (!eolTriggerReturn)
{
if (!string.IsNullOrEmpty(error))
Meter.WriteLog(error);
if (feedback != null)
foreach (var feed in feedback)
{
ErrorMsgDispatcher(Resources.StrErrorMsgSentinelConcerns + " " +
feed.Key + ": " + feed.Value);
}
ErrorMsgDispatcher(Resources.StrErrorMsgSentinelCheckEolProgress);
return StatusReturn.Failed;
}
SuccessMsgDispatcher(Resources.StrSuccessMsgSentinelCheckEolProgress);
ActualProcessProgress++;
// Initialize the ucDialog
ucDialog.SetHeadline(Resources.StrStateReadyForShipping);
ucDialog.SetMessage(Resources.StrInfoMeterBoxing);
var OptionList = new List<UcDialog.DialogOption>();
var okay = new UcDialog.DialogOption("Ok", Resources.StrInstructionMeterBoxing, () =>
{
ForcedSuccessExitState = ProcessState.ReadyForShipping;
_waitOnUserFeedback = false;
});
OptionList.Add(okay);
var print = new UcDialog.DialogOption(Resources.StrBtnPrintSuccessReport, Resources.StrInstructionSuccessPapers,
() =>
{
ForcedSuccessExitState = ProcessState.PrintSuccessReport;
_waitOnUserFeedback = false;
});
OptionList.Add(print);
ucDialog.SetDialogOptions(OptionList);
ActualProcessProgress = MaxActualProgressSteps;
SetOverallProcessTo100Percent();
while (_waitOnUserFeedback && SingleProcessState != SingleProcessState.Idle)
{
}
// Always use okay to kick off ForcedSuccessExitState
return StatusReturn.Okay;
}
catch (Exception e)
{
ErrorMsgDispatcher(e.Message);
return StatusReturn.Failed;
}
}
}
}
@@ -0,0 +1,90 @@
using System;
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;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
{
public class VisualInspection : BaseProductionProcess
{
/// <summary>
/// Test finished can be good or abort by done button to exit this production process
/// </summary>
private Boolean _waitOnUserFeedback;
private DialogResult _result = DialogResult.Undef;
/// <summary>
/// Ctor
/// </summary>
public VisualInspection(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcDialog
{
lblHeadLine = { Content = Resources.StrStateVisualInspection },
tbxMessage = { Text = "" }
};
}
/// <summary>
/// Check the Cordonel display
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Sep-04" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public override StatusReturn ExecuteProcess()
{
SignalRunningState();
StatusReturn retVal;
_waitOnUserFeedback = true;
var ucDialog = (UcDialog)UserControl;
ucDialog.Clear();
// Initialize the ucDialog
var OptionList = new List<UcDialog.DialogOption>();
var done = new UcDialog.DialogOption(Resources.StrBtnDone, Resources.StrMsgDoneVisualInspection,
() =>
{
_result = DialogResult.Done;
_waitOnUserFeedback = false;
}, isPositiveAcknBtn: true);
OptionList.Add(done);
var failed = new UcDialog.DialogOption(Resources.StrBtnFail, Resources.StrErrorMsgVisualInspection,
() =>
{
_result = DialogResult.Error;
_waitOnUserFeedback = false;
}, isPositiveAcknBtn: false);
OptionList.Add(failed);
ucDialog.SetDialogOptions(OptionList);
// wait on result or timeout
while (_waitOnUserFeedback && SingleProcessState != SingleProcessState.Idle)
{
}
switch (_result)
{
case DialogResult.Done:
retVal = StatusReturn.Okay;
break;
case DialogResult.Error:
default:
retVal = StatusReturn.Failed;
break;
}
return retVal;
}
}
}
@@ -0,0 +1,15 @@
using System;
using System.Windows.Media;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs
{ public class ProcessLogArgs : System.EventArgs
{
public readonly String Data;
public readonly SolidColorBrush Color;
public ProcessLogArgs(String text, SolidColorBrush solidColorBrush = null)
{
Data = text;
Color = solidColorBrush;
}
}
}
@@ -0,0 +1,23 @@
using System;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs
{
public class ProcessProgressArgs : System.EventArgs
{
public enum ProcessProgressType
{
MaxOverallProcessSteps,
OverallProcessesSteps,
MaxActualProcessSteps,
ActualProcessSteps,
}
public readonly ProcessProgressType ProgressType;
public readonly Int32 Progress;
public ProcessProgressArgs(Int32 progress, ProcessProgressType type)
{
Progress = progress;
ProgressType = type;
}
}
}
@@ -0,0 +1,16 @@
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs
{
/// <summary>
/// Class to publish actual state as event argument
/// </summary>
public class ProcessStateArgs : System.EventArgs
{
public readonly ProcessState? State;
public ProcessStateArgs(ProcessState? state)
{
State = state;
}
}
}
@@ -0,0 +1,16 @@
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs
{
/// <summary>
/// Class to publish actual state as event argument
/// </summary>
public class SingleProcessStateArgs : System.EventArgs
{
public readonly SingleProcessState SingleProcessState;
public SingleProcessStateArgs(SingleProcessState state)
{
SingleProcessState = state;
}
}
}
@@ -0,0 +1,95 @@
using System;
using Xylem.Common.CommonCore.Consts;
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.GenericProcesses
{
public class CheckOrderNumber : BaseProductionProcess
{
// string from scanner with string delimiter
private String _scan = "";
// marker for scan input received
private Boolean _scanInputReceived;
/// <summary>
/// Ctor
/// </summary>
public CheckOrderNumber(String name) : base( name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcBarcodeInput
{
lblHeadLine = { Content = Resources.StrInstructionScanOrder },
tbxScan = { Text = "" }
};
}
/// <summary>
/// Event handler taking the input from text box which should be scanned but also can be input manually.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void UserControlInput_Handler(Object sender, String input)
{
_scan = input;
_scanInputReceived = true;
}
/// <summary>
/// Scanning of order number from production order sheet and comparing with DB.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
if (Meter == null)
{
return StatusReturn.Failed;
}
SignalRunningState();
if (UserControl == null)
{
ErrorMsgDispatcher(Resources.StrErrorMsgScanOrderNumber);
return StatusReturn.Failed;
}
var ucBarCode = (UcBarcodeInput)UserControl;
ucBarCode.OnBarcodeInput += UserControlInput_Handler;
// set focus to text input for scanner
ucBarCode.FocusMe();
_scanInputReceived = false;
// wait for new line
while (!_scanInputReceived)
{
}
ucBarCode.OnBarcodeInput -= UserControlInput_Handler;
if (_scan.Equals(Meter.OrderNumber.ToString()))
{
return StatusReturn.Okay;
}
ErrorMsgDispatcher(Resources.StrErrorMsgScanOrderNumber);
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,105 @@
using System;
using Xylem.Common.CommonCore.Consts;
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.GenericProcesses
{
public class CheckSerialNumber : BaseProductionProcess
{
// string from scanner with string delimiter
private String _scan = "";
// marker for scan input received
private Boolean _scanInputReceived;
/// <summary>
/// Ctor
/// </summary>
public CheckSerialNumber(String name) : base(name)
{
}
/// <inheritdoc/>
protected override void InitUserControl()
{
UserControl = new UcBarcodeInput
{
lblHeadLine = { Content = Resources.StrInstructionScanHousing },
tbxScan = { Text = "" }
};
}
/// <summary>
/// Event handler taking the input from text box which should be scanned but also can be input manually.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void UserControlInput_Handler(Object sender, String input)
{
_scan = input;
_scanInputReceived = true;
}
/// <summary>
/// Scanning of serial number from housing and comparing with DB.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2025-Jan-12" author="Thomas Wiedebusch">
/// - Customer serial number alternative to sensus serial number and alternative radio address as valid inputs as
/// some customers require only the radio address as identification in the scan field.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
if (Meter == null)
{
return StatusReturn.Failed;
}
SignalRunningState();
if (UserControl == null)
{
ErrorMsgDispatcher(Resources.StrErrorMsgScanSerialNumber);
return StatusReturn.Failed;
}
var ucBarCode = (UcBarcodeInput)UserControl;
ucBarCode.OnBarcodeInput += UserControlInput_Handler;
// set focus to text input for scanner
ucBarCode.FocusMe();
_scanInputReceived = false;
// wait for new line
while (!_scanInputReceived)
{
}
ucBarCode.OnBarcodeInput -= UserControlInput_Handler;
// Customer serial number, sensus serial number or radio address are valid
var cleanedScan = _scan.Trim().Replace(" ", "");
var originalCustomerSerialNumber = Meter.CustomerSerialNumber.Trim().Replace(" ", "");
if (cleanedScan.Contains(originalCustomerSerialNumber) ||
cleanedScan.Contains(Meter.SerialNumber) ||
cleanedScan.Contains(Meter.RadioAddress.ToString()))
{
return StatusReturn.Okay;
}
ErrorMsgDispatcher(Resources.StrErrorMsgScanSerialNumber);
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,59 @@
using System;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses
{
public class ConnectCordonel : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public ConnectCordonel(String name) : base( name)
{
}
/// <summary>
/// Connect initially to Cordonel.
/// Checks the ability to login as it gets the password from DB.
/// Precondition:
/// - The <see cref="DetectCordonel"/> process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
Boolean retVal;
if (Meter == null)
{
return StatusReturn.Failed;
}
SignalRunningState();
// connection process
try
{
Meter.Login();
retVal = Meter.IsLoggedOn;
}
catch (Exception)
{
retVal = false;
}
if (retVal) return StatusReturn.Okay;
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelLogin);
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,52 @@
using System;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses
{
/// <summary>
/// Production process detects the serial number of the Cordonel.
/// This is the indication, that a Cordonel is connected to the hardware interface.
/// </summary>
public class DetectCordonel : BaseProductionProcess
{
/// <summary>
/// Ctor
/// Setup of name and single process state
/// </summary>
public DetectCordonel(String name) : base( name)
{
}
/// <summary>
/// Detect Genesis.
/// Precondition: - NONE. This is the initial step for all other processes.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
if (Meter == null)
{
return StatusReturn.Failed;
}
SignalRunningState();
// read the PcbId to validate Cordonel is attached
var retVal = !string.IsNullOrEmpty(Meter.GetPcbId());
if (retVal) return StatusReturn.Okay;
ErrorMsgDispatcher(Resources.StrErrorMsgCordonelDetection);
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,91 @@
using System;
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;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses
{
public class ErrorHandler : BaseProductionProcess
{
/// <summary>
/// Wait on user feedback on error of any production process
/// </summary>
private Boolean _waitOnUserFeedback;
/// <summary>
/// Ctor
/// </summary>
public ErrorHandler(String name) : base(name)
{
IsSilentProcess = true;
}
/// <inheritdoc/>
protected override void InitUserControl()
{
// Mark user control as error dialog to force different coloring
UserControl = new UcDialog(true)
{
lblHeadLine = { Content = Resources.StrErrorMsgEolProcess },
tbxMessage = { Text = "" },
};
}
/// <summary>
/// Common error message dialog with user required action:
/// - Retry failed steps,
/// - Abort without report,
/// - Abort with report.
/// </summary>
/// <returns>always <see cref="StatusReturn.Okay"/> to avoid side effects </returns>
/// <remarks date="2024-Sep-01..10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public override StatusReturn ExecuteProcess()
{
_waitOnUserFeedback = true;
SignalRunningState();
var ucDialog = (UcDialog)UserControl;
ucDialog.SetMessage(Resources.StrErrorMsgCordonelInvestigate);
// Initialize the ucDialog
var OptionList = new List<UcDialog.DialogOption>();
var retry = new UcDialog.DialogOption(Resources.StrBtnRetryFailedSteps, Resources.StrInfoRetryFailedSteps,
() =>
{
ForcedSuccessExitState = ProcessState.RepeatFailedTests;
_waitOnUserFeedback = false;
}, true);
OptionList.Add(retry);
var abort = new UcDialog.DialogOption(Resources.StrBtnStop, Resources.StrInstructionStop,
() =>
{
ForcedSuccessExitState = ProcessState.Stop;
_waitOnUserFeedback = false;
}, true, false);
OptionList.Add(abort);
var print = new UcDialog.DialogOption(Resources.StrBtnPrintErrorReport, Resources.StrInstructionReturnPapers,
() =>
{
ForcedSuccessExitState = ProcessState.PrintReturnReport;
_waitOnUserFeedback = false;
}, true, false);
OptionList.Add(print);
ucDialog.SetDialogOptions(OptionList);
while (_waitOnUserFeedback)
{
}
// Always use okay to kick off ForcedSuccessExitState
return StatusReturn.Okay;
}
}
}
@@ -0,0 +1,112 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Production.ProductionUiCordonel.Properties;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses
{
/// <summary>
/// Request the serial number and radio address from DB.
/// </summary>
public class GetSerialNumberAndRadioAddress : BaseProductionProcess
{
/// <summary>
/// Ctor
/// </summary>
public GetSerialNumberAndRadioAddress(String name) : base(name)
{
}
/// <summary>
/// Request serial number and radio address from DB.
/// Precondition: - The detection process has to be completed as the PcbId is needed.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-14" author="Roland Drabesch/Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
/// these will be automatically set based on return value in
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
/// </remarks>
/// <remarks date="2024-Mar-13" author="Thomas Wiedebusch">
/// - Get customer serial number for display.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
public override StatusReturn ExecuteProcess()
{
if (Meter == null)
{
return StatusReturn.Failed;
}
SignalRunningState();
var retVal = false;
try
{
var url = $"{ServiceUrls.MarriageServiceUrl()}GetSerialNumberAndOrderNumber?PcbId={Meter.PcbId}" +
"&withRadioAdress=true";
var responseJson = LocalWebRequest.GetRequest(url, 30000);
var stringArray = JsonConvert.DeserializeObject<String[]>(responseJson);
if (stringArray != null)
{
int.TryParse(stringArray[1], out var tmpInt);
Meter.OrderNumber = tmpInt;
Int64 tmpLong = 0;
if (stringArray.Length > 2)
{
long.TryParse(stringArray[2], out tmpLong);
}
Meter.RadioAddress = tmpLong;
Meter.SerialNumber = stringArray[0];
retVal = true;
}
}
catch (Exception ex)
{
Meter?.WriteLog(ex.Message);
ErrorMsgDispatcher(Resources.StrErrorMsgOrderDetails);
return StatusReturn.Failed;
}
if (!retVal) return StatusReturn.Failed;
// Exit if meanwhile cancellation is required
if (CancellationToken.IsCancellationRequested)
return CancellationProcedure();
retVal &= OrderDetailsDb.GetCordonelOrderDetails(Meter.OrderNumber, out var orderDetails);
if (!retVal)
return StatusReturn.Failed;
retVal &= OrderDetailsDb.GetAllCordonelSerialNumbersFromDb(orderDetails[0].AuftragNr,
orderDetails[0].PositionNr, out var serialNumbers);
if (!retVal) return StatusReturn.Failed;
foreach (var serialNumber in serialNumbers.Where(serialNumber =>
Meter.PcbId.Equals(serialNumber.PcbId.ToString())))
{
Meter.CustomerSerialNumber = serialNumber.CustomerSerialNumber;
return StatusReturn.Okay;
}
return StatusReturn.Failed;
}
}
}
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
{
public interface IProcessStateDef
{
/// <summary>
/// Collection of state information
/// </summary>
List<ProcessStateStruct> StateContainer { get; }
/// <summary>
/// Get the process start struct linked to state of state machine
/// </summary>
/// <param name="stateMachineState">state to search for information text</param>
/// <returns>process state struct of the attached state</returns>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
ProcessStateStruct GetProcessStateStructOfState(ProcessState stateMachineState);
/// <summary>
/// Get the process linked to state of state machine
/// </summary>
/// <param name="stateMachineState">state to search for information text</param>
/// <returns>process attached to the state</returns>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
IProductionProcess GetProcessOfState(ProcessState stateMachineState);
/// <summary>
/// Get the current information of the state machine state
/// </summary>
/// <param name="stateMachineState">state to search for information text</param>
/// <returns>information attached to state depending on region</returns>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Mar-18" author="Thomas Wiedebusch">
/// - If state == null return error message.
/// </remarks>
String GetStateInfoOfState(ProcessState? stateMachineState);
/// <summary>
/// Get the info text from production process
/// </summary>
/// <param name="process">process to look for state info text</param>
/// <returns>information attached to process depending on region</returns>
/// <remarks date="2023-Mar-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
String GetStateInfoOfProcess(IProductionProcess process);
/// <summary>
/// Get the state machine state of the text
/// </summary>
/// <param name="text">information text to search if state is assigned</param>
/// <returns>state attached to information text</returns>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
ProcessState GetStateOfInfoText(String text);
/// <summary>
/// Get the state machine state from for the process
/// </summary>
/// <param name="process">process to look for state info text</param>
/// <returns>state attached to process</returns>
/// <remarks date="2023-Mar-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
ProcessState GetStateOfProcess(IProductionProcess process);
/// <summary>
/// Get number of EOL relevant processes for statistic e.g. progress bar
/// </summary>
/// <returns>number of initialized processes</returns>
/// <remarks date="2023-Mar-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
/// - Used marker for production relevant processes
/// </remarks>
Int32 GetNumberOfInitializedProcesses();
/// <summary>
/// Get a sorted list of all production processes in order of the execution sequence
/// </summary>
/// <returns>list of initialized processes</returns>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
/// - Used marker for production relevant processes
/// </remarks>
/// <remarks date="2025-Apr-14" author="Thomas Wiedebusch">
/// - Changed to sorted list by production process number
/// </remarks>
List<IProductionProcess> GetAllProductionProcesses();
}
}
@@ -0,0 +1,243 @@
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 IProductionProcess
{
/// <summary>
/// Cancellation token to stop operation
/// </summary>
CancellationToken CancellationToken { set; get; }
/// <summary>
/// Event for process list change or state of single process step change from wait to execute to done or error
/// </summary>
event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
/// <summary>
/// Entire process changed status due to state change of process execution
/// </summary>
event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
/// <summary>
/// Log event to log file and signal to GUI
/// </summary>
event EventHandler<ProcessLogArgs> OnProcessLogRequest;
/// <summary>
/// Send progress information to GUI for progress bars
/// </summary>
event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
/// <summary>
/// State of single production process
/// </summary>
/// <returns>process name</returns>
SingleProcessState SingleProcessState
{
get;
}
/// <summary>
/// Name of actual production process
/// </summary>
/// <returns>process name</returns>
String ProcessName
{
get;
}
/// <summary>
/// Version of assembly
/// </summary>
/// <returns>process name</returns>
Version Version
{
get;
}
/// <summary>
/// Set reference to status of meter
/// </summary>
GenesisStatus ProductionStatus
{
set;
}
/// <summary>
/// Set reference to end of line (EOL) progress of meter
/// </summary>
EOLProgressModel EolProgress
{
set;
}
/// <summary>
/// Set reference to production requirements of meter
/// </summary>
CordonelRequirements ProductionRequirements
{
set;
}
/// <summary>
/// Genesis meter
/// </summary>
IGenesisMeter Meter
{
set;
}
/// <summary>
/// Register restorer
/// </summary>
RegisterRestorer RegisterRestorer
{
set;
}
/// <summary>
/// Sensus radio encryption key as list of string as a single string is defined as object but
/// cannot be changed in derived classes as "Strings are immutable". But objects of a list can
/// be changed so the trick is to take a list instead of a strings.
/// </summary>
List<String> RadioEncryptionKey
{
set;
}
/// <summary>
/// Password container
/// </summary>
MeterPwdDb PwdContainer
{
set;
}
/// <summary>
/// Get the user control for the GUI and cleans the content.
/// </summary>
/// <returns>specific user control for GUI</returns>
/// <summary>
/// Update the sand clock on image
/// </summary>
/// <remarks date="2023-Feb-14" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-28" author="Roland Drabesch">
/// - Clear content, is important for recurrent call on a retry to clean it.
/// </remarks>
UserControl GetUserControl();
/// <summary>
/// Production process kick off with all current- and exit states.
///
/// ATTENTION: The OverallStateEvent has to be fired before the SingleProcessState event, as the
/// SingleProcessState event will uninstall the event handlers on finalizing!
///
/// </summary>
/// <param name="successExitState">success exit state, if set to null, leave state as is</param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2023-Jan-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
void StartProcess(ProcessState? successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop);
/// <summary>
/// Preparation of process in advance to execution routine.
/// This will set the process state to "SingleProcessState.Waiting"
/// </summary>
void InitProcess();
/// <summary>
/// Preparation of process for recurrent run to unblocking this state.
/// This will set the process state to "SingleProcessState.Idle"
/// </summary>
void IdleProcess();
/// <summary>
/// Preparation of process to signal abort request.
/// This will set the process state to "SingleProcessState.Abort"
/// </summary>
void AbortProcess();
/// <summary>
/// Preparation of process to signal retry request.
/// This will set the process state to "SingleProcessState.Idle"
/// </summary>
void RetryProcess();
/// <summary>
/// Pre execution process to isolate database calls from the ExecuteProcess method.
/// This is useful for unit tests to separate the peripheral calls.
/// </summary>
/// <returns></returns>
StatusReturn PreExecuteProcess();
/// <summary>
/// Process execution routine which will be executed in separate thread.
/// </summary>
/// <returns></returns>
StatusReturn ExecuteProcess();
/// <summary>
/// Routine for finalization of each process like cleaning and setting a status or control
/// Finalization of process.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2023-Jan-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
void FinalizeProcess(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects);
/// <summary>
/// Routine for error handling:
/// - Log success with Genesis write log,
/// - Color output to GUI in green,
/// - Dispatch message to UI.
/// </summary>
/// <param name="successMsg">message to dispatch</param>
/// <remarks date="2023-Mar-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
void SuccessMsgDispatcher(String successMsg);
/// <summary>
/// Routine for warning handling:
/// - Log error with Genesis write log,
/// - Color output to GUI in orange,
/// - Dispatch message to UI.
/// </summary>
/// <param name="warningMsg">message to dispatch</param>
/// <remarks date="2023-Mar-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
void WarningMsgDispatcher(String warningMsg);
/// <summary>
/// Routine for error handling:
/// - Log error with Genesis write log,
/// - Color output to GUI in red,
/// - Dispatch message to UI.
/// </summary>
/// <param name="errorMsg">message to dispatch</param>
/// <remarks date="2023-Mar-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
void ErrorMsgDispatcher(String errorMsg);
}
}
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ProductionUiCordonel.ProductionProcesses.Enum;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
namespace ProductionUiCordonel.ProductionProcesses
{
public class MultiProcessController
{
public List<IProductionProcess> Processes;
public event EventHandler ProcessStateChangedHandler;
public event EventHandler<PopUpProcessLogArgs> ProcessLogHandler;
public event EventHandler<PopUpProcessProgressArgs> ProcessProgressHandler;
private CancellationTokenSource canncelToken = new CancellationTokenSource();
public IProductionProcess ErrorProcess { get; internal set; }
public IProductionProcess DoneProcess { get; internal set; }
public void Stop()
{
canncelToken.Cancel();
}
internal void InitUserControl()
{
Processes.ForEach(a => a.Init());
}
internal void Start(GenesisMeter meter)
{
Processes.ForEach(f=> f.Init());
DoneProcess.Init();
ErrorProcess.Init();
var t = new Task(() =>
{
while (Processes.Any(cp => cp.GetState() == ProductionProcessState.Waiting) && !Processes.Any(cp => cp.GetState() == ProductionProcessState.Error))
{
var process = Processes.First(cp => cp.GetState() == ProductionProcessState.Waiting);
AddProccessEvents(process);
process.Start(meter,canncelToken.Token);
RemoveProccessEvents(process);
CalcAndPushOverAllProcess();
}
var showLastStep = DoneProcess;
if (Processes.Any(cp => cp.GetState() == ProductionProcessState.Error))
{
showLastStep = ErrorProcess;
}
AddProccessEvents(showLastStep);
showLastStep.Start(meter, canncelToken.Token);
RemoveProccessEvents(showLastStep);
CalcAndPushOverAllProcess();
});
t.Start();
}
void CalcAndPushOverAllProcess()
{
var Total = Processes.Count;
var Done = Processes.Where(w => w.GetState() == ProductionProcessState.Done).Count();
var progress = 0;
if (Done > 0)
{
progress = (100 / Total) * Done;
}
ProcessProgressHandler?.Invoke(this, new PopUpProcessProgressArgs(progress, PopUpProcessProgressArgs.ProcessProgessType.Total ));
}
private void AddProccessEvents(IProductionProcess process)
{
process.PopUpProcessProgressChangedHandler += Process_PopUpProcessProgressChangedHandler;
process.PopUpProcessLogHandler += Process_PopUpProcessLogHandler;
process.PopUpProcessStateChangedHandler += ProcessOnPopUpProcessStateChangedHandler;
}
private void RemoveProccessEvents(IProductionProcess process)
{
process.PopUpProcessProgressChangedHandler -= Process_PopUpProcessProgressChangedHandler;
process.PopUpProcessLogHandler -= Process_PopUpProcessLogHandler;
process.PopUpProcessStateChangedHandler -= ProcessOnPopUpProcessStateChangedHandler;
}
private void Process_PopUpProcessProgressChangedHandler(Object sender, PopUpProcessProgressArgs e)
{
ProcessProgressHandler?.Invoke(sender,e);
}
private void Process_PopUpProcessLogHandler(Object sender, PopUpProcessLogArgs e)
{
ProcessLogHandler?.Invoke(sender,e);
}
private void ProcessOnPopUpProcessStateChangedHandler(Object sender, EventArgs e)
{
ProcessStateChangedHandler?.Invoke(sender, e);
}
}
}
@@ -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
{
/// <summary>
/// Check all parameters needed as preparation for assembly.
/// </summary>
public class CheckProductionState : BaseProductionProcess
{
/// <summary>
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
/// </summary>
private const Int32 MaxActualProgressSteps = 13;
/// <summary>
/// FW update package containing the ABC and the ADF.
/// </summary>
public List<FilePart> FwUpdatePackage
{
get; set;
}
/// <summary>
/// Ctor
/// </summary>
public CheckProductionState(String name) : base(name)
{
}
/// <summary>
/// Start database calls and meter readouts here for better testability and ability to fill the
/// objects manually by the test routine.
/// </summary>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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<FilePart>();
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;
}
/// <summary>
/// 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 <see cref="GetSerialNumberAndRadioAddress"/> 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,
/// </summary>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Roland Drahbesh">
/// - Initial.
/// </remarks>
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 ---------------------------------------
}
/// <summary>
/// Common routine to exit with error and user window with error message
/// </summary>
/// <param name="errorMsg"></param>
/// <remarks date="2024-Nov-06" author="Thomas Wiedebusch">
/// - Extracted from <see cref="ExecuteProcess"/> for better testability.
/// </remarks>
private StatusReturn ExitWithErrorAndUserWindow(String errorMsg)
{
ErrorMsgDispatcher(errorMsg);
MessageBox.Show(errorMsg, Resources.StrProcessStateError, MessageBoxButton.OK, MessageBoxImage.Error);
return StatusReturn.Failed;
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// Check production order number between meter and requirements.
///
/// Software Requirement Specification:
/// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_002]_Check_ProductionOrderNo
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// Check region between meter and requirements
///
/// Software Requirement Specification:
/// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_004]_Check_Region
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// Check meter size
///
/// Software Requirement Specification:
/// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_006]_Check_Meter_Size
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// 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 "<see cref="MeterFwUpdate"/> 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
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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<FileApplications>();
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<Byte>(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;
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// Check the remaining lifetime
///
/// Software Requirement Specification:
/// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_009]_Check_Remaining_Lifetime
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// Check the months between first battery activation and now
///
/// Software Requirement Specification:
/// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_010]_Check_Storage_Months
///
/// </summary>
/// <param name="feedbackMsg"></param>
/// <returns></returns>
/// <remarks date="2025-Mai-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
}
}
@@ -0,0 +1,180 @@
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.PickingProcesses
{
/// <summary>
/// Concatenation for all production process states to feed the state machine:
/// - Production process class (representing a production process step), null if hidden process,
/// - attached state,
/// - state information to display a text message,
/// - production process number to define where to display in the overview,
/// - next state which should be executed after this succeeded,
/// [optional]:
/// - own error state,
/// - own break state,
/// - kick off state for parallel execution after starting this process in a separate task,
/// - wait for single process to finish successfully,
/// - wait for all processes to finish successfully,
/// - start always independent of any result, e.g. the error state needs to be started if called!
/// </summary>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public class PickingProcessStateDef : BaseProcessStatesDef
{
/// <summary>
/// State-machine definitions of production-processes which feeds the state-machine.
///
///
/// INSTRUCTION FOR PREPARATION:
/// -----------------------------
/// STEP 1: Define some states for your processes <see cref="ProcessState"/>
/// STEP 2: Implement your processes like <see cref="DetectCordonel"/>
/// STEP 3: Register and chain your states here
///
///
/// INSTRUCTION FOR NORMAL USAGE:
/// -----------------------------
/// Means one state automatically switched on successfully execution to the next required state.
///
/// Define the (e.g.):
/// productionProcess: new DetectCordonel(Resources.StrStateDetectCordonel),
/// processState: ProcessState.DetectCordonel,
/// processInfo: Resources.StrStateDetectCordonel,
/// productionProcessNo: 0,
/// nextStateOnSuccess: ProcessState.CheckNewMeter),
///
/// 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).
///
/// Define the (e.g.):
///
/// 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 after starting the "ConnectCordonel() task"
/// kickOffParallelState: ProcessState.GetSerialNumberAndRadioAddress),
///
///
/// INSTRUCTION TO DELAY THE START OF A PROCESS AND WAIT ON RESULTS OF OTHER PROCESS(ES):
/// -------------------------------------------------------------------------------------
/// 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 state of the process needed to wait for (e.g.):
///
/// 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 after starting
/// kickOffParallelState: ProcessState.ProcessPasswordFile,
/// // Wait until this process has been successfully completed
/// waitForSingleProcess: ProcessState.ConnectCordonel),
///
/// Wait for all preceding processes to complete to be sure to get all data and all processed are succeeded:
/// Set the "waitForAllProcesses" to true (e.g.):
/// productionProcess: new ExecPasswordFile(Resources.StrStatePasswordFile),
/// processState: ProcessState.ProcessPasswordFile,
/// processInfo: Resources.StrStatePasswordFile,
/// productionProcessNo: 5,
/// nextStateOnSuccess: ProcessState.ExecFinalParametrization,
/// waitForAllProcesses: true),
///
/// </summary>
public PickingProcessStateDef()
{
var stateContainer = new List<ProcessStateStruct>
{
#region ----------------------------------- GENERIC-STATES---------------------------------------------
// 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),
// 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,
// Repeat detect as a new meter has been connected
nextStateOnSuccess: ProcessState.DetectCordonel,
// Used to go ahead as new meter returns "false" meaning error state
errorExitState: 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!
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),
// 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,
nextStateOnSuccess: ProcessState.CheckOrderNumber,
waitForAllProcesses: true),
// This scans the order number, it has to be executed exclusively with automatic
new ProcessStateStruct(
productionProcess: new CheckOrderNumber(Resources.StrStateCheckOrderNumber),
processState: ProcessState.CheckOrderNumber,
processInfo: Resources.StrStateCheckOrderNumber,
productionProcessNo: 3,
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 --------------------------------------------
};
StateContainer.AddRange(stateContainer);
}
}
}
@@ -0,0 +1,13 @@
using System;
namespace ProductionUiCordonel.ProductionProcesses
{
public class PopUpProcessLogArgs : EventArgs
{
public readonly String Data;
public PopUpProcessLogArgs(String text)
{
Data = text;
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace ProductionUiCordonel.ProductionProcesses
{
public class PopUpProcessProgressArgs : EventArgs
{
public enum ProcessProgessType
{
Total,
SubProcess,
}
public readonly ProcessProgessType ProgressType;
public readonly Int32 Progress;
public PopUpProcessProgressArgs(Int32 progress, ProcessProgessType type)
{
Progress = progress;
ProgressType = type;
}
}
}
@@ -0,0 +1,860 @@
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
{
/// <summary>
/// Implementation for all methods needed to drive the state machine of the process controller
/// </summary>
public class ProcessController
{
#region ------------------------------------------ Variables --------------------------------------------------
// actual genesis meter
private IGenesisMeter _genesisMeter;
// Cordonel requirements for production
private CordonelRequirements _cordonelRequirements;
// EOL progress states for production
private EOLProgressModel _cordonelEolProgress;
// last pcbId for check of changed genesisMeter
private String _lastPcbId;
// batch just used for setup, as only one meter will be assigned
private MeterBatch _meterBatch;
// state of the state machine
private ProcessState _stateMachineState;
// locker to avoid repeated state execution
private ProcessState _lockedProcessState;
// sender of state change event to evaluate in e.g. error process
private IProductionProcess _stateChangeRequestProcess;
// List of production processes
public List<IProductionProcess> Processes
{
get;
private set;
}
// Container for production process states
private IProcessStateDef _processStateDef;
// Container for production status information, a list is used to keep the content over all classes of production
// processes as a reference seems not to return their value (always reset to the initial value)
private GenesisStatus _productionStatus;
// register restorer
private RegisterRestorer _registerRestorer;
// sensus radio encryption key
private List<String> _radioEncryptionKey;
// Password container with all information, a list is used to keep the content over all classes of production
// processes as a reference seems not to return their value (always reset to the initial value)
private MeterPwdDb _pwdContainer;
// reminder for slot
private readonly Int32 _slot;
/// <summary>
/// Change the overview table of the GUI to the state of the individual process
/// </summary>
public event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
/// <summary>
/// The overall process chain changed needed to signal to the GUI
/// - Can be forced by the state machine to invoke,
/// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next
/// required sate after break, error or completion to change the state of the state machine.
/// </summary>
public event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
/// <summary>
/// Request for logging to GUI output and log-file
/// </summary>
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
/// <summary>
/// Keep the progress bars of the GUI up to date
/// </summary>
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
// cancellation token
private CancellationTokenSource _cancellationTokenSource;
private CancellationToken _cancellationToken;
// cyclic timer for state machine call
private Timer _processCtrlTimer;
private const Int32 ProcessTimerCycleMs = 250;
/// <summary>
/// Avoid waiting for a single process completion.
/// </summary>
public const IProductionProcess DoNotWaitForSingleProcessCompletion = null;
/// <summary>
/// Kick off a parallel execution of the state machine.
/// </summary>
public const Boolean ExecuteInParallel = true;
#endregion --------------------------------------- Variables --------------------------------------------------
#region ------------------------------------------ Class ------------------------------------------------------
/// <summary>
/// Ctor:
/// - Setup process timer for cyclic call of state machine,
/// - Create and setup Cordonel.
/// </summary>
/// <param name="slot">physical slot for this meter</param>
/// <param name="processSateDef"></param>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-04" author="Thomas Wiedebusch">
/// - Always set to production mode to load the registers for this FW and not the latest,
/// - Always RegisterWatchService on,
/// - Always MinMaxCHeck on.
/// </remarks>
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;
}
/// <summary>
/// Initialization of new meter
/// </summary>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Initialize processes to build a list of all production processes.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes
/// </remarks>
private void InitNewMeter()
{
_genesisMeter = new GenesisMeter();
_genesisMeter.SetupFromConfigFile(_slot);
// Important setup to overwrite setting from "Setup" in GTB
_genesisMeter.Configuration.ProductionMode = true;
_genesisMeter.Configuration.UseRegisterWatchService = true;
_genesisMeter.Configuration.UseMinMaxCheck = true;
// Meter batch even if only one meter in use as the meter batch routines can be used
_meterBatch = new MeterBatch();
_meterBatch.AddMeter(_genesisMeter);
// Set lock process to unreachable state delimiter to force initial entry of state machine
_lockedProcessState = ProcessState.StateListDelimiter;
// Create a register restorer
_registerRestorer = new RegisterRestorer(_genesisMeter);
_radioEncryptionKey = new List<String>();
// Containers for production status and standard or special requirement for production
_productionStatus = new GenesisStatus();
_cordonelRequirements = new CordonelRequirements();
// Password container as reminder for repeated test on identical device
_pwdContainer = new MeterPwdDb();
// EOL progress to feed the sentinel
_cordonelEolProgress = new EOLProgressModel();
}
/// <summary>
/// Removal of old meter
/// </summary>
private void RemoveMeter()
{
_genesisMeter?.ClearPassword();
// meter batch dispose disposes all IMeters
_meterBatch?.Dispose();
_meterBatch = null;
_genesisMeter = null;
_registerRestorer = null;
_radioEncryptionKey = null;
_productionStatus = null;
_cordonelRequirements = null;
_pwdContainer = null;
_cordonelEolProgress = null;
}
/// <summary>
/// Dispose.
/// </summary>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void Dispose()
{
_processCtrlTimer?.Dispose();
_processCtrlTimer = null;
_processStateDef = null;
KillProcesses();
RemoveMeter();
}
#endregion --------------------------------------- Class ------------------------------------------------------
#region ------------------------------------------ Events -----------------------------------------------------
/// <summary>
/// Single process state or process list changed event,
/// Use event to force special procedure like uninstalling events
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Remove process events if process is finished.
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - Remove process events if process is finished or skipped.
/// </remarks>
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);
}
/// <summary>
/// Handler to set a new state for the state machine regarding the entire final-process
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2023-Mar-03" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void 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;
}
}
/// <summary>
/// Process timer
/// </summary>
/// <param name="state"></param>
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void TmrProcessCtrlCycle_Elapsed(Object state)
{
StateMachine();
}
#endregion --------------------------------------- Events -----------------------------------------------------
#region ------------------------------------------ State Machine ----------------------------------------------
/// <summary>
/// State-machine of ProcessController which serves all production processes.
/// This-state machine executes all states defined in the "GenericStateMachineDef" and the individual
/// production-process specific "XxxStateMachineDef".
///
/// A few states with special functions will be caught in advance in this StateMachine
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Generic implementation
/// </remarks>
private void StateMachine()
{// avoid repeated execution of the state machine on unchanged state
if (_stateMachineState == _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 ------------------------------------------------------
/// <summary>
/// Returns the currentGenesis information for display
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public CordonelRequirements GetProductionRequirements()
{
return _cordonelRequirements;
}
/// <summary>
/// Returns the currentGenesis information for display
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public IGenesisMeter GetGenesisMeter()
{
return _genesisMeter;
}
/// <summary>
/// Clears all collected data on new genesisMeter and reminds the detected for next
/// detection and run of this comparison.
/// </summary>
/// <returns>true for new meter</returns>
/// <remarks date="2023-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - The StartProcessSequence has already removed all processes from list to have a clean start,
/// avoid it here again, because the detect is now in the list!
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Handle genesis meter is null as new meter.
/// </remarks>
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
/// - Check _genesisMeter not null before equality with old pcbid.
/// </remarks>
private Boolean CheckForNewMeter()
{
if (_genesisMeter == null ||
(!string.IsNullOrEmpty(_lastPcbId) &&
_genesisMeter?.PcbId != null && !_genesisMeter.PcbId.Equals(_lastPcbId)))
{
// This loop will be entered after the meter has changed, but not on the initial meter
// after program start.
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;
}
/// <summary>
/// Restart final test on first failed or aborted test.
/// </summary>
/// <remarks date="2023-Jul-18" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes and search in Processes
/// </remarks>
/// <remarks date="2024-Oct-21" author="Thomas Wiedebusch">
/// - Leave skipped processes as is.
/// </remarks>
private ProcessState RepeatFailedTests()
{
var state = ProcessState.Idle;
// Search for first test in processes list which failed or aborted due to final test in
// parallel processing forced abort of active processes
foreach (var p in Processes)
{
if (p.SingleProcessState == SingleProcessState.Error ||
p.SingleProcessState == SingleProcessState.Abort ||
p.SingleProcessState == SingleProcessState.Retry)
{
// set the state to the first entry to kick off the state machine here
state = _processStateDef.GetStateOfProcess(p);
break;
}
}
// Put all processes which are not successfully completed to init (Waiting for execution)
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
pp.SingleProcessState != SingleProcessState.Skipped))
{
pp.IdleProcess();
}
return state;
}
/// <summary>
/// Error handler
/// </summary>
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Feb-07" author="Thomas Wiedebusch">
/// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization
/// to re-start with ProductionProcessExecFinalParametrization.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes
/// </remarks>
/// <remarks date="2025-Jan-28" author="Thomas Wiedebusch">
/// - Kick off exec parametrization if check after reboot failed.
/// </remarks>
private void ProcessErrorHandler(IProductionProcess process)
{
if (process == null)
return;
// Special action if parametrization check failed to repeat it
if (ProcessState.CheckFinalParametrization == _processStateDef.GetStateOfProcess(process))
{
// Signal state change to retry sequence exec config, store config, reboot, check config
var pp = _processStateDef.GetProcessOfState(ProcessState.ExecFinalParametrization);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.ExecStoreConfiguration);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.RebootCordonel);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.CheckFinalParametrization);
pp.RetryProcess();
}
// Error signal needed to abort to all processes
AbortProcesses();
}
/// <summary>
/// Calculate the maximum of all processes expected to execute and signal to GUI.
/// </summary>
private void CalcAndPushMaxOverAllProcessSteps()
{
if (_processStateDef == null)
return;
var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses();
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps,
ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps));
}
/// <summary>
/// Calculate all processes which are completed or skipped and signal to GUI.
/// </summary>
private void CalcAndPushOverAllProgress()
{
if (Processes == null)
return;
// check the readiness of all processes in the loop
var _overallReadyProcesses = 0;
foreach (var p in Processes)
{
if (p.SingleProcessState == SingleProcessState.Succeeded ||
p.SingleProcessState == SingleProcessState.Skipped)
_overallReadyProcesses++;
}
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_overallReadyProcesses,
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
}
/// <summary>
/// Initialize all processes which are connected to a production process.
/// </summary>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitProcesses()
{
if (Processes == null)
Processes = new List<IProductionProcess>();
// get all processes relevant for EOL from table which has to be sorted
var processes = _processStateDef.GetAllProductionProcesses();
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);
}
}
}
/// <summary>
/// Production process kick off:
/// - Sets single process state to waiting within the process init,
/// - Adds new process to process list,
/// - Optional waits for single process to complete before process is going to be started,
/// - Optional waits for all previously assigned and started processes to complete before process is going to
/// be started,
/// - Assigns slot, productionStatus, programmingParameters and genesisMeter to process,
/// - Adds event handlers for process information,
/// - Starts process.
/// </summary>
/// <param name="processStateStruct"></param>
/// <returns>actual state</returns>
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Skipped process due to one of the previous processes failed
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - Restructured to avoid process start if processes to wait for are not completed
/// </remarks>
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
/// - Changed from programming parameters to register restorer
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Changed setup process to start process being able to create a process list in advance.
/// </remarks>
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
/// - Changed setup process to start process being able to create a process list in advance.
/// </remarks>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Feed with processStateStruct.
/// </remarks>
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);
}
/// <summary>
/// Check all processes for error or abort request
/// </summary>
/// <returns>true if abort required</returns>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Cancellation token.
/// </remarks>
private Boolean AnyProcessRequiresAbortion()
{
return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error ||
pp.SingleProcessState == SingleProcessState.Abort ||
_cancellationToken.IsCancellationRequested);
}
/// <summary>
/// Check process if in status waiting or running (incomplete)
/// </summary>
/// <param name="pp">production process</param>
/// <returns>true if process to wait for execution or exit running state</returns>
private Boolean WaitForProcess(IProductionProcess pp)
{
return pp.SingleProcessState == SingleProcessState.Waiting ||
pp.SingleProcessState == SingleProcessState.Running;
}
/// <summary>
/// Install process events
/// </summary>
/// <param name="process"></param>
private void AddProcessEvents(IProductionProcess process)
{
if (process == null)
return;
process.OnProcessProgressChanged += ProcessProgress_Handler;
process.OnProcessLogRequest += ProcessLog_Handler;
process.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler;
process.OnOverallProcessStateChanged += OverallProcessStateChanged_Handler;
}
/// <summary>
/// Remove process events
/// </summary>
/// <param name="process"></param>
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;
}
/// <summary>
/// Get the actual process state of the state machine
/// </summary>
/// <returns>actual process state</returns>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private ProcessState GetProcessState()
{
return _stateMachineState;
}
/// <summary>
/// Set the new process state of the state machine
/// </summary>
/// <returns>true, if process state is set</returns>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean SetProcessState(ProcessState newProcessState)
{
if (newProcessState != _stateMachineState)
{
_stateMachineState = newProcessState;
return true;
}
return false;
}
/// <summary>
/// Remove all processes from the <see cref="Processes"/> list:
/// - Signal change to GUI,
/// - Remove process events all,
/// - Set state to abort.
/// </summary>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
/// - Abort register access.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - Cancellation token source.
/// </remarks>
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();
}
}
/// <summary>
/// Abort all processes if one previous process failed, but leave the <see cref="Processes"/> list intact.
/// Leave all events for those which are already running intact to force a clean stop.
/// </summary>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void AbortProcesses()
{
if (Processes == null || Processes.Count == 0)
return;
// Skip all processes which are not succeeded, erroneous or skipped to leave the status intact
// and request abort for all others
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
pp.SingleProcessState != SingleProcessState.Error &&
pp.SingleProcessState != SingleProcessState.Skipped &&
pp.SingleProcessState != SingleProcessState.Retry))
{
pp.AbortProcess();
}
}
#endregion --------------------------------------- Tools ------------------------------------------------------
}
}
@@ -0,0 +1,860 @@
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
{
/// <summary>
/// Implementation for all methods needed to drive the state machine of the process controller
/// </summary>
public class ProcessController
{
#region ------------------------------------------ Variables --------------------------------------------------
// actual genesis meter
private IGenesisMeter _genesisMeter;
// Cordonel requirements for production
private CordonelRequirements _cordonelRequirements;
// EOL progress states for production
private EOLProgressModel _cordonelEolProgress;
// last pcbId for check of changed genesisMeter
private String _lastPcbId;
// batch just used for setup, as only one meter will be assigned
private MeterBatch _meterBatch;
// state of the state machine
private ProcessState _stateMachineState;
// locker to avoid repeated state execution
private ProcessState _lockedProcessState;
// sender of state change event to evaluate in e.g. error process
private IProductionProcess _stateChangeRequestProcess;
// List of production processes
public List<IProductionProcess> Processes
{
get;
private set;
}
// Container for production process states
private IProcessStateDef _processStateDef;
// Container for production status information, a list is used to keep the content over all classes of production
// processes as a reference seems not to return their value (always reset to the initial value)
private GenesisStatus _productionStatus;
// register restorer
private RegisterRestorer _registerRestorer;
// sensus radio encryption key
private List<String> _radioEncryptionKey;
// Password container with all information, a list is used to keep the content over all classes of production
// processes as a reference seems not to return their value (always reset to the initial value)
private MeterPwdDb _pwdContainer;
// reminder for slot
private readonly Int32 _slot;
/// <summary>
/// Change the overview table of the GUI to the state of the individual process
/// </summary>
public event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
/// <summary>
/// The overall process chain changed needed to signal to the GUI
/// - Can be forced by the state machine to invoke,
/// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next
/// required sate after break, error or completion to change the state of the state machine.
/// </summary>
public event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
/// <summary>
/// Request for logging to GUI output and log-file
/// </summary>
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
/// <summary>
/// Keep the progress bars of the GUI up to date
/// </summary>
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
// cancellation token
private CancellationTokenSource _cancellationTokenSource;
private CancellationToken _cancellationToken;
// cyclic timer for state machine call
private Timer _processCtrlTimer;
private const Int32 ProcessTimerCycleMs = 250;
/// <summary>
/// Avoid waiting for a single process completion.
/// </summary>
public const IProductionProcess DoNotWaitForSingleProcessCompletion = null;
/// <summary>
/// Kick off a parallel execution of the state machine.
/// </summary>
public const Boolean ExecuteInParallel = true;
#endregion --------------------------------------- Variables --------------------------------------------------
#region ------------------------------------------ Class ------------------------------------------------------
/// <summary>
/// Ctor:
/// - Setup process timer for cyclic call of state machine,
/// - Create and setup Cordonel.
/// </summary>
/// <param name="slot">physical slot for this meter</param>
/// <param name="processSateDef"></param>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-04" author="Thomas Wiedebusch">
/// - Always set to production mode to load the registers for this FW and not the latest,
/// - Always RegisterWatchService on,
/// - Always MinMaxCHeck on.
/// </remarks>
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;
}
/// <summary>
/// Initialization of new meter
/// </summary>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Initialize processes to build a list of all production processes.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes
/// </remarks>
private void InitNewMeter()
{
_genesisMeter = new GenesisMeter();
_genesisMeter.SetupFromConfigFile(_slot);
// Important setup to overwrite setting from "Setup" in GTB
_genesisMeter.Configuration.ProductionMode = true;
_genesisMeter.Configuration.UseRegisterWatchService = true;
_genesisMeter.Configuration.UseMinMaxCheck = true;
// Meter batch even if only one meter in use as the meter batch routines can be used
_meterBatch = new MeterBatch();
_meterBatch.AddMeter(_genesisMeter);
// Set lock process to unreachable state delimiter to force initial entry of state machine
_lockedProcessState = ProcessState.StateListDelimiter;
// Create a register restorer
_registerRestorer = new RegisterRestorer(_genesisMeter);
_radioEncryptionKey = new List<String>();
// Containers for production status and standard or special requirement for production
_productionStatus = new GenesisStatus();
_cordonelRequirements = new CordonelRequirements();
// Password container as reminder for repeated test on identical device
_pwdContainer = new MeterPwdDb();
// EOL progress to feed the sentinel
_cordonelEolProgress = new EOLProgressModel();
}
/// <summary>
/// Removal of old meter
/// </summary>
private void RemoveMeter()
{
_genesisMeter?.ClearPassword();
// meter batch dispose disposes all IMeters
_meterBatch?.Dispose();
_meterBatch = null;
_genesisMeter = null;
_registerRestorer = null;
_radioEncryptionKey = null;
_productionStatus = null;
_cordonelRequirements = null;
_pwdContainer = null;
_cordonelEolProgress = null;
}
/// <summary>
/// Dispose.
/// </summary>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void Dispose()
{
_processCtrlTimer?.Dispose();
_processCtrlTimer = null;
_processStateDef = null;
KillProcesses();
RemoveMeter();
}
#endregion --------------------------------------- Class ------------------------------------------------------
#region ------------------------------------------ Events -----------------------------------------------------
/// <summary>
/// Single process state or process list changed event,
/// Use event to force special procedure like uninstalling events
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
/// - Remove process events if process is finished.
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - Remove process events if process is finished or skipped.
/// </remarks>
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);
}
/// <summary>
/// Handler to set a new state for the state machine regarding the entire final-process
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2023-Mar-03" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void 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;
}
}
/// <summary>
/// Process timer
/// </summary>
/// <param name="state"></param>
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void TmrProcessCtrlCycle_Elapsed(Object state)
{
StateMachine();
}
#endregion --------------------------------------- Events -----------------------------------------------------
#region ------------------------------------------ State Machine ----------------------------------------------
/// <summary>
/// State-machine of ProcessController which serves all production processes.
/// This-state machine executes all states defined in the "GenericStateMachineDef" and the individual
/// production-process specific "XxxStateMachineDef".
///
/// A few states with special functions will be caught in advance in this StateMachine
/// </summary>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Generic implementation
/// </remarks>
private void StateMachine()
{// avoid repeated execution of the state machine on unchanged state
if (_stateMachineState == _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 ------------------------------------------------------
/// <summary>
/// Returns the currentGenesis information for display
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public CordonelRequirements GetProductionRequirements()
{
return _cordonelRequirements;
}
/// <summary>
/// Returns the currentGenesis information for display
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public IGenesisMeter GetGenesisMeter()
{
return _genesisMeter;
}
/// <summary>
/// Clears all collected data on new genesisMeter and reminds the detected for next
/// detection and run of this comparison.
/// </summary>
/// <returns>true for new meter</returns>
/// <remarks date="2023-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - The StartProcessSequence has already removed all processes from list to have a clean start,
/// avoid it here again, because the detect is now in the list!
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Handle genesis meter is null as new meter.
/// </remarks>
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
/// - Check _genesisMeter not null before equality with old pcbid.
/// </remarks>
private Boolean CheckForNewMeter()
{
if (_genesisMeter == null ||
(!string.IsNullOrEmpty(_lastPcbId) &&
_genesisMeter?.PcbId != null && !_genesisMeter.PcbId.Equals(_lastPcbId)))
{
// This loop will be entered after the meter has changed, but not on the initial meter
// after program start.
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;
}
/// <summary>
/// Restart final test on first failed or aborted test.
/// </summary>
/// <remarks date="2023-Jul-18" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes and search in Processes
/// </remarks>
/// <remarks date="2024-Oct-21" author="Thomas Wiedebusch">
/// - Leave skipped processes as is.
/// </remarks>
private ProcessState RepeatFailedTests()
{
var state = ProcessState.Idle;
// Search for first test in processes list which failed or aborted due to final test in
// parallel processing forced abort of active processes
foreach (var p in Processes)
{
if (p.SingleProcessState == SingleProcessState.Error ||
p.SingleProcessState == SingleProcessState.Abort ||
p.SingleProcessState == SingleProcessState.Retry)
{
// set the state to the first entry to kick off the state machine here
state = _processStateDef.GetStateOfProcess(p);
break;
}
}
// Put all processes which are not successfully completed to init (Waiting for execution)
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
pp.SingleProcessState != SingleProcessState.Skipped))
{
pp.IdleProcess();
}
return state;
}
/// <summary>
/// Error handler
/// </summary>
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Feb-07" author="Thomas Wiedebusch">
/// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization
/// to re-start with ProductionProcessExecFinalParametrization.
/// </remarks>
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
/// - Removed list of failed production processes
/// </remarks>
/// <remarks date="2025-Jan-28" author="Thomas Wiedebusch">
/// - Kick off exec parametrization if check after reboot failed.
/// </remarks>
private void ProcessErrorHandler(IProductionProcess process)
{
if (process == null)
return;
// Special action if parametrization check failed to repeat it
if (ProcessState.CheckFinalParametrization == _processStateDef.GetStateOfProcess(process))
{
// Signal state change to retry sequence exec config, store config, reboot, check config
var pp = _processStateDef.GetProcessOfState(ProcessState.ExecFinalParametrization);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.ExecStoreConfiguration);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.RebootCordonel);
pp.RetryProcess();
pp = _processStateDef.GetProcessOfState(ProcessState.CheckFinalParametrization);
pp.RetryProcess();
}
// Error signal needed to abort to all processes
AbortProcesses();
}
/// <summary>
/// Calculate the maximum of all processes expected to execute and signal to GUI.
/// </summary>
private void CalcAndPushMaxOverAllProcessSteps()
{
if (_processStateDef == null)
return;
var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses();
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps,
ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps));
}
/// <summary>
/// Calculate all processes which are completed or skipped and signal to GUI.
/// </summary>
private void CalcAndPushOverAllProgress()
{
if (Processes == null)
return;
// check the readiness of all processes in the loop
var _overallReadyProcesses = 0;
foreach (var p in Processes)
{
if (p.SingleProcessState == SingleProcessState.Succeeded ||
p.SingleProcessState == SingleProcessState.Skipped)
_overallReadyProcesses++;
}
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_overallReadyProcesses,
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
}
/// <summary>
/// Initialize all processes which are connected to a production process.
/// </summary>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitProcesses()
{
if (Processes == null)
Processes = new List<IProductionProcess>();
// 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);
}
}
}
/// <summary>
/// Production process kick off:
/// - Sets single process state to waiting within the process init,
/// - Adds new process to process list,
/// - Optional waits for single process to complete before process is going to be started,
/// - Optional waits for all previously assigned and started processes to complete before process is going to
/// be started,
/// - Assigns slot, productionStatus, programmingParameters and genesisMeter to process,
/// - Adds event handlers for process information,
/// - Starts process.
/// </summary>
/// <param name="processStateStruct"></param>
/// <returns>actual state</returns>
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Skipped process due to one of the previous processes failed
/// </remarks>
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
/// - Restructured to avoid process start if processes to wait for are not completed
/// </remarks>
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
/// - Changed from programming parameters to register restorer
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Changed setup process to start process being able to create a process list in advance.
/// </remarks>
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
/// - Changed setup process to start process being able to create a process list in advance.
/// </remarks>
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
/// - Feed with processStateStruct.
/// </remarks>
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);
}
/// <summary>
/// Check all processes for error or abort request
/// </summary>
/// <returns>true if abort required</returns>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Cancellation token.
/// </remarks>
private Boolean AnyProcessRequiresAbortion()
{
return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error ||
pp.SingleProcessState == SingleProcessState.Abort ||
_cancellationToken.IsCancellationRequested);
}
/// <summary>
/// Check process if in status waiting or running (incomplete)
/// </summary>
/// <param name="pp">production process</param>
/// <returns>true if process to wait for execution or exit running state</returns>
private Boolean WaitForProcess(IProductionProcess pp)
{
return pp.SingleProcessState == SingleProcessState.Waiting ||
pp.SingleProcessState == SingleProcessState.Running;
}
/// <summary>
/// Install process events
/// </summary>
/// <param name="process"></param>
private void AddProcessEvents(IProductionProcess process)
{
if (process == null)
return;
process.OnProcessProgressChanged += ProcessProgress_Handler;
process.OnProcessLogRequest += ProcessLog_Handler;
process.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler;
process.OnOverallProcessStateChanged += OverallProcessStateChanged_Handler;
}
/// <summary>
/// Remove process events
/// </summary>
/// <param name="process"></param>
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;
}
/// <summary>
/// Get the actual process state of the state machine
/// </summary>
/// <returns>actual process state</returns>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private ProcessState GetProcessState()
{
return _stateMachineState;
}
/// <summary>
/// Set the new process state of the state machine
/// </summary>
/// <returns>true, if process state is set</returns>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean SetProcessState(ProcessState newProcessState)
{
if (newProcessState != _stateMachineState)
{
_stateMachineState = newProcessState;
return true;
}
return false;
}
/// <summary>
/// Remove all processes from the <see cref="Processes"/> list:
/// - Signal change to GUI,
/// - Remove process events all,
/// - Set state to abort.
/// </summary>
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
/// - Abort register access.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - Cancellation token source.
/// </remarks>
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();
}
}
/// <summary>
/// Abort all processes if one previous process failed, but leave the <see cref="Processes"/> list intact.
/// Leave all events for those which are already running intact to force a clean stop.
/// </summary>
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void AbortProcesses()
{
if (Processes == null || Processes.Count == 0)
return;
// Skip all processes which are not succeeded, erroneous or skipped to leave the status intact
// and request abort for all others
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
pp.SingleProcessState != SingleProcessState.Error &&
pp.SingleProcessState != SingleProcessState.Skipped &&
pp.SingleProcessState != SingleProcessState.Retry))
{
pp.AbortProcess();
}
}
#endregion --------------------------------------- Tools ------------------------------------------------------
}
}
@@ -0,0 +1,8 @@
using System;
namespace ProductionUiCordonel.ProductionProcesses
{
public class ProcessStateChangedArgs : EventArgs
{
}
}
@@ -0,0 +1,108 @@
using System;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
{
/// <summary>
/// Concatenation between test state and text feeding the state machine with next state.
/// </summary>
public struct ProcessStateStruct
{
/// <summary>
/// Assign process state information
/// </summary>
/// <param name="productionProcess">production process, set to null for "invisible" states</param>
/// <param name="processState">assigned state for call of this process used by the state machine</param>
/// <param name="processInfo">name for process to display a message in the GUI</param>
/// <param name="productionProcessNo">set a unique number to sort the table of production processes,
/// set to null for all hidden processes which are not a production process</param>
/// <param name="nextStateOnSuccess">BaseProductionProcess.DoNotChangeStateAfterCompletion will not
/// change the state automatically</param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <param name="kickOffParallelState">start a parallel process</param>
/// <param name="waitForSingleProcess">wait for single processes to complete before execution</param>
/// <param name="waitForAllProcesses">wait for all processes to complete before execution</param>
/// <param name="startAlways">start even if abort is required</param>
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Apr-14" author="Thomas Wiedebusch">
/// - Extended to steer state machine with this struct.
/// </remarks>
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)
{
ProductionProcess = productionProcess;
ProcessState = processState;
ProcessInfo = processInfo;
ProductionProcessNo = productionProcessNo;
NextStateOnSuccess = nextStateOnSuccess;
ErrorExitState = errorExitState;
BreakExitState = breakExitState;
KickOffParallelState = kickOffParallelState;
WaitForSingleProcess = waitForSingleProcess;
WaitForAllProcesses = waitForAllProcesses;
StartAlways = startAlways;
}
/// <summary>
/// Start a parallel process
/// </summary>
public ProcessState? KickOffParallelState { get; }
/// <summary>
/// The state of the final test
/// </summary>
public IProductionProcess ProductionProcess { get; }
/// <summary>
/// The state of the final test
/// </summary>
public ProcessState ProcessState { get; }
/// <summary>
/// The info message of the state
/// </summary>
public String ProcessInfo { get; }
/// <summary>
/// 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
/// </summary>
public UInt32? ProductionProcessNo { get; }
/// <summary>
/// Next state required after successfully execution
/// </summary>
public ProcessState? NextStateOnSuccess { get; }
/// <summary>
/// Special state used for error or for the second output of the process
/// </summary>
public ProcessState ErrorExitState { get; }
/// <summary>
/// Special state required on user break
/// </summary>
public ProcessState BreakExitState { get; }
/// <summary>
/// Wait for a single process named here to take the result into account
/// </summary>
public ProcessState? WaitForSingleProcess { get; }
/// <summary>
/// Wait until all preceding processes are finished with success
/// </summary>
public Boolean WaitForAllProcesses { get; }
/// <summary>
/// Sate which has to be active always even if a predecessor failed (e.g. error state)
/// </summary>
public Boolean StartAlways { get; }
}
}
@@ -0,0 +1,616 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4928FC37-15EC-4872-8514-EA4ADCC4139D}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Xylem.Common.Production.ProductionUiCordonel</RootNamespace>
<AssemblyName>ProductionUiCordonel</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>C:\Users\drabesch_ro\Documents\cordonelProduction\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<AutorunEnabled>true</AutorunEnabled>
<ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>7.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>B5ADA7D4C91EA2D22928C5D42E39A4BAD8EDBABB</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>ProductionUiCordonel_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<StartupObject>Xylem.Common.Production.ProductionUiCordonel.App</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.0</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.0</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.9.0.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="Cyotek.Windows.Forms.ColorPicker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=21b3b44f30e0c5f0, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Cyotek.Windows.Forms.ColorPicker.dll</HintPath>
</Reference>
<Reference Include="DotNetZip, Version=1.16.0.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\DotNetZip.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.Abstractions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Binder, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.Binder.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.CommandLine, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.CommandLine.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.CommandLine.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.EnvironmentVariables.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.FileExtensions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.FileExtensions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.FileExtensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Json, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.Json.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Json.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.UserSecrets, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.DependencyInjection.3.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.FileProviders.Physical, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.FileProviders.Physical.3.0.0\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Physical.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.FileSystemGlobbing, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.FileSystemGlobbing.3.0.0\lib\netstandard2.0\Microsoft.Extensions.FileSystemGlobbing.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Hosting, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Hosting.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Hosting.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Hosting.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Hosting.Abstractions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Configuration.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Configuration.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Console, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Console.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Console.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Debug, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Debug.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Debug.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.EventLog, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.EventLog.3.0.0\lib\net461\Microsoft.Extensions.Logging.EventLog.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.EventSource, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Logging.EventSource.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.EventSource.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Options, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Options.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Options.ConfigurationExtensions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.ConfigurationExtensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Primitives, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Extensions.Primitives.3.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Core, Version=8.0.5.0, Culture=neutral, PublicKeyToken=8afb38e9204fc0a9, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Microsoft.WindowsAPICodePack.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.ExtendedLinguisticServices, Version=8.0.4.0, Culture=neutral, PublicKeyToken=8afb38e9204fc0a9, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Microsoft.WindowsAPICodePack.ExtendedLinguisticServices.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Sensors, Version=8.0.4.0, Culture=neutral, PublicKeyToken=8afb38e9204fc0a9, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Microsoft.WindowsAPICodePack.Sensors.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=8.0.5.0, Culture=neutral, PublicKeyToken=8afb38e9204fc0a9, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.ShellExtensions, Version=8.0.4.0, Culture=neutral, PublicKeyToken=8afb38e9204fc0a9, processorArchitecture=MSIL">
<HintPath>..\..\packages\Krypton.Toolkit.Suite.Extended.Ultimate.LTS.85.24.10.288\lib\net481\Microsoft.WindowsAPICodePack.ShellExtensions.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\..\packages\NLog.5.3.2\lib\net46\NLog.dll</HintPath>
</Reference>
<Reference Include="SIRTCOM">
<HintPath>.\SIRTCOM.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Diagnostics.EventLog, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.EventLog.4.6.0\lib\net461\System.Diagnostics.EventLog.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Context" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal.Windows, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Principal.Windows.4.6.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Encodings.Web.4.6.0\lib\netstandard2.0\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Json.4.6.0\lib\net461\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsFormsIntegration" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="..\..\.shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="ApplicationSettings.cs" />
<Compile Include="BarcodeInput\Listener.cs" />
<Compile Include="ctls\ctlMultiModeMarriage.xaml.cs">
<DependentUpon>ctlMultiModeMarriage.xaml</DependentUpon>
</Compile>
<Compile Include="ctls\ctlMultiModePicking.xaml.cs">
<DependentUpon>ctlMultiModePicking.xaml</DependentUpon>
</Compile>
<Compile Include="ctls\ctlMultiModePressure.xaml.cs">
<DependentUpon>ctlMultiModePressure.xaml</DependentUpon>
</Compile>
<Compile Include="Model\EOLHandler.cs" />
<Compile Include="ProductionProcesses\Enum\PickingProcessState.cs" />
<Compile Include="ProductionProcesses\EolProcesses\VisualInspection.cs" />
<Compile Include="ProductionProcesses\IProcessStateDef.cs" />
<Compile Include="ProductionProcesses\BaseProcessStatesDef.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\ErrorHandler.cs" />
<Compile Include="ProductionProcesses\EolProcesses\ExecStoreConfiguration.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\GetSerialNumberAndRadioAddress.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\DetectCordonel.cs" />
<Compile Include="ProductionProcesses\EolProcesses\ExecRebootCordonel.cs" />
<Compile Include="ProductionProcesses\EolProcesses\PrepareShipping.cs" />
<Compile Include="ProductionProcesses\Enum\DialogResult.cs" />
<Compile Include="ProductionProcesses\Enum\ImgState.cs" />
<Compile Include="ProductionProcesses\Enum\ProcessState.cs" />
<Compile Include="ProductionProcesses\EventArgs\SingleProcessStateArgs.cs" />
<Compile Include="ProductionProcesses\EolProcesses\EolProcessStatesDef.cs" />
<Compile Include="ProductionProcesses\PickingProcesses\CheckProductionState.cs" />
<Compile Include="ProductionProcesses\PickingProcesses\PickingProcessStatesDef.cs" />
<Compile Include="ProductionProcesses\ProcessStateStruct.cs" />
<Compile Include="Data\PickingResultData.cs" />
<Compile Include="Data\PressureResultDataNotifyable.cs" />
<Compile Include="Event\StringArgs.cs" />
<Compile Include="Model\MultiModeMasterViewModel.cs" />
<Compile Include="Model\PressureMultiModeViewModel.cs" />
<Compile Include="Model\MultiModeChildViewModel.cs" />
<Compile Include="Model\MarriageMultiModeViewModel.cs" />
<Compile Include="Model\PickingMultiModeViewModel.cs" />
<Compile Include="Model\PickingViewModel.cs" />
<Compile Include="Model\IMultiModeChildViewModel.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\CheckSerialNumber.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\CheckOrderNumber.cs" />
<Compile Include="ProductionProcesses\EolProcesses\CheckAttachments.cs" />
<Compile Include="ProductionProcesses\EolProcesses\CheckProductionState.cs" />
<Compile Include="ProductionProcesses\EolProcesses\CheckFinalParametrization.cs" />
<Compile Include="ProductionProcesses\EolProcesses\GetFinalParametrization.cs" />
<Compile Include="ProductionProcesses\EolProcesses\ExecFinalParametrization.cs" />
<Compile Include="ProductionProcesses\EolProcesses\CheckRadio.cs" />
<Compile Include="ProductionProcesses\GenericProcesses\ConnectCordonel.cs" />
<Compile Include="ProductionProcesses\EolProcesses\ExecPasswordFile.cs" />
<Compile Include="ProductionProcesses\BaseProductionProcess.cs" />
<Compile Include="ProductionProcesses\Enum\SingleProcessState.cs" />
<Compile Include="ProductionProcesses\IProductionProcess.cs" />
<Compile Include="ProductionProcesses\ProcessController.cs" />
<Compile Include="ProductionProcesses\EventArgs\ProcessStateArgs.cs" />
<Compile Include="ProductionProcesses\EventArgs\ProcessLogArgs.cs" />
<Compile Include="ProductionProcesses\EventArgs\ProcessProgressArgs.cs" />
<Compile Include="Properties\Resources.de.Designer.cs">
<DependentUpon>Resources.de.resx</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Properties\Resources.de1.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.de.resx</DependentUpon>
</Compile>
<Compile Include="UserAccessWindow.xaml.cs">
<DependentUpon>UserAccessWindow.xaml</DependentUpon>
</Compile>
<Compile Include="UserControls\IUserControl.cs" />
<Compile Include="UserControls\UcBarcodeInput.xaml.cs">
<DependentUpon>UcBarcodeInput.xaml</DependentUpon>
</Compile>
<Compile Include="UserControls\UcDialog.xaml.cs">
<DependentUpon>UcDialog.xaml</DependentUpon>
</Compile>
<Compile Include="UserControls\UcRadioCheck.xaml.cs">
<DependentUpon>UcRadioCheck.xaml</DependentUpon>
</Compile>
<Compile Include="UserControls\UcPlain.xaml.cs">
<DependentUpon>UcPlain.xaml</DependentUpon>
</Compile>
<Compile Include="UserControls\UcPasswordFile.xaml.cs">
<DependentUpon>UcPasswordFile.xaml</DependentUpon>
</Compile>
<Page Include="ctls\ctlMultiModeMarriage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ctls\ctlMultiModePicking.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ctls\ctlMultiModePressure.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ProductionWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="ProductionWindow.xaml.cs">
<DependentUpon>ProductionWindow.xaml</DependentUpon>
</Compile>
<Page Include="UserAccessWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UserControls\UcBarcodeInput.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="UserControls\UcDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="UserControls\UcRadioCheck.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="UserControls\UcPlain.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UserControls\UcPasswordFile.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Resource Include="TextFile1.txt" />
<EmbeddedResource Include="Properties\Resources.de.resx">
<Generator>ResXFileCodeGenerator</Generator>
<SubType>Designer</SubType>
<LastGenOutput>Resources.de1.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Content Include="NLog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="Docu\Handbuch Cordonel Produktionsfreigaben.docx" />
<None Include="Docu\Handbuch Cordonel Prouktionsfreigaben.pdf" />
<None Include="Docu\Handbuch Cordonel Versandfertigmachen %28VFM%29.docx" />
<None Include="Docu\Handbuch Cordonel Versandfertigmachen %28VFM%29.pdf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Docu\ProductionUiCordonel.qea" />
<None Include="Docu\TBF_COM_EOL.pptx" />
<None Include="Docu\TBF_COM_EOLd.pptx" />
<None Include="Docu\TBF_COM_EOLe.pptx" />
<None Include="Docu\TBF_COM_EOL_SDDd.vsd" />
<None Include="Docu\TBF_COM_EOL_SDDe.vsd" />
<None Include="Docu\TBF_COM_EOL_SRSe.docx" />
<None Include="Docu\VFM 2.6.10 Code Review.xlsx" />
<None Include="NLog.xsd">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\CommonCore.Configuration\CommonCore.Configuration.csproj">
<Project>{9EB1F659-6F73-4F65-BCAB-4114FD94F5FC}</Project>
<Name>CommonCore.Configuration</Name>
</ProjectReference>
<ProjectReference Include="..\..\CommonCore\CommonCore.csproj">
<Project>{1B02C79E-0B19-43E2-8F6B-71EF0C786C97}</Project>
<Name>CommonCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\Interfaces\Ports\PortCore\PortCore.csproj">
<Project>{2E139F63-B6FE-4EA9-A098-85364FE9B26C}</Project>
<Name>PortCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\Applications\Applications.csproj">
<Project>{315E94B8-B82F-4B25-B5BD-297DF3BC1D54}</Project>
<Name>Applications</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\GenesisConfig\GenesisConfig.csproj">
<Project>{F013A56B-7BE6-4852-9CD8-B971CD1ADD0C}</Project>
<Name>GenesisConfig</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\GenesisCore\GenesisCore.csproj">
<Project>{04201E95-90A0-4B73-A72F-379739B2A3AE}</Project>
<Name>GenesisCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\GenesisFile\GenesisFile.csproj">
<Project>{2C6378C6-6BA1-4CEE-8992-8404D8369A90}</Project>
<Name>GenesisFile</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\GenesisPwd\GenesisPwd.csproj">
<Project>{AC434B18-4D7B-49BA-9AD5-0527C79E8297}</Project>
<Name>GenesisPwd</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\GenesisStatus\GenesisStatus.csproj">
<Project>{0F646872-D8DC-4EE5-989F-D4C73F0ACE3F}</Project>
<Name>GenesisStatus</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\Genesis\Registers\Registers.csproj">
<Project>{F4AAF7E6-7333-4284-B735-E32DEB076C64}</Project>
<Name>Registers</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\WaterMeterCore\WaterMeterCore.csproj">
<Project>{8203ABB8-38E5-472E-A742-33F38B7A74A6}</Project>
<Name>WaterMeterCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\Hardware\WaterMeter\WaterMeterCore\WaterMeterRegisters\WaterMeterRegisters.csproj">
<Project>{26AFCF1E-1287-48B8-A506-32B1E8A0DD0E}</Project>
<Name>WaterMeterRegisters</Name>
</ProjectReference>
<ProjectReference Include="..\..\LaaPackages\LaaPackages.Features.Cordonel\LaaPackages.Features.Cordonel.csproj">
<Project>{8BEFD3AC-BB21-4B62-8D9A-CF8CBFD439EB}</Project>
<Name>LaaPackages.Features.Cordonel</Name>
</ProjectReference>
<ProjectReference Include="..\..\Logic.PrdoctionToProductMapper\Logic.ProductionToProductMapper.csproj">
<Project>{D0C859D9-D55E-4E85-AC50-0FF31B1CE50D}</Project>
<Name>Logic.ProductionToProductMapper</Name>
</ProjectReference>
<ProjectReference Include="..\..\Logic\ProductionOrderCore\ProductionOrderCore.csproj">
<Project>{6D2777BB-7A88-466D-A49B-3266F9BF0160}</Project>
<Name>ProductionOrderCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\SoftwareAccessHelper\SoftwareAccessHelper.csproj">
<Project>{A33E3C6D-EAAE-4A20-A0EE-F9E6922FD029}</Project>
<Name>SoftwareAccessHelper</Name>
</ProjectReference>
<ProjectReference Include="..\..\Utils\Logging\Logging.csproj">
<Project>{4B88B0D3-E791-4774-9521-EABEACDC420E}</Project>
<Name>Logging</Name>
</ProjectReference>
<ProjectReference Include="..\..\Utils\ProcessExec\ProcessExec.csproj">
<Project>{2fd60cf1-45cd-4060-b8e5-4f39fba7f1cb}</Project>
<Name>ProcessExec</Name>
</ProjectReference>
<ProjectReference Include="..\..\Utils\UserAccessCtrl\UserAccessCtrl.csproj">
<Project>{2d55bbd1-5373-484c-8781-c6eceafc2a90}</Project>
<Name>UserAccessCtrl</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\iconCheck.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\iconWarn.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\iconWating.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.6 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Refresh_16x.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\Connect_16x.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\ConnectPlugged_16x.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<None Include="Resources\ClearMatrix.bmp" />
<None Include="Resources\CLEARMAtrix.JPG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Resource Include="Resources\SensusLogoXylem.JPG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\Cordonel With Display small.JPG">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Content Include="SIRT_Driver_Win32_PC.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.NETFramework.ReferenceAssemblies.net481.1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net481.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NETFramework.ReferenceAssemblies.net481.1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net481.targets'))" />
<Error Condition="!Exists('..\..\packages\JetBrains.NETFramework.ReferenceAssemblies.net35.1.0.1\build\JetBrains.NETFramework.ReferenceAssemblies.net35.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\JetBrains.NETFramework.ReferenceAssemblies.net35.1.0.1\build\JetBrains.NETFramework.ReferenceAssemblies.net35.targets'))" />
</Target>
<Import Project="..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('..\..\packages\Microsoft.Extensions.Configuration.UserSecrets.3.0.0\build\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.targets')" />
<Import Project="..\..\packages\Microsoft.NETFramework.ReferenceAssemblies.net481.1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net481.targets" Condition="Exists('..\..\packages\Microsoft.NETFramework.ReferenceAssemblies.net481.1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net481.targets')" />
<Import Project="..\..\packages\JetBrains.NETFramework.ReferenceAssemblies.net35.1.0.1\build\JetBrains.NETFramework.ReferenceAssemblies.net35.targets" Condition="Exists('..\..\packages\JetBrains.NETFramework.ReferenceAssemblies.net35.1.0.1\build\JetBrains.NETFramework.ReferenceAssemblies.net35.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp110</s:String></wpf:ResourceDictionary>
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<HiddenComponentWarnings />
</Settings>
</ProjectConfiguration>
@@ -0,0 +1,155 @@
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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.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">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2,2,2,2">
<!--Tool Panel-->
<!--ToolBarTray DockPanel.Dock="Top" Visibility="Visible" Background="#FFF0F0F0" HorizontalAlignment="Stretch" -->
<Menu DockPanel.Dock="Top" Visibility="Visible" Background="#FFF0F0F0" HorizontalAlignment="Stretch">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel HorizontalAlignment="Stretch"></DockPanel>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem x:Name="optionsMenu" HorizontalAlignment="Right" Height="30" Width="60" Header="_Options" >
<MenuItem x:Name="optionsMenuLogin" Header="_Login" HorizontalAlignment="Left" Click="OnLogin_Click"/>
<MenuItem x:Name="optionsMenuLogout" Header="L_ogout" HorizontalAlignment="Left" Click="OnLogout_Click"/>
<MenuItem x:Name="optionsMenuChangePassword" Header="Change Password" HorizontalAlignment="Left" Click="OnChangePassword_Click"/>
</MenuItem>
<Label Content="Slot: " Margin="2,2,2,2" Visibility="Hidden"/>
<ComboBox x:Name="cbxSlotBox" Margin="2,2,2,2" SelectionChanged="CbxSlotBox_SelectionChanged" Visibility="Hidden"/>
<Label x:Name="lblApprovalText" Margin="110,1,1,1" Content="SPECIAL APPROVAL - Id.Ver: 10.07" Foreground="#FFFB0808" FontSize="16" MouseDown="LblApprovalText_Click" HorizontalAlignment="Center"/>
<MenuItem x:Name="hlpMenu" HorizontalAlignment="Right" Height="30" Width="40" FontSize="12" Header="Help" Click="HlpMenu_Click" />
<!--Special approval / requirement-->
</Menu>
<!--/-->
<!--Status Panel-->
<StatusBar DockPanel.Dock="Bottom" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2,2,2,2">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="0" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Label Content="PcbID:" Margin="2,2,0,2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StatusBarItem>
<StatusBarItem Grid.Column="1" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Label x:Name="lblPcbIdValue" Content="-" Margin="2,2,2,2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StatusBarItem>
<Separator Grid.Column="2" />
<StatusBarItem x:Name="lblOperator" Grid.Column="4" Margin="-1,1,-109,3" HorizontalAlignment="Center" VerticalAlignment="Stretch" />
<Separator Grid.Column="7" />
<StatusBarItem Grid.Column="8" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock x:Name="lblTimeText" Text="Elapsed Time [min:s]:" Margin="2,2,2,2" HorizontalAlignment="Right" VerticalAlignment="Stretch" />
</StatusBarItem>
<StatusBarItem Grid.Column="9" Margin="0,0,0,0" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="lblTimeValue" Content="0:00" Margin="2,2,2,2" HorizontalAlignment="Right" VerticalAlignment="Stretch" />
</StatusBarItem>
</StatusBar>
<!--Grid Design-->
<Grid Background="AliceBlue" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2,2,2,2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="17"/>
<ColumnDefinition Width="113"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<!--Cordonel information-->
<Label x:Name="lblSerialNumberTxt" Content="SerialNumber:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="0" Grid.Column="2"/>
<Label x:Name="lblOrderNumberText" Content="OrderNumber:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="1" Grid.Column="2"/>
<Label Content="Region:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="2" Grid.Column="2"/>
<Label x:Name="lblRadioAddressText" Content="RadioAddress:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="3" Grid.Column="2"/>
<Label Content="Firmware:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="0" Grid.Column="5"/>
<Label x:Name="lblLutText" Content="LUT CRC:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="1" Grid.Column="5"/>
<Label x:Name="lblMeterSizeText" Content="Meter Size:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="2" Grid.Column="5"/>
<Label Content="" x:Name="lblSerialNumberValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="0" Grid.Column="3"/>
<Label Content="" x:Name="lblOrderNumberValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="1" Grid.Column="3"/>
<Label Content="" x:Name="lblRegionValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="2" Grid.Column="3"/>
<Label Content="" x:Name="lblRadioAddressValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="3" Grid.Column="3"/>
<Label Content="" x:Name="lblFwValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="0" Grid.Column="6"/>
<Label Content="" x:Name="lblLutValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="1" Grid.Column="6"/>
<Label Content="" x:Name="lblMeterSizeValue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="2" Grid.Column="6"/>
<!--Pictures-->
<Border Grid.Row="0" Grid.RowSpan="4" Grid.Column="0" Margin="2,2,2,2" BorderThickness="1" BorderBrush="Black" Grid.ColumnSpan="2">
<Image Margin="0,0,-1,-1" Source="/Resources/Cordonel With Display small.JPG" Stretch="Fill" />
</Border>
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="9" Grid.ColumnSpan="2" Margin="2,2,2,2" BorderThickness="1" BorderBrush="Black" >
<Image Margin="0,0,-1,-1" Source="/Resources/SensusLogoXylem.JPG" Stretch="Fill" />
</Border>
<!--Process Information-->
<DataGrid x:Name="dgTotalProcess" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="4" Grid.RowSpan="11" Grid.Column="0" Grid.ColumnSpan="4" ItemsSource="{Binding}"
AutoGenerateColumns="True" SelectionMode="Single" IsReadOnly="True" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserResizeRows="False" CanUserSortColumns="False" IsEnabled="True" />
<StackPanel x:Name="dpSubProcess" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="4" Grid.RowSpan="11" Grid.Column="5" Grid.ColumnSpan="3" />
<GroupBox Header="Info" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="15" Grid.RowSpan="4" Grid.Column="0" Grid.ColumnSpan="11" >
<RichTextBox x:Name="rtbLog" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</GroupBox>
<!--Progress Bars and Info-->
<ProgressBar x:Name="pbSubProgress" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,4,2,4" Grid.Row="19" Grid.Column="2" Grid.ColumnSpan="8" />
<ProgressBar x:Name="pbTotalProgress" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,4,2,4" Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="8" />
<Label x:Name="lblSubProgressValue" HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="0,0,2,0" Grid.Row="19" Grid.Column="10" Content="0,0 %"/>
<Label x:Name="lblTotalProgressValue" HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="0,0,2,0" Grid.Row="20" Grid.Column="10" Content="0,0 %"/>
<Label x:Name="lblSingleProcessText" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="19" Grid.Column="0" Content="Single Process:" Grid.ColumnSpan="2" />
<Label x:Name="lblTotalProcessText" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,2" Grid.Row="20" Grid.Column="0" Content="Total Process:" Grid.ColumnSpan="2" />
<!--Buttons-->
<Button x:Name="btnStart" HorizontalAlignment="Stretch" Margin="2,4,2,4" VerticalAlignment="Stretch" Grid.Column="9" Grid.ColumnSpan="2" Grid.Row="2" Grid.RowSpan="2" IsEnabled="false" Click="BtnStart_Click" Content="Start" />
<Button x:Name="btnStop" HorizontalAlignment="Stretch" Margin="2,4,2,4" VerticalAlignment="Stretch" Grid.Column="9" Grid.ColumnSpan="2" Grid.Row="4" Grid.RowSpan="2" IsEnabled="false" Click="BtnStop_Click" Content="Stop" />
</Grid>
</DockPanel>
</Window>
@@ -0,0 +1,976 @@
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.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
{
/// <summary>
/// Interaction logic for FinalTest.xaml
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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<SlotConfig[]>(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}: ?");
}
}
/// <summary>
/// Shows the user access window with login form or password change form.
/// </summary>
/// <param name="visible"></param>
/// <param name="control">Windows forms control</param>
/// <param name="headerTxt"></param>
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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);
}
}
/// <summary>
/// Close event handler of the user access window with login form or password change form.
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}));
}
/// <summary>
/// Update the elapsed time as information
/// </summary>
/// <returns>information attached to process depending on region</returns>
/// <remarks date="2024-Jul-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void SetTimeDisplay()
{
var time = DateTimeOffset.UtcNow;
var timeSpan = time - _startTime;
UpdateContentControl(lblTimeValue, $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}");
}
/// <summary>
/// Common routine for state change:
/// - Log state change,
/// - Execute stop procedure on error.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Aug-30" author="Thomas Wiedebusch">
/// - Extended.
/// </remarks>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Extended.
/// </remarks>
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);
}
}
/// <summary>
/// Enable UI elements
/// </summary>
/// <param name="elm"></param>
/// <param name="isEnabled"></param>
/// <param name="isVisible"></param>
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;
}));
}
/// <summary>
/// Fill logging window with information
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-06" author="Thomas Wiedebusch">
/// - Changed to rich text edit with colored output.
/// </remarks>
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
/// - Avoid output of repeated text or useless empty text.
/// </remarks>
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)
{
}
}
}
));
}
/// <summary>
/// Set values for progress bar, maximum and actual for single process and overall process.
/// - Limit values to 0.0..100.0.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-07-18" author="Thomas Wiedebusch">
/// - Introduced multi line text with center alignment.
/// </remarks>
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} %");
}
));
}
}
/// <summary>
/// Output message to control without access violation.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
/// - Introduced multi line text with center alignment.
/// </remarks>
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
};
}
));
}
/// <summary>
/// Status grid update for all single processes
/// </summary>
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;
}
));
}
/// <summary>
/// Dispatch new user control to main screen
/// </summary>
/// <param name="uc"></param>
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
/// <summary>
/// Handle timing depending updates.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-07-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void TmrProgressUpdate_Tick(Object sender, EventArgs e)
{
SetTimeDisplay();
}
#endregion
/// <summary>
/// Update of status grid with information returned by user control.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
/// - Activate new user control on start of process.
/// </remarks>
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);
}
/// <summary>
/// Set labels of device information and order number
/// </summary>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-07-15" author="Thomas Wiedebusch">
/// - Additional info of Cordonel.
/// </remarks>
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();
// 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);
}
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);
}
/// <summary>
/// Logging a file with all process information, building a compressed report for printout.
/// </summary>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Reduced printout.
/// </remarks>
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);
}
}
}
/// <summary>
/// Stop button as user interaction to kill the finalization process
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnStop_Click(Object sender, RoutedEventArgs e)
{
//set the process state to the state machine
_processCtrl?.SetProcessState(ProcessState.Stop);
}
/// <summary>
/// Open requirement in Web-Browser.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
}
}
@@ -0,0 +1,977 @@
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
{
/// <summary>
/// Interaction logic for FinalTest.xaml
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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<SlotConfig[]>(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}: ?");
}
}
/// <summary>
/// Shows the user access window with login form or password change form.
/// </summary>
/// <param name="visible"></param>
/// <param name="control">Windows forms control</param>
/// <param name="headerTxt"></param>
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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);
}
}
/// <summary>
/// Close event handler of the user access window with login form or password change form.
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}));
}
/// <summary>
/// Update the elapsed time as information
/// </summary>
/// <returns>information attached to process depending on region</returns>
/// <remarks date="2024-Jul-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void SetTimeDisplay()
{
var time = DateTimeOffset.UtcNow;
var timeSpan = time - _startTime;
UpdateContentControl(lblTimeValue, $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}");
}
/// <summary>
/// Common routine for state change:
/// - Log state change,
/// - Execute stop procedure on error.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Aug-30" author="Thomas Wiedebusch">
/// - Extended.
/// </remarks>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Extended.
/// </remarks>
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);
}
}
/// <summary>
/// Enable UI elements
/// </summary>
/// <param name="elm"></param>
/// <param name="isEnabled"></param>
/// <param name="isVisible"></param>
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;
}));
}
/// <summary>
/// Fill logging window with information
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-06" author="Thomas Wiedebusch">
/// - Changed to rich text edit with colored output.
/// </remarks>
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
/// - Avoid output of repeated text or useless empty text.
/// </remarks>
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)
{
}
}
}
));
}
/// <summary>
/// Set values for progress bar, maximum and actual for single process and overall process.
/// - Limit values to 0.0..100.0.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-07-18" author="Thomas Wiedebusch">
/// - Introduced multi line text with center alignment.
/// </remarks>
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} %");
}
));
}
}
/// <summary>
/// Output message to control without access violation.
/// </summary>
/// <returns></returns>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
/// - Introduced multi line text with center alignment.
/// </remarks>
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
};
}
));
}
/// <summary>
/// Status grid update for all single processes
/// </summary>
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;
}
));
}
/// <summary>
/// Dispatch new user control to main screen
/// </summary>
/// <param name="uc"></param>
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
/// <summary>
/// Handle timing depending updates.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-07-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void TmrProgressUpdate_Tick(Object sender, EventArgs e)
{
SetTimeDisplay();
}
#endregion
/// <summary>
/// Update of status grid with information returned by user control.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
/// - Activate new user control on start of process.
/// </remarks>
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);
}
/// <summary>
/// Set labels of device information and order number
/// </summary>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-07-15" author="Thomas Wiedebusch">
/// - Additional info of Cordonel.
/// </remarks>
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();
// 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);
}
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);
}
/// <summary>
/// Logging a file with all process information, building a compressed report for printout.
/// </summary>
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
/// - Reduced printout.
/// </remarks>
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);
}
}
}
/// <summary>
/// Stop button as user interaction to kill the finalization process
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnStop_Click(Object sender, RoutedEventArgs e)
{
//set the process state to the state machine
_processCtrl?.SetProcessState(ProcessState.Stop);
}
/// <summary>
/// Open requirement in Web-Browser.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
}
}
@@ -0,0 +1,51 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ProductionUiCordonel")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Xylem")]
[assembly: AssemblyProduct("ProductionUiCordonel")]
[assembly: AssemblyCopyright("Copyright © 2018..2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,803 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human-readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="ConnectPlugged_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\ConnectPlugged_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Connect_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Connect_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconCheck" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconCheck.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconWarn" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconWarn.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconWating" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconWating.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Refresh_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Refresh_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="CLEARMAtrix" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\CLEARMAtrix.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="StrErrorMsgMissingNetworkConnetction" xml:space="preserve">
<value>Keine Verbindung zum Netzwerk. Die Software wird beendet!</value>
</data>
<data name="StrErrorMsgMissingConfigurationFile" xml:space="preserve">
<value>Konfigurationsdatei nicht gefunden. Slot muß mit der GenesisToolBox eingestellt werden! Die Software wird beendet!</value>
</data>
<data name="StrErrorMsgMissingComPort" xml:space="preserve">
<value>Kommunikationsanschluß ist belegt oder nicht vorhanden! Die Software wird beendet!</value>
</data>
<data name="StrStartMessageSwLicenseExpired" xml:space="preserve">
<value>Neue Cordonel VFM Software verfügbar! Die Software wird beendet! Diese version:</value>
</data>
<data name="StrStateIdle" xml:space="preserve">
<value>Warte</value>
</data>
<data name="StrStateRetry" xml:space="preserve">
<value>Wiederhole</value>
</data>
<data name="StrStateStartInitial" xml:space="preserve">
<value>Neustart</value>
</data>
<data name="StrProcessStateDone" xml:space="preserve">
<value>Erfolg</value>
</data>
<data name="StrProcessStateSkipped" xml:space="preserve">
<value>Übersprungen</value>
</data>
<data name="StrProcessStateError" xml:space="preserve">
<value>FEHLER</value>
</data>
<data name="StrProcessStateRunning" xml:space="preserve">
<value>Läuft</value>
</data>
<data name="StrProcessStateWaiting" xml:space="preserve">
<value>Warte</value>
</data>
<data name="StrSuccessMsgPasswordFileWritten" xml:space="preserve">
<value>Erfolg: Passwortdatei erfolgreich geschrieben</value>
</data>
<data name="StrStateRepeatFinalTest" xml:space="preserve">
<value>Wiederholung Versandfertigmachen</value>
</data>
<data name="StrStateRepeatStep" xml:space="preserve">
<value>Wiederholung Einzelschritt</value>
</data>
<data name="StrStateSkipping" xml:space="preserve">
<value>Prozeß auslassen</value>
</data>
<data name="StrStateUserStop" xml:space="preserve">
<value>Abbruch durch Benutzer</value>
</data>
<data name="StrStatePrintReturnReport" xml:space="preserve">
<value>Drucken des Rückläuferscheines</value>
</data>
<data name="StrStatePrintSuccessReport" xml:space="preserve">
<value>Drucken des Erfolgsreport</value>
</data>
<data name="StrStateCheckAttachments" xml:space="preserve">
<value>Beilagen und Anbauteile überprüfen</value>
</data>
<data name="StrStateVisualInspection" xml:space="preserve">
<value>Visuelle Inspektion Cordonel Display</value>
</data>
<data name="StrMsgAssemblePulseModule" xml:space="preserve">
<value>Pulsmodul einbauen</value>
</data>
<data name="StrMsgCheckOrderForPulseModule" xml:space="preserve">
<value>Auftragspapiere prüfen ob Adapter eingebaut werden soll!</value>
</data>
<data name="StrBtnImpossible" xml:space="preserve">
<value>Nicht möglich</value>
</data>
<data name="StrBtnDone" xml:space="preserve">
<value>Erledigt</value>
</data>
<data name="StrMsgImpossibleCheckAttachment" xml:space="preserve">
<value>Falls Information oder Hardware fehlt</value>
</data>
<data name="StrMsgDoneCheckAttachment" xml:space="preserve">
<value>Sobald Adapter installiert wurde oder wenn kein Adapter gefordert ist!</value>
</data>
<data name="StrMsgDoneVisualInspection" xml:space="preserve">
<value>Display zeigt Zahlenwerte an und Funk ist aus!</value>
</data>
<data name="StrErrorMsgVisualInspection" xml:space="preserve">
<value>Display zeigt Idle an und keine Zahlen oder Funk is aktiv!</value>
</data>
<data name="StrStateCheckOrderNumber" xml:space="preserve">
<value>Auftragsnummer einscannen</value>
</data>
<data name="StrStateCheckParametrization" xml:space="preserve">
<value>Auslieferungseinstellungen überprüfen</value>
</data>
<data name="StrStateCheckSerialNumber" xml:space="preserve">
<value>Deckelseriennummer einscannen</value>
</data>
<data name="StrStateCheckProductionState" xml:space="preserve">
<value>Versandvorbedingungen überprüfen</value>
</data>
<data name="StrStateConnectCordonel" xml:space="preserve">
<value>Cordonel verbinden</value>
</data>
<data name="StrStateFinalParametrization" xml:space="preserve">
<value>Auslieferungseinstellungen programmieren</value>
</data>
<data name="StrStateLogOffCordonel" xml:space="preserve">
<value>Cordonel abmelden</value>
</data>
<data name="StrStatePrepareShipping" xml:space="preserve">
<value>Versandbereitschaft herstellen</value>
</data>
<data name="StrStateRadioCheck" xml:space="preserve">
<value>Funktest</value>
</data>
<data name="StrStateReadyForShipping" xml:space="preserve">
<value>Cordonel ist versandfertig</value>
</data>
<data name="StrStateRebootCordonel" xml:space="preserve">
<value>Cordonel Neustart</value>
</data>
<data name="StrProcessSateAbort" xml:space="preserve">
<value>Abgebrochen</value>
</data>
<data name="StrStateDetectCordonel" xml:space="preserve">
<value>Cordonel suchen</value>
</data>
<data name="StrErrorMsgDataBaseAccess" xml:space="preserve">
<value>FEHLER: Datenbankzugriff fehlgeschlagen</value>
</data>
<data name="StrErrorMsgDeleteOldEolProgress" xml:space="preserve">
<value>FEHLER: Alte Wächter Parameter konnten nicht gelöscht werden</value>
</data>
<data name="StrErrorMsgCreateEolProgress" xml:space="preserve">
<value>FEHLER: Wächter Parameter konnten nicht erzeugt werden</value>
</data>
<data name="StrErrorMsgMissingEolProgress" xml:space="preserve">
<value>FEHLER: Es gab fehlerhafte oder ausgelassene Prozesse</value>
</data>
<data name="StrErrorMsgUploadEolProgress" xml:space="preserve">
<value>FEHLER: Wächter Parameter konnten nicht hochgeladen werden</value>
</data>
<data name="StrErrorMsgSentinelCheckEolProgress" xml:space="preserve">
<value>FEHLER: Wächter meldet Versandfertigmachen fehlerhaft</value>
</data>
<data name="StrErrorMsgSentinelConcerns" xml:space="preserve">
<value>FEHLER: Sentinel Meldung: </value>
</data>
<data name="StrMsgSentinelProcesses" xml:space="preserve">
<value>Wächter überprüft Ergebnisse und gleicht diese mit den Datenbankaufzeichnungen ab....</value>
</data>
<data name="StrSuccessMsgSentinelCheckEolProgress" xml:space="preserve">
<value>Erfolg: Wächter bestätigt Versandfertigmachen Einstellungen und Prozesse nach Überprüfung</value>
</data>
<data name="StrStateConfigCheck" xml:space="preserve">
<value>Konfigurationsvergleich</value>
</data>
<data name="StrErrorMsgCordonelDetection" xml:space="preserve">
<value>FEHLER: Cordonel Erkennung fehlgeschlagen</value>
</data>
<data name="StrErrorMsgCordonelLogin" xml:space="preserve">
<value>FEHLER: Cordonel Anmeldung fehlgeschlagen</value>
</data>
<data name="StrErrorMsgParameterDb" xml:space="preserve">
<value>FEHLER: Datenbank Konfigurationsdatenanfrage fehlgeschlagen</value>
</data>
<data name="StrStateGetProgrammingParameters" xml:space="preserve">
<value>Konfigurationen ermitteln</value>
</data>
<data name="StrStateGetSerialAndRadioAddress" xml:space="preserve">
<value>Seriennummer und Funkadresse ermitteln</value>
</data>
<data name="StrStatePasswordFile" xml:space="preserve">
<value>Paßwortdatei erzeugen und installieren</value>
</data>
<data name="StrDataTableColumnProcess" xml:space="preserve">
<value>Prozeß</value>
</data>
<data name="StrDataTableColumnState" xml:space="preserve">
<value>Status</value>
</data>
<data name="StrInfoMsgWaitForDevice" xml:space="preserve">
<value>Warte auf Cordonel</value>
</data>
<data name="StrPasswordFileExists" xml:space="preserve">
<value>Passwörter schon geschrieben. Prozess wird übersprungen</value>
</data>
<data name="StrErrorMsgCalibration" xml:space="preserve">
<value>FEHLER: Cordonel Kalibrierungsprozeß ausgelassen</value>
</data>
<data name="StrSuccessMsgCalibration" xml:space="preserve">
<value>Erfolg: Cordonel Kalibrierungsprozeß ist bestätigt worden </value>
</data>
<data name="StrErrorMsgZeroFlow" xml:space="preserve">
<value>FEHLER: Cordonel Zero Flow Prozeß ausgelassen</value>
</data>
<data name="StrSuccessMsgZeroFlow" xml:space="preserve">
<value>Erfolg: Cordonel Zero Flow Prozeß ist bestätigt worden</value>
</data>
<data name="StrErrorMsgBatteryLow" xml:space="preserve">
<value>FEHLER: Batterielaufzeit zu klein </value>
</data>
<data name="StrErrorMsgPasswordDbOverwrite" xml:space="preserve">
<value>FEHLER: Passwortüberschreiben in Datenbank fehlgeschlagen</value>
</data>
<data name="StrErrorMsgPreAndFlowTestStatusRequest" xml:space="preserve">
<value>FEHLER: Teststatus kann nicht ermittelt werden</value>
</data>
<data name="StrErrorMsgProductionRequirements" xml:space="preserve">
<value>FEHLER: Datenbankabruf für Produktionsfreigabe fehlgeschlagen</value>
</data>
<data name="StrSuccessMsgProductionRequirements" xml:space="preserve">
<value>Erfolg: Produktionsfreigabe gemäß Nummer</value>
</data>
<data name="StrErrorMsgWriteVerifyCordonelPasswordFile" xml:space="preserve">
<value>FEHLER: Schreiben und Überprüfen Cordonel Paßwortdatei fehlgeschlagen</value>
</data>
<data name="StrErrorMsgPasswordFileGenerationFailed" xml:space="preserve">
<value>FEHLER: Erzeugung der Cordonel Paßwortdatei fehlgeschlagen</value>
</data>
<data name="StrErrorMsgPasswordLevel3Wrong" xml:space="preserve">
<value>FEHLER: Paßwort Level 3 der Cordonel Paßwortdatei ist fehlerhaft</value>
</data>
<data name="StrErrorMsgRemainingLifeTime" xml:space="preserve">
<value>FEHLER: Verbleibende Restlaufzeit zu klein</value>
</data>
<data name="StrErrorMsgLifeTimeCalculation" xml:space="preserve">
<value>FEHLER: Restlaufzeitberechnung fehlgeschlagen</value>
</data>
<data name="StrStateFinalTestFailed" xml:space="preserve">
<value>FEHLER: Versandfertigmachen fehlgeschlagen</value>
</data>
<data name="StrStateStepFailed" xml:space="preserve">
<value>FEHLER: Prozeßschritt fehlgeschlagen</value>
</data>
<data name="StrErrorMsgScanOrderNumber" xml:space="preserve">
<value>FEHLER: Auftragsnummerscan fehlgeschlagen</value>
</data>
<data name="StrErrorMsgScanSerialNumber" xml:space="preserve">
<value>FEHLER: Deckelseriennummerscan fehlgeschlagen</value>
</data>
<data name="StrErrorMsgCheckRadioKey" xml:space="preserve">
<value>FEHLER: Funkschlüssel prüfen fehlgeschlagen</value>
</data>
<data name="StrErrorMsgRadioCheck" xml:space="preserve">
<value>FEHLER: Überprüfung Funksignalstärke fehlgeschlagen </value>
</data>
<data name="StrErrorMsgSirt" xml:space="preserve">
<value>FEHLER: SIRT konnte nicht initialisiert werden</value>
</data>
<data name="StrErrorMsgRadioKey" xml:space="preserve">
<value>FEHLER: Funkverschlüsselungscode fehlerhaft</value>
</data>
<data name="StrLblOrderNumberTxt" xml:space="preserve">
<value>Auftrag</value>
</data>
<data name="StrPrintOrderNumberTxt" xml:space="preserve">
<value>Fertigungsauftragsnummer</value>
</data>
<data name="StrLblMeterSizeTxt" xml:space="preserve">
<value>Baugröße</value>
</data>
<data name="StrLblKitronProductionDate" xml:space="preserve">
<value>Kitron Produktionsdatum</value>
</data>
<data name="StrLblExpirationDate" xml:space="preserve">
<value>Endtestablaufdatum</value>
</data>
<data name="StrLblMaxStorageMonths" xml:space="preserve">
<value>Maximallagerzeit</value>
</data>
<data name="StrLblMonths" xml:space="preserve">
<value>Monate</value>
</data>
<data name="StrLblRadioAddressTxt" xml:space="preserve">
<value>Funkadresse</value>
</data>
<data name="StrLblSerialNumberTxt" xml:space="preserve">
<value>Seriennummer</value>
</data>
<data name="StrSkippedMsgRadioCheckRegion" xml:space="preserve">
<value>Funktest übersprungen wegen NA Region</value>
</data>
<data name="StrSkippedMsgRadioCheckSpecialPermission" xml:space="preserve">
<value>WARNUNG: Funktest übersprungen wegen Sonderfreigabe</value>
</data>
<data name="StrSkippedMsgCalibrationCheckSpecialPermission" xml:space="preserve">
<value>WARNUNG: Kalibrierungsüberprüfung übersprungen wegen Sonderfreigabe</value>
</data>
<data name="StrProcessStateCheckNewMeter" xml:space="preserve">
<value>Überprüfe auf neuen Cordonel</value>
</data>
<data name="StrErrorMsgCompare" xml:space="preserve">
<value>FEHLER: Vergleich fehlgeschlagen</value>
</data>
<data name="StrErrorMsgRadioInconsistent" xml:space="preserve">
<value>Funkerkennung inkonsistent zwischen Cordonel und geforderten Einstellungen</value>
</data>
<data name="StrErrorMsgRead" xml:space="preserve">
<value>FEHLER: Lesen fehlgeschlagen</value>
</data>
<data name="StrErrorMsgUnsupportedFw" xml:space="preserve">
<value>FEHLER: Firmware wird nicht unterstützt</value>
</data>
<data name="StrErrorMsgLoadingFwFromDb" xml:space="preserve">
<value>FEHLER: Firmware konnte nicht aus Datenbank geladen werden</value>
</data>
<data name="StrErrorMsgWrite" xml:space="preserve">
<value>FEHLER: Schreiben fehlgeschlagen</value>
</data>
<data name="StrErrorMsgCoreRevision" xml:space="preserve">
<value>FEHLER: Falsche Kernversion des Cordonels</value>
</data>
<data name="StrErrorMsgAppVersionOrCrc" xml:space="preserve">
<value>FEHLER: Mindestens eine App fehlt oder ist falsch</value>
</data>
<data name="StrSuccessMsgCompare" xml:space="preserve">
<value>Erfolgreich verglichen</value>
</data>
<data name="StrSuccessMsgRead" xml:space="preserve">
<value>Erfolg: Erfolgreich gelesen</value>
</data>
<data name="StrSuccessMsgWrite" xml:space="preserve">
<value>Erfolg: Erfolgreich geschrieben</value>
</data>
<data name="StrErrorMsgAbortProcess" xml:space="preserve">
<value>FEHLER: Prozeß abgebrochen</value>
</data>
<data name="StrErrorMsgProcess" xml:space="preserve">
<value>FEHLER</value>
</data>
<data name="StrSuccessMsgProcess" xml:space="preserve">
<value>Erfolg</value>
</data>
<data name="StrErrorMsgGetRadioRssiLimitsFromDb" xml:space="preserve">
<value>FEHLER: RSSI Grenzwerte aus Datenbank holen fehlgeschlagen</value>
</data>
<data name="StrSuccessMsgGetRadioRssiLimitsFromDb" xml:space="preserve">
<value>Erfolg: RSSI Grenzwerte erfolgreich aus Datenbank geladen</value>
</data>
<data name="StrErrorMsgReboot" xml:space="preserve">
<value>FEHLER: Cordonel Neustart fehlgeschlagen</value>
</data>
<data name="StrErrorMsgSetRadioRssiLimitsToDb" xml:space="preserve">
<value>FEHLER: RSSI Werte in Datenbank speichern fehlgeschlagen</value>
</data>
<data name="StrSuccessMsgSetRadioRssiLimitsToDb" xml:space="preserve">
<value>Erfolg: RSSI Werte erfolgreich in Datenbank gespeichert</value>
</data>
<data name="StrLblRadioCheck" xml:space="preserve">
<value>Funktest</value>
</data>
<data name="StrMeasuredMsgRadioRssiCordonel" xml:space="preserve">
<value>RSSI der SEMI vom Cordonel (Cordonel =&gt; SIRT)</value>
</data>
<data name="StrMeasuredMsgRadioRssiSirt" xml:space="preserve">
<value>RSSI der PAM von SIRT (SIRT =&gt; Cordonel)</value>
</data>
<data name="StrRadioMsgSendPam" xml:space="preserve">
<value>Sende PAM an Adresse</value>
</data>
<data name="StrStartMsgProcess" xml:space="preserve">
<value>Start</value>
</data>
<data name="StrSuccessMsgRadioAlarmsReset" xml:space="preserve">
<value>Erfolg: Funkalarme erfolgreich gelöscht</value>
</data>
<data name="StrSuccessMsgRadioInCustomerMode" xml:space="preserve">
<value>Erfolg: Funk efolgreich in Kundenmodus geschaltet</value>
</data>
<data name="StrBtnRetry" xml:space="preserve">
<value>Wiederholen</value>
</data>
<data name="StrBtnSkip" xml:space="preserve">
<value>Überspringe</value>
</data>
<data name="StrBtnRestart" xml:space="preserve">
<value>Starte erneut</value>
</data>
<data name="StrBtnRetryFailedSteps" xml:space="preserve">
<value>Wiederhole fehlgeschlagene Prozesse</value>
</data>
<data name="StrInfoRetryFailedSteps" xml:space="preserve">
<value>Wiederholung ab erstem Fehler!</value>
</data>
<data name="StrBtnPrintErrorReport" xml:space="preserve">
<value>Rückläuferschein drucken</value>
</data>
<data name="StrBtnPrintSuccessReport" xml:space="preserve">
<value>Bericht drucken</value>
</data>
<data name="StrBtnStart" xml:space="preserve">
<value>Starte</value>
</data>
<data name="StrBtnStop" xml:space="preserve">
<value>Abbruch</value>
</data>
<data name="StrBtnFail" xml:space="preserve">
<value>Fehler</value>
</data>
<data name="StrErrorMsg" xml:space="preserve">
<value>FEHLER</value>
</data>
<data name="StrSuccessMsg" xml:space="preserve">
<value>Erfolg</value>
</data>
<data name="StrErrorMsgOrderDetails" xml:space="preserve">
<value>FEHLER: Auftragsdatails abrufen fehlgeschlagen</value>
</data>
<data name="StrErrorMsgParameterCompare" xml:space="preserve">
<value>FEHLER: Parametervergleich fehlgeschlagen</value>
</data>
<data name="StrErrorMsgParameterWrite" xml:space="preserve">
<value>FEHLER: Parameterschreiben fehlgeschlagen</value>
</data>
<data name="StrErrorMsgStoreConfiguration" xml:space="preserve">
<value>FEHLER: Einstellungen Sichern fehlgeschlagen</value>
</data>
<data name="StrStateStoreConfiguration" xml:space="preserve">
<value>Einstellungen sichern</value>
</data>
<data name="StrSuccessMsgStoreConfiguration" xml:space="preserve">
<value>Erfolg: Einstellungen erfolgreich gespeichert</value>
</data>
<data name="StrProcessStateWarning" xml:space="preserve">
<value>WARNUNG</value>
</data>
<data name="StrWarningMsgProcess" xml:space="preserve">
<value>WARNUNG: Ergebnis manuell überprüfen</value>
</data>
<data name="StrLblSpecialRequirement" xml:space="preserve">
<value>SONDERFREIGABE</value>
</data>
<data name="StrLblMissingActiveAndApprovedRequirement" xml:space="preserve">
<value>Keine Freigabe vorhanden</value>
</data>
<data name="StrLblStandardRequirement" xml:space="preserve">
<value>STANDARDFREIGABE</value>
</data>
<data name="Cordonel_With_Display_small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Cordonel With Display small.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SensusLogoXylem" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\SensusLogoXylem.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="StrErrorMsgRequirementPcbId" xml:space="preserve">
<value>FEHLER: Falsche PcbId der Produktionsanforderungen aus der Datenbank</value>
</data>
<data name="StrErrorMsgCalibrationPcbId" xml:space="preserve">
<value>FEHLER: Falsche PcbId der Kalibrierwerte aus der Datenbank</value>
</data>
<data name="StrErrorMsgCalibrationValues" xml:space="preserve">
<value>ERROR: Kalibrierwerte aus Datenbank und gelesen aus Cordonel sind unterschiedlich</value>
</data>
<data name="StrErrorMsgDownloadCalibrationValuesFromDb" xml:space="preserve">
<value>FEHLER: Kalibrierwerte konnten nicht aus Datenbank geladen werden</value>
</data>
<data name="StrErrorMsgDownloadFlowTestStatusFromDb" xml:space="preserve">
<value>FEHLER: Regulierstatus konnte nicht aus Datenbank geladen werden </value>
</data>
<data name="StrErrorMsgCalibrationValuesOutOfRange" xml:space="preserve">
<value>FEHLER: Kalibrierwerte gelesen vom Cordonel sind außerhalb des zulässigen Bereiches</value>
</data>
<data name="StrWarningMsgCalibrationValuesOnStandard" xml:space="preserve">
<value>WARNUNG: Alle Kalibrierwerte des Cordonels sind Standardwerte</value>
</data>
<data name="StrErrorMsgRequirementFw" xml:space="preserve">
<value>FEHLER: Falsche Firmware in Cordonel installiert</value>
</data>
<data name="StrErrorMsgRequirementLut" xml:space="preserve">
<value>FEHLER: Falsche LUT in Cordonel installiert</value>
</data>
<data name="StrErrorMsgRequirementMaximumStorageMonth" xml:space="preserve">
<value>FEHLER: Maximale Lagerzeit überschritten</value>
</data>
<data name="StrErrorMsgRequirementMaximumStorageMonthUnknown" xml:space="preserve">
<value>FEHLER: Maximale Lagerzeit oder Inbetriebnahmezeit unbekannt</value>
</data>
<data name="StrErrorMsgRequirementMeterSize" xml:space="preserve">
<value>FEHLER: Falsche Nennweite</value>
</data>
<data name="StrErrorMsgRequirementProductionOrder" xml:space="preserve">
<value>FEHLER: Falsche Produktionsnummer</value>
</data>
<data name="StrErrorMsgRequirementRegion" xml:space="preserve">
<value>FEHLER: Falsche Region</value>
</data>
<data name="StrErrorMsgRequirementInactive" xml:space="preserve">
<value>FEHLER: Nicht für die Produktion freigegeben</value>
</data>
<data name="StrErrorMsgRequirementRemainingBatteryLoad" xml:space="preserve">
<value>FEHLER: Maximal Batterieladung</value>
</data>
<data name="StrErrorMsgRequirementRemainingLifeTime" xml:space="preserve">
<value>FEHLER: Geforderte Restlaufzeit</value>
</data>
<data name="StrErrorMsgDbToMeterEncryptionKey" xml:space="preserve">
<value>FEHLER: Datenbank und Cordonel Funkschlüssel ungleich</value>
</data>
<data name="StrLblTimeText" xml:space="preserve">
<value>Prozeßzeit [min:s]</value>
</data>
<data name="StrLblTotalProcessText" xml:space="preserve">
<value>Gesamtprozeß</value>
</data>
<data name="StrLblOperatorNameText" xml:space="preserve">
<value>Mitarbeiter</value>
</data>
<data name="StrMenuOptionsText" xml:space="preserve">
<value>_Optionen</value>
</data>
<data name="StrMenuHelpText" xml:space="preserve">
<value>_Hilfe</value>
</data>
<data name="StrMenuChangePasswordText" xml:space="preserve">
<value>_Paßwort ändern</value>
</data>
<data name="StrLblSingleProcessText" xml:space="preserve">
<value>Einzelprozeß</value>
</data>
<data name="StrSuccessMsgRequirementPcbId" xml:space="preserve">
<value>Erfolg: PcbId der Produktionsanforderungen aus der Datenbank und des Cordonels erfolgreich abgeglichen</value>
</data>
<data name="StrSuccessMsgOrderBasedRequirements" xml:space="preserve">
<value>Erfolg: Auftragsbasierende Produktionsanforderungen festgestellt</value>
</data>
<data name="StrSuccessMsgCalibrationPcbId" xml:space="preserve">
<value>Erfolg: PcbId der Kalibrierwerte aus der Datenbank und des Cordonels erfolgreich abgeglichen</value>
</data>
<data name="StrSuccessMsgCalibrationCalibration" xml:space="preserve">
<value>Erfolg: Kalibrierwerte aus der Datenbank stimmen mit den gelesenen Werten aus Cordonel überein</value>
</data>
<data name="StrSuccessMsgRequirementFw" xml:space="preserve">
<value>Erfolg: Korrekte Firmware installiert</value>
</data>
<data name="StrSuccessMsgRequirementLut" xml:space="preserve">
<value>Erfolg: Korrekte LUT installiert</value>
</data>
<data name="StrSuccessMsgRequirementMaximumStorageMonth" xml:space="preserve">
<value>Erfolg: Maximale Lagerzeit eingehalten</value>
</data>
<data name="StrSuccessMsgRequirementMeterSize" xml:space="preserve">
<value>Erfolg: Nennweite erfolgreich validiert</value>
</data>
<data name="StrSuccessMsgRequirementProductionOrder" xml:space="preserve">
<value>Erfolg: Produktionsnummer erfolgreich abgeglichen</value>
</data>
<data name="StrSuccessMsgRequirementRegion" xml:space="preserve">
<value>Erfolg: Region erfolgreich überprüft</value>
</data>
<data name="StrSuccessMsgRequirementActive" xml:space="preserve">
<value>Erfolg: Standardanforderung für die Produktion freigegeben</value>
</data>
<data name="StrWarningMsgSpecialRequirementActive" xml:space="preserve">
<value>ACHTUNG: Sonderfreigabe für Produktion erteilt</value>
</data>
<data name="StrSuccessMsgRequirementRemainingBatteryLoad" xml:space="preserve">
<value>Zulässige maximale Batterieentladung</value>
</data>
<data name="StrSuccessMsgDbToMeterEncryptionKey" xml:space="preserve">
<value>Erfolg: Datenbank und Cordonel Funkschlüssel erfolgreich abgeglichen</value>
</data>
<data name="StrMsgDrainedBattery" xml:space="preserve">
<value>Verbrauchte Batterieladung</value>
</data>
<data name="StrMsgAlternativeDrainedBatteryCalculation" xml:space="preserve">
<value>Berechnung basierend auf Ruhestrom und Produktionszuschlag</value>
</data>
<data name="StrErrorMsgThresholdDrainedBattery" xml:space="preserve">
<value>FEHLER: Maximale erlaubte Batterieentladung überschritten</value>
</data>
<data name="StrMsgRequirementAbsolutLimitDrainedBattery" xml:space="preserve">
<value>Absoluter Grenzwert</value>
</data>
<data name="StrMsgRequirementLimitDrainedBattery" xml:space="preserve">
<value>Maximal erlaubt</value>
</data>
<data name="StrMsgRemainingLifeTime" xml:space="preserve">
<value>Abgeschätzte Restlaufzeit</value>
</data>
<data name="StrErrorMsgThresholdRemainingLifeTime" xml:space="preserve">
<value>FEHLER: Minimale geforderte Restlaufzeit unterschritten</value>
</data>
<data name="StrErrorMsgBatteryLoadCalculation" xml:space="preserve">
<value>FEHLER: Batterieentladung konnte nicht berechnet werden</value>
</data>
<data name="StrSuccessMsgThresholdRemainingLifeTime" xml:space="preserve">
<value>Erfolg: Minimale Restlaufzeit eingehalten</value>
</data>
<data name="StrMsgRequiredLifeTime" xml:space="preserve">
<value>Minimal geforderte Restlaufzeit</value>
</data>
<data name="StrMsgYears" xml:space="preserve">
<value>Jahre</value>
</data>
<data name="StrEolSoftwareNameVersion" xml:space="preserve">
<value>Cordonel - Versandfertigmachen - Version</value>
</data>
<data name="StrPickingSoftwareNameVersion" xml:space="preserve">
<value>Cordonel - Montage - Version</value>
</data>
<data name="StrInstructionScanHousing" xml:space="preserve">
<value>Deckel scannen</value>
</data>
<data name="StrInstructionScanOrder" xml:space="preserve">
<value>Auftrag scannen</value>
</data>
<data name="StrDateTime" xml:space="preserve">
<value>Datum</value>
</data>
<data name="StrInstructionReturnPapers" xml:space="preserve">
<value>Rückläuferschein beim Cordonel lassen und auf Klärung durch QS warten!</value>
</data>
<data name="StrInfoSuccessPapers" xml:space="preserve">
<value>Cordonel ist fertig zum Versenden! Dieser Bericht dient der Information.</value>
</data>
<data name="StrInstructionSuccessPapers" xml:space="preserve">
<value>Optional: Zur Information kann ein Bericht gedruckt werden.</value>
</data>
<data name="StrErrorMsgCordonelInvestigate" xml:space="preserve">
<value>Cordonel muß näher untersucht werden! Details stehen im Info Fenster. </value>
</data>
<data name="StrInstructionStop" xml:space="preserve">
<value>Abbruch ohne Bericht!</value>
</data>
<data name="StrInstructionMeterBoxing" xml:space="preserve">
<value>Cordonel verpacken!</value>
</data>
<data name="StrInfoMeterBoxing" xml:space="preserve">
<value>Cordonel hat alle Tests bestanden!</value>
</data>
<data name="StrErrorMsgEolProcess" xml:space="preserve">
<value>Versandfertigmachen fehlgeschlagen!</value>
</data>
<data name="StrMsgPwdRequest" xml:space="preserve">
<value>Passwörter anfordern</value>
</data>
<data name="StrMsgPwdFileGeneration" xml:space="preserve">
<value>Passwortdatei erstellen</value>
</data>
<data name="StrMsgPwdFileWrite" xml:space="preserve">
<value>Passwortdatei installieren</value>
</data>
<data name="StrMsgPwdUpdateToDb" xml:space="preserve">
<value>Passwort in Datenbank erneuern</value>
</data>
<data name="StrMsgPwdLogoff" xml:space="preserve">
<value>Cordonel abmelden</value>
</data>
<data name="StrMsgPwdValidating" xml:space="preserve">
<value>Cordonel Anmeldung</value>
</data>
<data name="StrSkippedMsgProcess" xml:space="preserve">
<value>AUSGELASSEN</value>
</data>
<data name="StrLoginMsg" xml:space="preserve">
<value>Anmeldung</value>
</data>
<data name="StrChangePasswordMsg" xml:space="preserve">
<value>Paßwort ändern</value>
</data>
</root>
@@ -0,0 +1,802 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human-readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="ConnectPlugged_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\ConnectPlugged_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Connect_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Connect_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconCheck" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconCheck.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconWarn" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconWarn.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="iconWating" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\iconWating.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Refresh_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Refresh_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="CLEARMAtrix" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\CLEARMAtrix.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="StrErrorMsgMissingNetworkConnetction" xml:space="preserve">
<value>Missing Network Connection. The software is going to exit!</value>
</data>
<data name="StrErrorMsgMissingConfigurationFile" xml:space="preserve">
<value>Configuration file not found. Setup slot with the GenesisToolBox. The software is going to exit!</value>
</data>
<data name="StrErrorMsgMissingComPort" xml:space="preserve">
<value>Communication port is in use or not present. The software is going to exit!</value>
</data>
<data name="StrStartMessageSwLicenseExpired" xml:space="preserve">
<value>New Cordonel EOL Software available! The software is going to exit! This version:</value>
</data>
<data name="StrStateIdle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="StrStateRetry" xml:space="preserve">
<value>Retry</value>
</data>
<data name="StrStateStartInitial" xml:space="preserve">
<value>Initial start</value>
</data>
<data name="StrProcessStateDone" xml:space="preserve">
<value>Success</value>
</data>
<data name="StrProcessStateSkipped" xml:space="preserve">
<value>Skipped</value>
</data>
<data name="StrProcessStateError" xml:space="preserve">
<value>ERROR</value>
</data>
<data name="StrProcessStateRunning" xml:space="preserve">
<value>Processing</value>
</data>
<data name="StrProcessStateWaiting" xml:space="preserve">
<value>Waiting</value>
</data>
<data name="StrSuccessMsgPasswordFileWritten" xml:space="preserve">
<value>Success: Password file successfully written</value>
</data>
<data name="StrStateRepeatFinalTest" xml:space="preserve">
<value>Repeat shipping procedure</value>
</data>
<data name="StrStateRepeatStep" xml:space="preserve">
<value>Repeat single step</value>
</data>
<data name="StrStateSkipping" xml:space="preserve">
<value>Skip process</value>
</data>
<data name="StrStateUserStop" xml:space="preserve">
<value>Process cancellation by user</value>
</data>
<data name="StrStatePrintReturnReport" xml:space="preserve">
<value>Print return report</value>
</data>
<data name="StrStatePrintSuccessReport" xml:space="preserve">
<value>Print success report</value>
</data>
<data name="StrStateCheckAttachments" xml:space="preserve">
<value>Check attachments and assemblies</value>
</data>
<data name="StrStateVisualInspection" xml:space="preserve">
<value>Visual inspection Cordonel display</value>
</data>
<data name="StrMsgAssemblePulseModule" xml:space="preserve">
<value>Assemble pulse module</value>
</data>
<data name="StrMsgCheckOrderForPulseModule" xml:space="preserve">
<value>Check order papers for adapter assembly</value>
</data>
<data name="StrBtnImpossible" xml:space="preserve">
<value>Not possible</value>
</data>
<data name="StrBtnDone" xml:space="preserve">
<value>Done</value>
</data>
<data name="StrMsgImpossibleCheckAttachment" xml:space="preserve">
<value>If information or hardware is missing</value>
</data>
<data name="StrMsgDoneCheckAttachment" xml:space="preserve">
<value>As soon as adapter is installed or if an adapter is not required!</value>
</data>
<data name="StrMsgDoneVisualInspection" xml:space="preserve">
<value>Display shows numbers and radio is off!</value>
</data>
<data name="StrErrorMsgVisualInspection" xml:space="preserve">
<value>Display shows Idle or radio is active!</value>
</data>
<data name="StrStateCheckOrderNumber" xml:space="preserve">
<value>Scan and check order number</value>
</data>
<data name="StrStateCheckParametrization" xml:space="preserve">
<value>Configuration check full readout</value>
</data>
<data name="StrStateCheckSerialNumber" xml:space="preserve">
<value>Scan and check housing serial number</value>
</data>
<data name="StrStateCheckProductionState" xml:space="preserve">
<value>Check production state</value>
</data>
<data name="StrStateConnectCordonel" xml:space="preserve">
<value>Connect Cordonel</value>
</data>
<data name="StrStateFinalParametrization" xml:space="preserve">
<value>Programming configuration</value>
</data>
<data name="StrStateLogOffCordonel" xml:space="preserve">
<value>Log off from Cordonel</value>
</data>
<data name="StrStatePrepareShipping" xml:space="preserve">
<value>Prepare for shipping</value>
</data>
<data name="StrStateRadioCheck" xml:space="preserve">
<value>Check radio</value>
</data>
<data name="StrStateReadyForShipping" xml:space="preserve">
<value>Cordonel is ready for shipping</value>
</data>
<data name="StrStateRebootCordonel" xml:space="preserve">
<value>Reboot Cordonel</value>
</data>
<data name="StrProcessSateAbort" xml:space="preserve">
<value>Aborted</value>
</data>
<data name="StrStateDetectCordonel" xml:space="preserve">
<value>Autodetect Cordonel</value>
</data>
<data name="StrErrorMsgDataBaseAccess" xml:space="preserve">
<value>ERROR: Data base access failed</value>
</data>
<data name="StrErrorMsgDeleteOldEolProgress" xml:space="preserve">
<value>ERROR: Unable to delete old sentinel parameter</value>
</data>
<data name="StrErrorMsgCreateEolProgress" xml:space="preserve">
<value>ERROR: Unable to generate sentinel parameter</value>
</data>
<data name="StrErrorMsgMissingEolProgress" xml:space="preserve">
<value>ERROR: Not all EOL progresses succeeded</value>
</data>
<data name="StrErrorMsgUploadEolProgress" xml:space="preserve">
<value>ERROR: Unable to upload sentinel parameter</value>
</data>
<data name="StrErrorMsgSentinelCheckEolProgress" xml:space="preserve">
<value>ERROR: Sentinel returned EOL check failed</value>
</data>
<data name="StrErrorMsgSentinelConcerns" xml:space="preserve">
<value>ERROR: Sentinel message: </value>
</data>
<data name="StrMsgSentinelProcesses" xml:space="preserve">
<value>Sentinel checks results and matching with database recordings </value>
</data>
<data name="StrSuccessMsgSentinelCheckEolProgress" xml:space="preserve">
<value>Success: Sentinel crosscheck acknowledged preparation for shipping (EOL) settings and processes</value>
</data>
<data name="StrStateConfigCheck" xml:space="preserve">
<value>Configuration check</value>
</data>
<data name="StrErrorMsgCordonelDetection" xml:space="preserve">
<value>ERROR: Cordonel detection failed</value>
</data>
<data name="StrErrorMsgCordonelLogin" xml:space="preserve">
<value>ERROR: Cordonel logon failed</value>
</data>
<data name="StrErrorMsgParameterDb" xml:space="preserve">
<value>ERROR: Data base configuration request failed</value>
</data>
<data name="StrStateGetProgrammingParameters" xml:space="preserve">
<value>Get configurations</value>
</data>
<data name="StrStateGetSerialAndRadioAddress" xml:space="preserve">
<value>Get serial number and radio address</value>
</data>
<data name="StrStatePasswordFile" xml:space="preserve">
<value>Build and install password file</value>
</data>
<data name="StrDataTableColumnProcess" xml:space="preserve">
<value>Process</value>
</data>
<data name="StrDataTableColumnState" xml:space="preserve">
<value>State</value>
</data>
<data name="StrInfoMsgWaitForDevice" xml:space="preserve">
<value>Waiting on Cordonel</value>
</data>
<data name="StrPasswordFileExists" xml:space="preserve">
<value>Password file already exists. Skip process</value>
</data>
<data name="StrErrorMsgCalibration" xml:space="preserve">
<value>ERROR: Cordonel not successfully tested</value>
</data>
<data name="StrSuccessMsgCalibration" xml:space="preserve">
<value>Success: Cordonel Calibration process execution reported </value>
</data>
<data name="StrErrorMsgZeroFlow" xml:space="preserve">
<value>ERROR: Cordonel zero flow calibration missing</value>
</data>
<data name="StrSuccessMsgZeroFlow" xml:space="preserve">
<value>Success: Cordonel Zero Flow process execution reported</value>
</data>
<data name="StrErrorMsgBatteryLow" xml:space="preserve">
<value>ERROR: Battery low</value>
</data>
<data name="StrErrorMsgPasswordDbOverwrite" xml:space="preserve">
<value>ERROR: Password overwriting in database failed</value>
</data>
<data name="StrErrorMsgPreAndFlowTestStatusRequest" xml:space="preserve">
<value>ERROR: Pre- and flow-test status unknown</value>
</data>
<data name="StrErrorMsgProductionRequirements" xml:space="preserve">
<value>ERROR: Production requirements request from data base failed</value>
</data>
<data name="StrSuccessMsgProductionRequirements" xml:space="preserve">
<value>Success: Production requirements according to number</value>
</data>
<data name="StrErrorMsgWriteVerifyCordonelPasswordFile" xml:space="preserve">
<value>ERROR: Write and verify Cordonel password file failed</value>
</data>
<data name="StrErrorMsgPasswordFileGenerationFailed" xml:space="preserve">
<value>ERROR: Generation of Cordonel password file failed</value>
</data>
<data name="StrErrorMsgPasswordLevel3Wrong" xml:space="preserve">
<value>ERROR: Password Level 3 in Cordonel password file is wrong</value>
</data>
<data name="StrErrorMsgRemainingLifeTime" xml:space="preserve">
<value>ERROR: Remaining lifetime too short</value>
</data>
<data name="StrErrorMsgLifeTimeCalculation" xml:space="preserve">
<value>ERROR: Lifetime calculation failed</value>
</data>
<data name="StrStateFinalTestFailed" xml:space="preserve">
<value>ERROR: Preparation for shipping failed</value>
</data>
<data name="StrStateStepFailed" xml:space="preserve">
<value>ERROR: Process step failed</value>
</data>
<data name="StrErrorMsgScanOrderNumber" xml:space="preserve">
<value>ERROR: Order number scan failed</value>
</data>
<data name="StrErrorMsgScanSerialNumber" xml:space="preserve">
<value>ERROR: Housing serial number scan failed</value>
</data>
<data name="StrErrorMsgCheckRadioKey" xml:space="preserve">
<value>ERROR: Check radio encryption key failed</value>
</data>
<data name="StrErrorMsgRadioCheck" xml:space="preserve">
<value>ERROR: Radio signal strength check failed</value>
</data>
<data name="StrErrorMsgSirt" xml:space="preserve">
<value>ERROR: SIRT could not be initialized </value>
</data>
<data name="StrErrorMsgRadioKey" xml:space="preserve">
<value>ERROR: Radio encryption code wrong</value>
</data>
<data name="StrLblOrderNumberTxt" xml:space="preserve">
<value>Order</value>
</data>
<data name="StrPrintOrderNumberTxt" xml:space="preserve">
<value>Production Order Number </value>
</data>
<data name="StrLblMeterSizeTxt" xml:space="preserve">
<value>Meter Size</value>
</data>
<data name="StrLblKitronProductionDate" xml:space="preserve">
<value>Kitron production date</value>
</data>
<data name="StrLblExpirationDate" xml:space="preserve">
<value>Final test expiration date</value>
</data>
<data name="StrLblMaxStorageMonths" xml:space="preserve">
<value>Maximum storage time</value>
</data>
<data name="StrLblMonths" xml:space="preserve">
<value>Months</value>
</data>
<data name="StrLblRadioAddressTxt" xml:space="preserve">
<value>RadioAddress</value>
</data>
<data name="StrLblSerialNumberTxt" xml:space="preserve">
<value>SerialNumber</value>
</data>
<data name="StrSkippedMsgRadioCheckRegion" xml:space="preserve">
<value>Radio check skipped due to NA region</value>
</data>
<data name="StrSkippedMsgRadioCheckSpecialPermission" xml:space="preserve">
<value>WARNING: Radio check skipped due to special permission</value>
</data>
<data name="StrSkippedMsgCalibrationCheckSpecialPermission" xml:space="preserve">
<value>WARNING: Calibration check skipped due to special permission</value>
</data>
<data name="StrProcessStateCheckNewMeter" xml:space="preserve">
<value>Check for new Cordonel</value>
</data>
<data name="StrErrorMsgCompare" xml:space="preserve">
<value>ERROR: Comparison failed</value>
</data>
<data name="StrErrorMsgRadioInconsistent" xml:space="preserve">
<value>ERROR: Radio inconsistent between meter and programming parameters</value>
</data>
<data name="StrErrorMsgRead" xml:space="preserve">
<value>ERROR: Read failed</value>
</data>
<data name="StrErrorMsgUnsupportedFw" xml:space="preserve">
<value>ERROR: Firmware is not supported</value>
</data>
<data name="StrErrorMsgLoadingFwFromDb" xml:space="preserve">
<value>ERROR: Firmware could not be downloaded from database</value>
</data>
<data name="StrErrorMsgWrite" xml:space="preserve">
<value>ERROR: Write failed</value>
</data>
<data name="StrErrorMsgCoreRevision" xml:space="preserve">
<value>ERROR: Wrong Core Revision of Cordonel</value>
</data>
<data name="StrErrorMsgAppVersionOrCrc" xml:space="preserve">
<value>ERROR: At least one wrong or missing App</value>
</data>
<data name="StrSuccessMsgCompare" xml:space="preserve">
<value>Success: Successfully compared</value>
</data>
<data name="StrSuccessMsgRead" xml:space="preserve">
<value>Success: Successfully read</value>
</data>
<data name="StrSuccessMsgWrite" xml:space="preserve">
<value>Successfully written</value>
</data>
<data name="StrErrorMsgAbortProcess" xml:space="preserve">
<value>ERROR: Abort process</value>
</data>
<data name="StrErrorMsgProcess" xml:space="preserve">
<value>ERROR</value>
</data>
<data name="StrSuccessMsgProcess" xml:space="preserve">
<value>Success</value>
</data>
<data name="StrErrorMsgGetRadioRssiLimitsFromDb" xml:space="preserve">
<value>ERROR: RSSI limits from data base request failed</value>
</data>
<data name="StrSuccessMsgGetRadioRssiLimitsFromDb" xml:space="preserve">
<value>Success: Successfully received RSSI limits from database</value>
</data>
<data name="StrErrorMsgReboot" xml:space="preserve">
<value>ERROR: Cordonel reboot failed</value>
</data>
<data name="StrErrorMsgSetRadioRssiLimitsToDb" xml:space="preserve">
<value>ERROR: Error saving RSSI to database failed</value>
</data>
<data name="StrSuccessMsgSetRadioRssiLimitsToDb" xml:space="preserve">
<value>Success: Successfully saved RSSI to database</value>
</data>
<data name="StrLblRadioCheck" xml:space="preserve">
<value>Radio Check</value>
</data>
<data name="StrMeasuredMsgRadioRssiCordonel" xml:space="preserve">
<value>RSSI of SEMI sent by Cordonel (Cordonel =&gt; SIRT)</value>
</data>
<data name="StrMeasuredMsgRadioRssiSirt" xml:space="preserve">
<value>RSSI of PAM sent by SIRT (SIRT =&gt; Cordonel)</value>
</data>
<data name="StrRadioMsgSendPam" xml:space="preserve">
<value>Sending PAM to address</value>
</data>
<data name="StrStartMsgProcess" xml:space="preserve">
<value>Start</value>
</data>
<data name="StrSuccessMsgRadioAlarmsReset" xml:space="preserve">
<value>Success: Radio alarms successfully reset</value>
</data>
<data name="StrSuccessMsgRadioInCustomerMode" xml:space="preserve">
<value>Radio successfully switched to customer mode</value>
</data>
<data name="StrBtnRetry" xml:space="preserve">
<value>Retry</value>
</data>
<data name="StrBtnSkip" xml:space="preserve">
<value>Skip</value>
</data>
<data name="StrBtnRestart" xml:space="preserve">
<value>Start From Beginning</value>
</data>
<data name="StrBtnRetryFailedSteps" xml:space="preserve">
<value>Repeat Failed Processes</value>
</data>
<data name="StrInfoRetryFailedSteps" xml:space="preserve">
<value>Repeat from first failed process!</value>
</data>
<data name="StrBtnPrintErrorReport" xml:space="preserve">
<value>Print Return Report</value>
</data>
<data name="StrBtnPrintSuccessReport" xml:space="preserve">
<value>Print Report</value>
</data>
<data name="StrBtnStart" xml:space="preserve">
<value>Start</value>
</data>
<data name="StrBtnStop" xml:space="preserve">
<value>Abort</value>
</data>
<data name="StrBtnFail" xml:space="preserve">
<value>Failed</value>
</data>
<data name="StrErrorMsg" xml:space="preserve">
<value>ERROR</value>
</data>
<data name="StrSuccessMsg" xml:space="preserve">
<value>Success</value>
</data>
<data name="StrErrorMsgOrderDetails" xml:space="preserve">
<value>ERROR: Collection of order details failed</value>
</data>
<data name="StrErrorMsgParameterCompare" xml:space="preserve">
<value>ERROR: Parameter compare failed</value>
</data>
<data name="StrErrorMsgParameterWrite" xml:space="preserve">
<value>ERROR: Parameter write failed</value>
</data>
<data name="StrErrorMsgStoreConfiguration" xml:space="preserve">
<value>ERROR: Storing of configurations failed</value>
</data>
<data name="StrStateStoreConfiguration" xml:space="preserve">
<value>Store configurations</value>
</data>
<data name="StrSuccessMsgStoreConfiguration" xml:space="preserve">
<value>Success: Configurations successfully stored</value>
</data>
<data name="StrProcessStateWarning" xml:space="preserve">
<value>WARNING</value>
</data>
<data name="StrWarningMsgProcess" xml:space="preserve">
<value>WARNING: Inspect results</value>
</data>
<data name="StrLblSpecialRequirement" xml:space="preserve">
<value>SPECIAL APPROVAL</value>
</data>
<data name="StrLblMissingActiveAndApprovedRequirement" xml:space="preserve">
<value>Missing requirement</value>
</data>
<data name="StrLblStandardRequirement" xml:space="preserve">
<value>STANDARD APPROVAL</value>
</data>
<data name="Cordonel_With_Display_small" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Cordonel With Display small.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SensusLogoXylem" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\SensusLogoXylem.JPG;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="StrErrorMsgRequirementPcbId" xml:space="preserve">
<value>ERROR: Requirement PcbId from database does not match</value>
</data>
<data name="StrErrorMsgCalibrationPcbId" xml:space="preserve">
<value>ERROR: PcbId of calibration values from database does not match</value>
</data>
<data name="StrErrorMsgCalibrationValues" xml:space="preserve">
<value>ERROR: Calibration values from database and read from Cordonel are unequal</value>
</data>
<data name="StrErrorMsgDownloadCalibrationValuesFromDb" xml:space="preserve">
<value>ERROR: Download calibration values from database failed</value>
</data>
<data name="StrErrorMsgDownloadFlowTestStatusFromDb" xml:space="preserve">
<value>ERROR: Download flow test status from database failed</value>
</data>
<data name="StrErrorMsgCalibrationValuesOutOfRange" xml:space="preserve">
<value>ERROR: Calibration values read from Cordonel are out of range</value>
</data>
<data name="StrWarningMsgCalibrationValuesOnStandard" xml:space="preserve">
<value>WARNING: All calibration values read from Cordonel are on default</value>
</data>
<data name="StrErrorMsgRequirementFw" xml:space="preserve">
<value>ERROR: Wrong Firmware installed in Cordonel</value>
</data>
<data name="StrErrorMsgRequirementLut" xml:space="preserve">
<value>ERROR: Wrong LUT installed in Cordonel</value>
</data>
<data name="StrErrorMsgRequirementMaximumStorageMonth" xml:space="preserve">
<value>ERROR: Maximum Storage Month exceeded</value>
</data>
<data name="StrErrorMsgRequirementMaximumStorageMonthUnknown" xml:space="preserve">
<value>ERROR: Maximum Storage Month or battery assembly date unknown</value>
</data>
<data name="StrErrorMsgRequirementMeterSize" xml:space="preserve">
<value>ERROR: Requirement Meter Size does not match</value>
</data>
<data name="StrErrorMsgRequirementProductionOrder" xml:space="preserve">
<value>ERROR: Requirement Production Order does not match</value>
</data>
<data name="StrErrorMsgRequirementRegion" xml:space="preserve">
<value>ERROR: Requirement Region does not match</value>
</data>
<data name="StrErrorMsgRequirementInactive" xml:space="preserve">
<value>ERROR: Requirement not valid for production</value>
</data>
<data name="StrErrorMsgRequirementRemainingBatteryLoad" xml:space="preserve">
<value>ERROR: Remaining Battery load to low</value>
</data>
<data name="StrErrorMsgRequirementRemainingLifeTime" xml:space="preserve">
<value>ERROR: Remaining Life Time to low</value>
</data>
<data name="StrErrorMsgDbToMeterEncryptionKey" xml:space="preserve">
<value>ERROR: Data base and Cordonel Radio Encryption Key are different</value>
</data>
<data name="StrLblTimeText" xml:space="preserve">
<value>Elapsed Time [min:s]</value>
</data>
<data name="StrLblTotalProcessText" xml:space="preserve">
<value>Entire Process</value>
</data>
<data name="StrLblOperatorNameText" xml:space="preserve">
<value>Operator</value>
</data>
<data name="StrMenuOptionsText" xml:space="preserve">
<value>_Options</value>
</data>
<data name="StrMenuHelpText" xml:space="preserve">
<value>_Help</value>
</data>
<data name="StrMenuChangePasswordText" xml:space="preserve">
<value>Change _Password</value>
</data>
<data name="StrLblSingleProcessText" xml:space="preserve">
<value>Single Process</value>
</data>
<data name="StrSuccessMsgRequirementPcbId" xml:space="preserve">
<value>Success: PcbId of Requirements from database and Cordonel successfully validated</value>
</data>
<data name="StrSuccessMsgOrderBasedRequirements" xml:space="preserve">
<value>Success: Order based requirements successfully validated</value>
</data>
<data name="StrSuccessMsgCalibrationPcbId" xml:space="preserve">
<value>Success: PcbId of calibration results from database and Cordonel successfully validated</value>
</data>
<data name="StrSuccessMsgCalibrationCalibration" xml:space="preserve">
<value>Success: Calibration results from database and Cordonel successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementFw" xml:space="preserve">
<value>Success: Requirement and Cordonel Firmware successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementLut" xml:space="preserve">
<value>Success: Requirement and Cordonel LUT successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementMaximumStorageMonth" xml:space="preserve">
<value>Success: Requirement for maximum storage time kept</value>
</data>
<data name="StrSuccessMsgRequirementMeterSize" xml:space="preserve">
<value>Success: Requirement and Cordonel meter size successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementProductionOrder" xml:space="preserve">
<value>Success: Requirement production order number successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementRegion" xml:space="preserve">
<value>Success: Requirement Region successfully validated</value>
</data>
<data name="StrSuccessMsgRequirementActive" xml:space="preserve">
<value>Success: Standard requirement approved for production</value>
</data>
<data name="StrWarningMsgSpecialRequirementActive" xml:space="preserve">
<value>ATTENTION: Special requirement setup and approved for production</value>
</data>
<data name="StrSuccessMsgRequirementRemainingBatteryLoad" xml:space="preserve">
<value>Success: Minimum battery load is in range</value>
</data>
<data name="StrSuccessMsgDbToMeterEncryptionKey" xml:space="preserve">
<value>Success: Database with Cordonel radio encryption key successfully crosschecked</value>
</data>
<data name="StrMsgDrainedBattery" xml:space="preserve">
<value>Drained battery load</value>
</data>
<data name="StrMsgAlternativeDrainedBatteryCalculation" xml:space="preserve">
<value>Calculation based on quiescent current and production supplement</value>
</data>
<data name="StrErrorMsgThresholdDrainedBattery" xml:space="preserve">
<value>ERROR: Maximum drained battery load overflow</value>
</data>
<data name="StrMsgRequirementAbsolutLimitDrainedBattery" xml:space="preserve">
<value>Absolut restriction</value>
</data>
<data name="StrMsgRequirementLimitDrainedBattery" xml:space="preserve">
<value>Maximum allowed</value>
</data>
<data name="StrMsgRemainingLifeTime" xml:space="preserve">
<value>Estimated remaining lifetime</value>
</data>
<data name="StrErrorMsgThresholdRemainingLifeTime" xml:space="preserve">
<value>ERROR: Minimum required lifetime underflow</value>
</data>
<data name="StrErrorMsgBatteryLoadCalculation" xml:space="preserve">
<value>ERROR: Failed to calculate battery load</value>
</data>
<data name="StrSuccessMsgThresholdRemainingLifeTime" xml:space="preserve">
<value>Success: Minimum required lifetime is in range</value>
</data>
<data name="StrMsgRequiredLifeTime" xml:space="preserve">
<value>Minimum required lifetime</value>
</data>
<data name="StrMsgYears" xml:space="preserve">
<value>years</value>
</data>
<data name="StrEolSoftwareNameVersion" xml:space="preserve">
<value>Cordonel - Shipping Preparation - Version</value>
</data>
<data name="StrPickingSoftwareNameVersion" xml:space="preserve">
<value>Cordonel - Picking - Version</value>
</data>
<data name="StrInstructionScanHousing" xml:space="preserve">
<value>Scan housing</value>
</data>
<data name="StrInstructionScanOrder" xml:space="preserve">
<value>Scan order</value>
</data>
<data name="StrDateTime" xml:space="preserve">
<value>Date</value>
</data>
<data name="StrInstructionReturnPapers" xml:space="preserve">
<value>Attach return report to Cordonel and wait for clarification by QS!</value>
</data>
<data name="StrInfoSuccessPapers" xml:space="preserve">
<value>Cordonel is ready for shipment! This report is for informational use.</value>
</data>
<data name="StrInstructionSuccessPapers" xml:space="preserve">
<value>Optional: Print report for informational use.</value>
</data>
<data name="StrErrorMsgCordonelInvestigate" xml:space="preserve">
<value>Cordonel needs further investigation! Details can be observed in the info window.</value>
</data>
<data name="StrInstructionStop" xml:space="preserve">
<value>Cancel process without report!</value>
</data>
<data name="StrInstructionMeterBoxing" xml:space="preserve">
<value>Pack Cordonel!</value>
</data>
<data name="StrInfoMeterBoxing" xml:space="preserve">
<value>Cordonel successfully passed all tests!</value>
</data>
<data name="StrErrorMsgEolProcess" xml:space="preserve">
<value>Shipping preparation failed!</value>
</data>
<data name="StrMsgPwdRequest" xml:space="preserve">
<value>Request passwords</value>
</data>
<data name="StrMsgPwdFileGeneration" xml:space="preserve">
<value>Generate password file</value>
</data>
<data name="StrMsgPwdFileWrite" xml:space="preserve">
<value>Install password file</value>
</data>
<data name="StrMsgPwdUpdateToDb" xml:space="preserve">
<value>Update password to data base</value>
</data>
<data name="StrMsgPwdLogoff" xml:space="preserve">
<value>Cordonel logout</value>
</data>
<data name="StrMsgPwdValidating" xml:space="preserve">
<value>Cordonel login</value>
</data>
<data name="StrSkippedMsgProcess" xml:space="preserve">
<value>Skipped</value>
</data>
<data name="StrLoginMsg" xml:space="preserve">
<value>Login</value>
</data>
<data name="StrChangePasswordMsg" xml:space="preserve">
<value>Password changing</value>
</data>
</root>
@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Xylem.Common.Production.ProductionUiCordonel.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Some files were not shown because too many files have changed in this diff Show More