common/Production/ProductionUiCordonel.UnitTests/Steps/CommonProductionProcessSteps.cs

1991 lines
85 KiB
C#
Raw Permalink Normal View History

2026-04-23 15:50:07 +00:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using LaaPackages.Features.Cordonel.Models;
using LaaPackages.Features.Cordonel.Models.Values;
using Logic.ProductionToProductMapper.Files.Fw;
using NUnit.Framework;
using NCrunch.Framework;
using Newtonsoft.Json;
using TechTalk.SpecFlow;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus;
using Xylem.Common.Hardware.WaterMeter.Genesis.MockGenesis;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Logic.ProductionOrderCore;
using Xylem.Common.Production.ProductionUiCordonel.Model;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses;
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses;
using Xylem.Common.Utils.ClassAccess;
namespace Xylem.Common.Production.ProductionUiCordonel.UnitTests.Steps
{
[Binding]
public class CommonSteps
{
#region -------------------------------- Production State Properties ------------------------------------------
/// <summary>
/// Name of the method to be tested:
/// - This will be used to assign dynamic a function to be checked,
/// - If it is null or empty, it is not assigned.
/// </summary>
private String MethodToTest
{
set; get;
}
/// <summary>
/// Marker for a private method.
/// </summary>
private Boolean IsPrivateMethod
{
set; get;
}
/// <summary>
/// Name of the property to be checked:
/// - This will be used to assign dynamic a property to be checked.
/// </summary>
private String PropertyToCheck
{
set; get;
}
/// <summary>
/// Name the object type holding the results of the test including the property:
/// - This will be used to assign dynamic a property to be checked.
/// </summary>
private String ObjectTypeName
{
set; get;
}
/// <summary>
/// Required to test the EOL progress and combine it with the individual result:
/// - The required EOL progress will be crosschecked with the result of the EOL progress by eventually
/// several individual methods or by preceding production processes.
/// </summary>
private Boolean TestEolProgress
{
set; get;
}
/// <summary>
/// The name of the production process to test. This is needed to call the correct type in the
/// <see cref="ExecTests"></see>
/// </summary>
private String TestName
{
set; get;
}
/// <summary>
/// The exec path of the assembly for debug in environment.
/// </summary>
private readonly String _assemblyExecRootPath =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "";
/// <summary>
/// Fw package path relative to solution main folder.
/// </summary>
private readonly String _mockFwPackageRootPath;
/// <summary>
/// Raw initialization parameters (unsorted and unfiltered for Order, VAKO and CSD priority) path relative to
/// solution main folder.
/// </summary>
private readonly String _mockRawParamsRootPath;
/// <summary>
/// Test result container
/// </summary>
private struct TestResult
{
public Int32 TestNo
{
set; get;
}
public EOLProgressModel EOLProgress
{
set; get;
}
/// <summary>
/// Used if property cannot be checked by an EOL progress.
/// </summary>
public StatusReturn StatusReturn
{
set; get;
}
/// <summary>
/// Feedback message from tested routines
/// </summary>
public String FeedbackMsg
{
set; get;
}
}
/// <summary>
/// Test definition container for test-table entries
/// </summary>
private struct TestRequirement
{
public Int32 TestNo
{
set; get;
}
public String Comment
{
set; get;
}
public MockGenesis? Meter
{
set; get;
}
/// <summary>
/// Production requirements for check of production status and special requirements
/// </summary>
public CordonelRequirements ProductionRequirements
{
set; get;
}
/// <summary>
/// EOL Progress as input where every single EOL property will be used to set the expected feedback
/// to fulfill the single test.
/// </summary>
public EOLProgressModel RequiredEOLProgress
{
set; get;
}
/// <summary>
/// Status of the Cordonel
/// </summary>
public GenesisStatus CordonelDeviceStatus
{
set; get;
}
/// <summary>
/// Calibration register restorer of Cordonel
/// </summary>
public RegisterRestorer CalibCheckRegisterRestorer
{
set; get;
}
/// <summary>
/// Register restorer of Cordonel for parametrization
/// </summary>
public RegisterRestorer RegisterRestorer
{
set; get;
}
/// <summary>
/// Production status of the Cordonel
/// </summary>
public List<String> TestBenchState
{
set; get;
}
/// <summary>
/// Used for partially or full compare on string result return
/// </summary>
public Boolean PartiallyCompareStringResult
{
set; get;
}
/// <summary>
/// Loaded FW package from local disk
/// </summary>
public List<FilePart> FwUpdatePackage
{
set; get;
}
/// <summary>
/// Used if property cannot be checked by an EOL progress.
/// </summary>
public StatusReturn RequiredStatusReturn
{
set; get;
}
/// <summary>
/// Control to fail the Kitron radio param test
/// </summary>
public Boolean KillRadioParam
{
set; get;
}
}
/// <summary>
/// List of names needed to be executed in advance to the real test.
/// </summary>
private readonly List<String> _ppNamesExecSequence = new List<String>();
/// <summary>
/// Test cases are the individual tests
/// </summary>
private readonly List<TestRequirement> _testCases = new List<TestRequirement>();
/// <summary>
/// Test results after execution
/// </summary>
private readonly List<TestResult> _testResults = new List<TestResult>();
/// <summary>
/// Presets needed to pass preceding tests or initialize some properties to a defined state
/// </summary>
private String _presetTestCaseJson = "";
/// <summary>
/// Presets needed to pass preceding tests or initialize some properties to a defined state
/// </summary>
private TestRequirement _presetTestCase;
#endregion ----------------------------- Production State Properties ------------------------------------------
#region -------------------------------- Ctor -----------------------------------------------------------------
/// <summary>
/// Ctor to crete directories depending on test runner.
/// </summary>
/// <remarks date="2024-Dec-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public CommonSteps()
{
MethodToTest = "";
PropertyToCheck = "";
ObjectTypeName = "";
TestName = "";
var subFolder = Path.Combine("Hardware", "WaterMeter", "Genesis", "MockGenesis");
var solutionPath = "";
if (NCrunchEnvironment.NCrunchIsResident())
{
solutionPath = NCrunchEnvironment.GetOriginalSolutionPath().Replace("Common.sln", "");
}
else
{
if (!string.IsNullOrWhiteSpace(_assemblyExecRootPath))
{
// We are in the Unit test folder "..\Common\Production\ProductionUiCordonel.UnitTests\bin\Debug"
// and need to go back to "..\Common"
var directoryInfo = Directory.GetParent(_assemblyExecRootPath)?.Parent?.Parent?.Parent?.Parent;
if (directoryInfo != null)
{
solutionPath = directoryInfo.FullName;
}
}
}
_mockFwPackageRootPath = Path.Combine(solutionPath, subFolder, "MockFwExamples");
_mockRawParamsRootPath = Path.Combine(solutionPath, subFolder, "MockRawParamsExamples");
Console.WriteLine($@"UNIT TEST MODULE: Search path for MockFwExamples: {_mockFwPackageRootPath}");
Console.WriteLine($@"UNIT TEST MODULE: Search path for MockRawParamsExamples: {_mockRawParamsRootPath}");
}
#endregion ----------------------------- Ctor -----------------------------------------------------------------
#region -------------------------------- Test definition input interfaces from *.feature files ----------------
// Multiple *.feature files are going to use those step definitions
[Given(@"Initialize new test scenario for production process '(.*)'")]
public void InitializeNewTestScenario(String productionProcessName)
{
TestName = productionProcessName;
// As here a new test step will be initialized and possibly the test-case table needs to be pre-filled for
// more complex tests of a huge routine to keep running until the real test to be executed will be reached,
// the test-cases and results have to be cleared here!
_testCases.Clear();
_testResults.Clear();
_ppNamesExecSequence.Clear();
_presetTestCaseJson = "";
}
[Given(@"Name the property to be checked for '(.*)'")]
public void GivenNameThePropertyToBeCheckedFor(String propertyName)
{
PropertyToCheck = propertyName;
}
[Given(@"Name the object type holding the property '(.*)'")]
public void GivenNameTheObjectTypeHoldingTheProperty(String objTypeName)
{
ObjectTypeName = objTypeName;
}
[Given(@"Name the private function to be checked for functionality '(.*)'")]
public void GivenNameThePrivateFunctionToBeCheckedForFunctionality(String functionToCheck)
{
MethodToTest = functionToCheck;
IsPrivateMethod = true;
}
[Given(@"Name the public function to be checked for functionality '(.*)'")]
public void GivenNameThePublicFunctionToBeCheckedForFunctionality(String functionToCheck)
{
MethodToTest = functionToCheck;
IsPrivateMethod = false;
}
[Given(@"Activate EOL progress check")]
public void GivenActivateEOLProgressCheck()
{
TestEolProgress = true;
}
[Given(@"Define production process for sequencing before test '(.*)'")]
public void GivenDefineForSequencingBeforeTest(String ppNameExecSequence)
{
_ppNamesExecSequence.Add(ppNameExecSequence);
}
[Given(@"Input initializations for the test matrix and presets to pass the preceding tests")]
public void GivenInputInitializationsForTheTestMatrixAndPresetsToPassThePrecedingTests(Table table)
{
ReadPresetTable(table);
}
[Given(@"Input matrix defining the test cases and results")]
public void GivenInputMatrixDefiningTheTestCasesAndResults(Table table)
{
ReadTestTable(table);
}
[When(@"Execute the generic tests")]
public void WhenExecuteTheGenericTests()
{
ExecTests();
}
[Then(@"Check the generic test results")]
public void ThenCheckTheGenericTestResults()
{
VerifyTests();
}
#endregion ----------------------------- Test definition input interfaces from *.feature file -----------------
#region -------------------------------- Common ---------------------------------------------------------------
/// <summary>
/// Verify all test cases and compare required-result with result coming from the method under test.
/// </summary>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-09" author="Thomas Wiedebusch">
/// - Check entire EOL progress on request as this defines which tests have been executed or skipped.
/// </remarks>
/// <remarks date="2024-Dec-10" author="Thomas Wiedebusch">
/// - Avoid string being null, instead set it to empty string "".
/// </remarks>
private void VerifyTests()
{
// Loop all test cases
foreach (var testCase in _testCases)
{
Boolean status;
var strTestCaseExpectedStatus = "";
var strTestResultStatus = "";
// For StatusReturn the property to check is null
var strPropertyToCheck =
PropertyToCheck == null ? "" : $" of the Property: \"{PropertyToCheck}\"";
var strErrorMsgForGetPropertyCall =
$"TestNo: {testCase.TestNo}\n" +
$"Could not access property value {strPropertyToCheck} of the Object: \"{ObjectTypeName}\"";
// Loop test cases
switch (ObjectTypeName)
{
case "EOLStatus":
status = ClassAccess.GetPropertyValue<EOLStatus, EOLProgressModel>(testCase.RequiredEOLProgress,
PropertyToCheck,
out var eolStatus);
status.Should().BeTrue(strErrorMsgForGetPropertyCall);
strTestCaseExpectedStatus = GetEOLStatusNameFromStatus(eolStatus);
break;
case "StringReturn":
status = ClassAccess.GetPropertyValue<String, EOLProgressModel>(testCase.RequiredEOLProgress,
PropertyToCheck,
out var strStringResult);
status.Should().BeTrue(strErrorMsgForGetPropertyCall);
strTestCaseExpectedStatus = strStringResult;
break;
case "StatusReturn":
strTestCaseExpectedStatus = GetStatusReturnNameFromStatus(testCase.RequiredStatusReturn);
break;
}
// Find the according test result based on the TestNo
foreach (var testResult in _testResults.Where(t => t.TestNo == testCase.TestNo))
{
switch (ObjectTypeName)
{
case "EOLStatus":
status = ClassAccess.GetPropertyValue<EOLStatus, EOLProgressModel>(testResult.EOLProgress,
PropertyToCheck, out var eolStatus);
status.Should().BeTrue(strErrorMsgForGetPropertyCall);
strTestResultStatus = GetEOLStatusNameFromStatus(eolStatus);
break;
case "StringReturn":
status = ClassAccess.GetPropertyValue<String, EOLProgressModel>(testResult.EOLProgress,
PropertyToCheck,
out var strStringResult);
status.Should().BeTrue(strErrorMsgForGetPropertyCall);
strTestResultStatus = strStringResult;
break;
case "StatusReturn":
strTestResultStatus = GetStatusReturnNameFromStatus(testResult.StatusReturn);
break;
}
Boolean doesMatchCompare;
String strResultHeader;
// Check optional entire sequenced EOL progress
if (TestEolProgress)
{
doesMatchCompare =
EOLHandler.CompareEolProgresses(testCase.RequiredEOLProgress, testResult.EOLProgress,
out _);
strResultHeader =
$"TestNo: {testCase.TestNo}\n" +
$"Test Comment: \"{testCase.Comment}\"\n";
Assert.That(doesMatchCompare, strResultHeader +
@"ERROR: Expected EOL status doesn't match the result EOL status");
Console.WriteLine(strResultHeader +
@"SUCCESS: Expected EOL status equals the result EOL status");
}
if (ObjectTypeName.Equals("StringReturn"))
{
// Avoid null for strings
if (string.IsNullOrEmpty(strTestResultStatus))
strTestResultStatus = "";
if (string.IsNullOrEmpty(strTestCaseExpectedStatus))
strTestCaseExpectedStatus = "";
}
doesMatchCompare = testCase.PartiallyCompareStringResult
? strTestResultStatus.Contains(strTestCaseExpectedStatus)
: strTestResultStatus.Equals(strTestCaseExpectedStatus);
// Generate an appropriate message as result for debug
strResultHeader =
!TestEolProgress ? $"TestNo: {testCase.TestNo}\n" +
$"Test Comment: \"{testCase.Comment}\"" : "" +
$"Test Result Status: \"{strTestResultStatus}\"" +
strPropertyToCheck + $" of the Object: \"{ObjectTypeName}\"";
var strMethodFeedbackMsg = "";
if (!string.IsNullOrEmpty(testResult.FeedbackMsg))
strMethodFeedbackMsg = $"\nFeedback message from method: \"{testResult.FeedbackMsg}\"";
Assert.That(doesMatchCompare, strResultHeader +
$"\nERROR: Expected Status: \"{strTestCaseExpectedStatus}\"" +
$" - Test Result Status: \"{strTestResultStatus}\"" +
strMethodFeedbackMsg);
// Success output for verbosity
var strResultSuccess = strResultHeader +
$"\nSUCCESS: Expected Status: \"{strTestCaseExpectedStatus}\"" +
$" - Test Result Status: \"{strTestResultStatus}\"" + strMethodFeedbackMsg + "\n";
Console.WriteLine(strResultSuccess);
}
}
}
/// <summary>
/// Tests execution for all defined test in test data table
/// </summary>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-06" author="Thomas Wiedebusch">
/// - Branch to production process type, fill with special objects and use CommonCallClassCheck.
/// </remarks>
/// <remarks date="2024-Dec-12" author="Thomas Wiedebusch">
/// - Init the test result depending on test case and production process.
/// </remarks>
/// <remarks date="2025-Feb-17" author="Thomas Wiedebusch">
/// - Improved parameter load.
/// </remarks>
/// <remarks date="2025-Mar-11" author="Thomas Wiedebusch">
/// - Test the missing parameter-file and the missing CSD in registerRestorer,
/// - Improved load of unmodified parameters.
/// </remarks>
private void ExecTests()
{
var regionForParams = "?";
var meterSizeForParams = "?";
var unmodifiedParamsUsableAsReferenceForNextTest = false;
var intermediateRecoveryRegisters = new List<RecoveryRegisterItem>();
foreach (var testCase in _testCases)
{
// Prepare one test result for an entire sequence to test
var testResult = new TestResult
{
TestNo = testCase.TestNo,
EOLProgress = new EOLProgressModel
{
Id = testCase.RequiredEOLProgress.Id,
PcbId = testCase.RequiredEOLProgress.PcbId,
StartDate = testCase.RequiredEOLProgress.StartDate
}
};
// Put the test production process at the end of the list of production processes needed to be executed
// in advance to this test but avoid a multiple input
if (_ppNamesExecSequence.All(pp => !pp.Contains(TestName)))
_ppNamesExecSequence.Add(TestName);
// Marker if parameter check may pass the test
var paramsCheckWillSucceed = true;
// Execute a test sequence for a complex test
foreach (var ppName in _ppNamesExecSequence)
{
IProductionProcess testPp;
switch (ppName)
{
case "CheckProductionState":
testPp = new CheckProductionState("UnitTestCheckProductionState")
{
// Assign test bench state
TestBenchState = testCase.TestBenchState,
// Assign calibration values
CalibCheckRegisterRestorer = testCase.CalibCheckRegisterRestorer,
// Assign firmware package
FwUpdatePackage = testCase.FwUpdatePackage
};
CommonCallClassCheck<CheckProductionState>(testPp, testCase,
ref testResult);
break;
case "ExecFinalParametrization":
// Load parameters
if (meterSizeForParams != testCase.Meter?.MeterSize || regionForParams != testCase.Meter?.Region ||
!unmodifiedParamsUsableAsReferenceForNextTest)
{
var rawProgParams = new List<ProgrammingParameters>();
if (!RegisterRestorer.ImportRawProgrammingParametersFromFile(_mockRawParamsRootPath,
testCase.Meter?.Region, testCase.Meter?.MeterSize, rawProgParams))
{
var errorMsg = $"TestNo: {testCase.TestNo}\n ERROR: Could not load programming parameters\n";
Console.WriteLine(errorMsg);
// Force failed test as parameters missing
testCase.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
testCase.RequiredEOLProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
}
else if (StatusReturn.Failed == RegisterRestorer.RawProgrammingParametersFilterAndSort(rawProgParams,
testCase.RegisterRestorer.RecoveryRegisters, out _, fieldUpdate: false))
{
var errorMsg = $"TestNo: {testCase.TestNo}\n ERROR: Missing programming parameters (e.g. CSD)\n";
Console.WriteLine(errorMsg);
// Force failed test as CSD parameters missing
testCase.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
testCase.RequiredEOLProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
}
intermediateRecoveryRegisters.Clear();
intermediateRecoveryRegisters.AddRange(testCase.RegisterRestorer.RecoveryRegisters);
meterSizeForParams = testCase.Meter?.MeterSize;
regionForParams = testCase.Meter?.Region;
unmodifiedParamsUsableAsReferenceForNextTest = true;
}
// Take the parameters from the last run if those haven't changed meanwhile (identical meter size and region)
else
{
testCase.RegisterRestorer.RecoveryRegisters = intermediateRecoveryRegisters;
}
if (testCase.RequiredEOLProgress.ParametrizationDoneChecked == EOLStatus.FAIL)
{
// Modify one parameter to fail the writing as it is out of range
testCase.RegisterRestorer.RecoveryRegisters.Add(
new RecoveryRegisterItem("GENESISFLOW_MeterSize", new Byte[] { 0xFF }));
unmodifiedParamsUsableAsReferenceForNextTest = false;
}
else if (testCase.KillRadioParam)
{
var removeProPar = testCase.RegisterRestorer.RecoveryRegisters.LastOrDefault(
x => x.RegisterIdent.Equals("SENSUSRADIO_PowerLevel"));
testCase.RegisterRestorer.RecoveryRegisters.Remove(removeProPar);
testCase.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.OK;
}
else
{
testCase.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.OK;
}
testPp = new ExecFinalParametrization("UnitTestExecFinalParametrization");
testPp.RegisterRestorer = testCase.RegisterRestorer;
CommonCallClassCheck<ExecFinalParametrization>(testPp, testCase, ref testResult);
break;
case "ExecStoreConfiguration":
// Avoid store configuration
if (testCase.RequiredEOLProgress.StoreConfigurationDoneChecked != EOLStatus.FAIL)
{
testCase.RequiredEOLProgress.StoreConfigurationDoneChecked = EOLStatus.OK;
testPp = new ExecStoreConfiguration("UnitTestExecStoreConfiguration");
CommonCallClassCheck<ExecStoreConfiguration>(testPp, testCase, ref testResult);
}
else
{
// Indication that the process wasn't executed
testCase.RequiredEOLProgress.StoreConfigurationDoneChecked = EOLStatus.NULL;
testResult.StatusReturn = StatusReturn.Failed;
paramsCheckWillSucceed = false;
}
break;
case "ExecRebootCordonel":
// Avoid reboot if failing is required
if (testCase.RequiredEOLProgress.RebootDoneChecked != EOLStatus.FAIL)
{
testCase.RequiredEOLProgress.RebootDoneChecked = EOLStatus.OK;
testPp = new ExecRebootCordonel("UnitTestExecReboot");
CommonCallClassCheck<ExecRebootCordonel>(testPp, testCase, ref testResult);
}
else
{
// Indication that the process wasn't executed
testCase.RequiredEOLProgress.RebootDoneChecked = EOLStatus.NULL;
testResult.StatusReturn = StatusReturn.Failed;
}
if (string.IsNullOrEmpty(testResult.EOLProgress.ProductionStatusFwVersion))
testResult.EOLProgress.ProductionStatusFwVersion = testCase.Meter?.FwVersion;
break;
case "CheckFinalParametrization":
// Modify one parameter to fail the compare
if (testCase.RequiredEOLProgress.ParametrizationCompareChecked == EOLStatus.FAIL)
{
var registerName = "SYSTEM_UpgradePermissions";
var readValue = RegisterConverter.ByteArrayToValue<UInt32>(
testCase.Meter?.ReadRegister(registerName));
readValue++;
testCase.Meter?.WriteRegister(registerName, readValue);
paramsCheckWillSucceed = false;
}
testCase.RequiredEOLProgress.ParametrizationCompareChecked =
paramsCheckWillSucceed ? EOLStatus.OK : EOLStatus.FAIL;
testPp = new CheckFinalParametrization("UnitTestCheckFinalParametrization");
CommonCallClassCheck<CheckFinalParametrization>(testPp, testCase, ref testResult);
break;
}
}
_testResults.Add(testResult);
}
}
/// <summary>
/// Common class access to take the individual production processes into account
/// </summary>
/// <typeparam name="TInstance"></typeparam>
/// <param name="testPp"></param>
/// <param name="testCase"></param>
/// <param name="testResult"></param>
/// <remarks date="2024-Dec-06" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void CommonCallClassCheck<TInstance>(IProductionProcess testPp, TestRequirement testCase,
ref TestResult testResult)
{
// Results filled by real function calls
testPp.EolProgress = testResult.EOLProgress;
// Assign objects filled with the values from the input table
testPp.Meter = testCase.Meter;
testPp.ProductionRequirements = testCase.ProductionRequirements;
testPp.ProductionStatus = testCase.CordonelDeviceStatus;
testPp.RegisterRestorer = testCase.RegisterRestorer;
// The new Object?[]{null} is a placeholder for the out return if needed for the method type reflection
var parameters = new Object?[] { null };
// Public methods do NOT return a variable
if (!IsPrivateMethod)
parameters = null;
var statusReturn = StatusReturn.Unknown;
// Call the function which should be tested
switch (ObjectTypeName)
{
// Call the function to be tested
case "EOLStatus":
ClassAccess.CallMethode<TInstance, EOLStatus>((TInstance)testPp, MethodToTest, parameters,
IsPrivateMethod);
break;
case "StringReturn":
ClassAccess.CallMethode<TInstance, String>((TInstance)testPp, MethodToTest, parameters,
IsPrivateMethod);
break;
case "StatusReturn":
statusReturn = ClassAccess.CallMethode<TInstance, StatusReturn>((TInstance)testPp,
MethodToTest, parameters, IsPrivateMethod);
break;
}
// Status return failed will have the highest priority and will be kept for a sequence if once set
// as testResult is a reference to the sequences test
if (testResult.StatusReturn != StatusReturn.Failed)
testResult.StatusReturn = statusReturn;
if (parameters?[0] is String)
testResult.FeedbackMsg += $"{parameters[0]} ";
}
/// <summary>
/// Change status return enum to string
/// </summary>
/// <param name="statusReturn"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static String GetStatusReturnNameFromStatus(StatusReturn statusReturn)
{
if (statusReturn == StatusReturn.Skipped)
return "Skipped";
if (statusReturn == StatusReturn.Okay)
return "Okay";
if (statusReturn == StatusReturn.Failed)
return "Failed";
if (statusReturn == StatusReturn.Warning)
return "Warning";
return "Unknown";
}
/// <summary>
/// Change naming string to status
/// </summary>
/// <param name="strStatusReturn"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static StatusReturn GetStatusReturnFromName(String strStatusReturn)
{
switch (strStatusReturn)
{
case "Okay":
return StatusReturn.Okay;
case "Skipped":
return StatusReturn.Skipped;
case "Failed":
return StatusReturn.Failed;
case "Warning":
return StatusReturn.Warning;
default:
return StatusReturn.Unknown;
}
}
/// <summary>
/// Change naming string to CSD Status
/// </summary>
/// <param name="strCsdStatusReturn"></param>
/// <returns></returns>
/// <remarks date="2025-Nov-25" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static CSDStatus GetCsdStatusReturnFromName(String strCsdStatusReturn)
{
switch (strCsdStatusReturn)
{
case "Blocked":
return CSDStatus.Blocked;
case "New":
return CSDStatus.New;
case "Known":
return CSDStatus.Known;
case "Released":
return CSDStatus.Released;
default:
return CSDStatus.Null;
}
}
/// <summary>
/// Change EOLStatus enum to string
/// </summary>
/// <param name="eolStatus"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static String GetEOLStatusNameFromStatus(EOLStatus eolStatus)
{
if (eolStatus == EOLStatus.OK)
return "Okay";
// NA stands for not skipped test by requirement or region
if (eolStatus == EOLStatus.NA)
return "Skipped";
if (eolStatus == EOLStatus.FAIL)
return "Failed";
return "NOT EXECUTED";
}
/// <summary>
/// Change naming string to status
/// </summary>
/// <param name="strEolStatus"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static EOLStatus GetEOLStatusFromName(String strEolStatus)
{
switch (strEolStatus)
{
case "Okay":
return EOLStatus.OK;
case "Skipped":
return EOLStatus.NA;
case "Failed":
return EOLStatus.FAIL;
default:
return EOLStatus.NULL;
}
}
#endregion ----------------------------- Common ---------------------------------------------------------------
#region -------------------------------- Parse test tables ----------------------------------------------------
/// <summary>
/// Read and parse the test table to the test-requirement struct
/// </summary>
/// <param name="table"></param>
/// <remarks date="2024-Nov-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-10" author="Thomas Wiedebusch">
/// - Login to build App list and registers.
/// </remarks>
/// <remarks date="2025-Nov-21" author="Thomas Wiedebusch">
/// - New object with deep copy of presetTestCase using the Json serializer.
/// </remarks>
/// <remarks date="2025-Nov-22" author="Thomas Wiedebusch">
/// - Moved meter login for new test requirement and register restorers modification to
/// <see cref="NewTestRequirement"/>.
/// </remarks>
/// <remarks date="2025-Nov-25" author="Thomas Wiedebusch">
/// - Init register restorers.
/// </remarks>
/// <remarks date="2025-Nov-26" author="Thomas Wiedebusch">
/// - CSD parsing.
/// </remarks>
private void ReadTestTable(Table table)
{
// Generate list of tests out of the "Given" table and create test-cases
foreach (var row in table.Rows)
{
TestRequirement testCase;
// Check if presets are required, if so copy all properties using the Json.Deserializer to create a
// new object as deep copy!!!
if (_presetTestCase.Meter != null)
{
if (string.IsNullOrEmpty(_presetTestCaseJson))
{
_presetTestCaseJson = JsonConvert.SerializeObject(_presetTestCase);
}
// Create a new object with a deep copy of the _presetTestCase
testCase = JsonConvert.DeserializeObject<TestRequirement>(_presetTestCaseJson);
// Special meter properties which are not include in the deep copy
ClassAccess.CopyAllProperties(testCase.Meter, _presetTestCase.Meter);
testCase.RegisterRestorer = new RegisterRestorer(testCase.Meter);
testCase.CalibCheckRegisterRestorer = new RegisterRestorer(testCase.Meter);
// Enable register compare even if not physically read from meter
testCase.RegisterRestorer.EnableCompareExternalSetRegisters();
testCase.CalibCheckRegisterRestorer.EnableCompareExternalSetRegisters();
// Login is needed for building the MeterAppListVersion and generating the accessible register list.
// This register list will be used to check for valid ranges.
testCase.Meter?.Login();
}
// Preset is not required, a single testCase has to be created
else
{
testCase = NewTestRequirement();
}
// This is the multiple row test table defining individual test cases
ReadTable(row, ref testCase);
_testCases.Add(testCase);
}
}
/// <summary>
/// Read and parse the preset table to the test-requirement struct:
/// - A preset table is limited to exclusively one row as this shall be used for every test-case,
/// - There can be multiple preset tables, so the object has to remain alive.
/// </summary>
/// <param name="table"></param>
/// <remarks date="2024-Nov-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-10" author="Thomas Wiedebusch">
/// - Login to build App list and registers.
/// </remarks>
/// <remarks date="2025-Nov-22" author="Thomas Wiedebusch">
/// - Moved meter login to <see cref="NewTestRequirement"/>.
/// </remarks>
private void ReadPresetTable(Table table)
{
if (_presetTestCase.Meter == null)
{
_presetTestCase = NewTestRequirement();
}
// A preset table is limited to exclusively one row, those settings will be used for every test of the
// test-cases!
ReadTable(table.Rows[0], ref _presetTestCase);
}
/// <summary>
/// Read and parse one table row
/// </summary>
/// <param name="tableRow"></param>
/// <param name="parsedTableRow"></param>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void ReadTable(TableRow tableRow, ref TestRequirement parsedTableRow)
{
#region ----------------------------------- Parse Common Table Fields ---------------------------------------
// This test number needs to be in the very first colum of the test table because this is the reference for
// each individual test and will be used for every subsequent table-parsing.
if (tableRow.TryGetValue("TestNo", out var strValue))
{
var isNumber = int.TryParse(strValue, out var testNo);
if (isNumber)
parsedTableRow.TestNo = testNo;
}
// The comment will be used to describe the test. On failed test this can be used to identify the expected result!
if (tableRow.TryGetValue("Comment", out var strComment))
{
parsedTableRow.Comment = strComment;
}
// "Partial Compare" can be used to check if the StringResult contains the named string if true, else the string
// has to be identical!
if (tableRow.TryGetValue("Partial Compare", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
parsedTableRow.PartiallyCompareStringResult = true;
}
#endregion
#region ----------------------------------- Parse EOL Settings --------------------------------------------
if (ParseInt(tableRow, "EOL Id", out strValue, out var intValue))
{
if (intValue != null)
parsedTableRow.RequiredEOLProgress.Id = (Int32)intValue;
}
var strHeader = "EOL StartDate";
if (tableRow.TryGetValue(strHeader, out strValue))
{
if (strValue.Equals("UTC Now"))
{
parsedTableRow.RequiredEOLProgress.StartDate = DateTime.UtcNow;
}
}
#endregion
#region ----------------------------------- Parse Requirements ---------------------------------------------
if (ParseInt(tableRow, "Standard Id", out strValue, out intValue))
{
parsedTableRow.ProductionRequirements.StandardId = intValue;
parsedTableRow.RequiredEOLProgress.StandardRequirementId = intValue;
parsedTableRow.RequiredEOLProgress.RequirementRequestChecked = EOLStatus.OK;
}
if (ParseInt(tableRow, "Standard Version", out strValue, out intValue))
{
parsedTableRow.ProductionRequirements.StandardVersion = intValue;
parsedTableRow.RequiredEOLProgress.StandardRequirementVersion = intValue;
}
if (ParseInt(tableRow, "Special Id", out strValue, out intValue))
{
parsedTableRow.ProductionRequirements.SpecialId = intValue;
parsedTableRow.RequiredEOLProgress.SpecialRequirementId = intValue;
}
if (ParseInt(tableRow, "Special Version", out strValue, out intValue))
{
parsedTableRow.ProductionRequirements.SpecialVersion = intValue;
parsedTableRow.RequiredEOLProgress.SpecialRequirementVersion = intValue;
}
if (tableRow.TryGetValue("Standard IsActive", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
parsedTableRow.ProductionRequirements.IsStandardActive = true;
}
if (tableRow.TryGetValue("Standard IsApproved", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
parsedTableRow.ProductionRequirements.IsStandardApproved = true;
}
if (tableRow.TryGetValue("Special IsActive", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
parsedTableRow.ProductionRequirements.IsSpecialActive = true;
}
if (tableRow.TryGetValue("Special IsApproved", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
parsedTableRow.ProductionRequirements.IsSpecialApproved = true;
}
#endregion
#region ----------------------------------- Parse Production Order -----------------------------------------
if (ParseInt(tableRow, "Cordonel ProdOrderNo", out strValue, out intValue))
{
if (parsedTableRow.Meter != null && intValue != null)
parsedTableRow.Meter.OrderNumber = (Int32)intValue;
}
if (ParseInt(tableRow, "Requirement ProdOrderNo", out strValue, out intValue))
{
if (intValue != null)
parsedTableRow.ProductionRequirements.ProductionOrderNr = (Int32)intValue;
parsedTableRow.RequiredEOLProgress.RequirementOrderChecked = EOLStatus.OK;
}
#endregion
#region ----------------------------------- Parse CSD -----------------------------------------------------
if (ParseInt(tableRow, "CSD ID", out strValue, out intValue))
{
if (intValue != null)
parsedTableRow.ProductionRequirements.CSD.IdentId = (Int32)intValue;
}
if (ParseString(tableRow, "CSD Name", out strValue))
{
parsedTableRow.ProductionRequirements.CSD.Name = strValue;
}
if (ParseString(tableRow, "CSD Code", out strValue))
{
parsedTableRow.ProductionRequirements.CSD.Code = strValue;
}
if (ParseString(tableRow, "CSD Status", out strValue))
{
parsedTableRow.ProductionRequirements.CSD.Status = GetCsdStatusReturnFromName(strValue);
}
#endregion
#region ----------------------------------- Parse PcbId ---------------------------------------------------
if (ParseInt(tableRow, "Cordonel PcbId", out strValue, out intValue))
{
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.PcbId = strValue;
}
if (ParseInt(tableRow, "Requirement PcbId", out strValue, out intValue))
{
parsedTableRow.ProductionRequirements.PcbId = intValue;
parsedTableRow.RequiredEOLProgress.RequirementPcbIdChecked = EOLStatus.OK;
}
if (ParseInt(tableRow, "EOL PcbId", out strValue, out intValue))
{
if (intValue != null)
parsedTableRow.RequiredEOLProgress.PcbId = (Int32)intValue;
}
#endregion
#region ----------------------------------- Parse LUT CRC ---------------------------------------------------
if (ParseString(tableRow, "Cordonel LUT CRC", out strValue))
{
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.LutCrc = strValue;
}
if (ParseString(tableRow, "Requirement LUT CRC", out strValue))
{
parsedTableRow.ProductionRequirements.LutCrc = strValue;
parsedTableRow.RequiredEOLProgress.ProductionStatusLutCrc = strValue;
}
#endregion
#region ----------------------------------- Parse Region ---------------------------------------------------
if (ParseString(tableRow, "Cordonel Region", out strValue))
{
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.Region = strValue;
}
if (ParseString(tableRow, "Requirement Region", out strValue))
{
parsedTableRow.ProductionRequirements.Region = strValue;
parsedTableRow.RequiredEOLProgress.RequirementRegionChecked = EOLStatus.OK;
}
#endregion
#region ----------------------------------- Parse Meter Size ----------------------------------------------
if (ParseString(tableRow, "Cordonel Meter Size", out strValue))
{
//if (parsedTableRow.Meter != null) parsedTableRow.Meter.MeterSize = strValue;
if (parsedTableRow.Meter != null)
{
parsedTableRow.Meter.EnablePropertyChangeAbility(true);
parsedTableRow.Meter.SetMeterSize(strValue);
}
}
if (ParseString(tableRow, "Requirement Meter Size", out strValue))
{
parsedTableRow.ProductionRequirements.MeterSize = strValue;
parsedTableRow.RequiredEOLProgress.ProductionStatusMeterSize = strValue;
}
#endregion
#region ----------------------------------- Parse FW and Apps ---------------------------------------------
// Temporary reminders until all app depending on modifications in one table entry are set
Byte? modifyAppId = null;
var eraseApp = false;
UInt32? appVersion = null;
UInt16? appCrc = null;
if (ParseInt(tableRow, "Modify AppId", out strValue, out intValue))
{
modifyAppId = (Byte?)intValue;
}
if (tableRow.TryGetValue("Erase App", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
eraseApp = true;
}
if (tableRow.TryGetValue("Change App Version", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
appVersion = 0;
}
if (tableRow.TryGetValue("Change App CRC", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
appCrc = 0x0000;
}
if (ParseString(tableRow, "Cordonel FW Version", out strValue))
{
// Login needed to install the FW from hard-coded package description file. This is essential as only
// then one App could be erased or modified
parsedTableRow.Meter?.Login();
parsedTableRow.RequiredEOLProgress.ProductionStatusFwVersion = strValue;
var isInstalled = parsedTableRow.Meter != null && parsedTableRow.Meter.InstallNewMockFw(fwName: strValue);
var errorMsg = $"FW \"{strValue}\" could not be found in \"MockFwExamples\", parsing or " +
"installation failed!";
Assert.That(isInstalled, errorMsg);
}
if (ParseFloat(tableRow, "Cordonel Core Revision", out strValue,
out var floatValue))
{
// Initially needed to install MockFw and set up the initial core revision. If the login will be
// delayed then the "Core Revision" is going to be overwritten from the content of the firmware
// package description file.
if (parsedTableRow.Meter != null)
{
parsedTableRow.Meter.Login();
if (floatValue != null)
parsedTableRow.Meter.CoreRevision = (Int32)(floatValue * 100);
parsedTableRow.Meter.StrCoreRevision = strValue;
}
}
if (ParseString(tableRow, "Requirement FW Version", out strValue))
{
parsedTableRow.ProductionRequirements.FwVersion = strValue;
var isInMockFwExamples = MeterFwUpdate.LoadFwUpdatePackage(_mockFwPackageRootPath, strValue,
parsedTableRow.FwUpdatePackage);
var errorMsg = $"FW \"{strValue}\" could not be found in \"MockFwExamples\" or parsing failed!";
Assert.That(isInMockFwExamples, errorMsg);
}
if (modifyAppId != null)
{
parsedTableRow.Meter?.ModifyMockApp((Byte)modifyAppId, appVersion, appCrc, eraseApp);
}
#endregion
#region ----------------------------------- Parse Storage Months ------------------------------------------
if (ParseFloat(tableRow, "Assembly Months before Now", out strValue, out floatValue))
{
if (floatValue < 0.0)
{
// The seconds will be used to calculate the assembly time
// (seconds * minutes * hours * DaysPerMonth * -1) the minus one creates a positive value as the
// requirement for the "Assembly Months before Now" a negative value is essential.
parsedTableRow.CordonelDeviceStatus.ExceededLifeTime_s =
(UInt64)((Double)floatValue * GenesisStatusHandler.SECONDS_PER_MONTH * -1);
// Assume 1 % usage to have a number. InitialLoad in mAh * Quantity converted to uAs divided by 100 %
parsedTableRow.CordonelDeviceStatus.DrainedBatteryLoad_uAs =
parsedTableRow.CordonelDeviceStatus.InitialBatteryLoad_mAh *
(UInt64)parsedTableRow.CordonelDeviceStatus.BatteryQuantity * 1000 * 3600 /
100; // corresponds to 1% of usage
}
}
if (ParseFloat(tableRow, "Max Storage Months", out strValue, out floatValue))
{
if (floatValue != null)
{
parsedTableRow.ProductionRequirements.MaxStorageMonths = floatValue;
parsedTableRow.RequiredEOLProgress.MaxStorageMonths = floatValue;
parsedTableRow.RequiredEOLProgress.MaxStorageMonthsChecked = EOLStatus.OK;
}
}
#endregion
#region ----------------------------------- Parse Power Consumption ---------------------------------------
if (ParseFloat(tableRow, "Normal Current [uA]", out strValue, out floatValue))
{
// If null, then this will be ignored
parsedTableRow.ProductionRequirements.QuiescentCurrent_uA = floatValue;
}
if (ParseFloat(tableRow, "Production Supplement [%]", out strValue, out floatValue))
{
// If null, then this will be ignored
parsedTableRow.ProductionRequirements.EstimatedProductionBatteryLoadPercent = floatValue;
}
if (ParseFloat(tableRow, "Actual Lifetime [days]", out strValue, out floatValue))
{
if (floatValue != null)
parsedTableRow.CordonelDeviceStatus.ExceededLifeTime_s =
(UInt64)(floatValue * 24 * 3600);
parsedTableRow.RequiredEOLProgress.ProductionStatusBatteryLoadChecked = EOLStatus.OK;
parsedTableRow.RequiredEOLProgress.ProductionStatusRemainingLifeTimeChecked = EOLStatus.OK;
}
if (ParseFloat(tableRow, "Actual Drained [%]", out strValue, out floatValue))
{
if (floatValue != null)
{
parsedTableRow.CordonelDeviceStatus.DrainedBatteryLoad_uAs =
(UInt64)(floatValue * parsedTableRow.CordonelDeviceStatus.InitialBatteryLoad_mAh *
parsedTableRow.CordonelDeviceStatus.BatteryQuantity * 1000 * 3600 / 100);
parsedTableRow.RequiredEOLProgress.ProductionStatusDrainedBatteryLoad = floatValue;
}
//GenesisStatusHandler.CalculateLifeTime(parsedTableRow.CordonelDeviceStatus);
//parsedTableRow.RequiredEOLProgress.ProductionStatusRemainingLifeTime =
// parsedTableRow.CordonelDeviceStatus.RemainingLifeTimeYears;
}
if (ParseFloat(tableRow, "Max Drained [%]", out strValue,
out floatValue))
{
parsedTableRow.ProductionRequirements.MaxDrainedBatteryLoadPercentShipping = floatValue;
}
if (ParseFloat(tableRow, "Required Lifetime [years]", out strValue,
out floatValue))
{
parsedTableRow.ProductionRequirements.RequiredLifeTimeYearsShipping = floatValue;
}
#endregion
#region ----------------------------------- Parse Calibration ---------------------------------------------
if (ParseString(tableRow, "Skip Calib", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.ProductionRequirements.SkipCalibration = true;
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.NA;
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.NA;
}
else
{
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.OK;
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.OK;
}
}
if (ParseInt(tableRow, "Test Bench Fields", out strValue, out intValue))
{
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.OK;
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.OK;
if (intValue != null)
{
for (var ctr = 0; ctr < intValue; ctr++)
parsedTableRow.TestBenchState.Add("");
}
}
if (ParseInt(tableRow, "Test Bench State[4]", out strValue, out intValue))
{
if (parsedTableRow.TestBenchState.Count >= 5)
if (strValue != null)
parsedTableRow.TestBenchState[4] = strValue;
}
if (ParseInt(tableRow, "Test Bench State[5]", out strValue, out intValue))
{
if (parsedTableRow.TestBenchState.Count >= 6)
if (strValue != null)
parsedTableRow.TestBenchState[5] = strValue;
}
if (ParseInt(tableRow, "Meter Calib Fact 1", out strValue, out intValue))
{
//// Login is needed for building the MeterAppListVersion and generating the accessible register list.
//// This register list will be used to check for valid ranges.
//parsedTableRow.Meter.Login();
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.OK;
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.OK;
if (intValue != null)
{
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor1",
readBackValue: RegisterConverter.ValueToByteArray((UInt16)intValue));
}
}
if (ParseInt(tableRow, "Meter Calib Fact 2", out strValue, out intValue))
{
if (intValue != null)
{
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor2",
readBackValue: RegisterConverter.ValueToByteArray((UInt16)intValue));
}
}
if (ParseInt(tableRow, "Meter Calib Fact 3", out strValue, out intValue))
{
if (intValue != null)
{
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor3",
readBackValue: RegisterConverter.ValueToByteArray((UInt16)intValue));
}
}
if (ParseInt(tableRow, "DB Calib Fact 1", out strValue, out intValue))
{
if (intValue != null)
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor1",
RegisterConverter.ValueToByteArray((UInt16)intValue));
}
if (ParseInt(tableRow, "DB Calib Fact 2", out strValue, out intValue))
{
if (intValue != null)
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor2",
RegisterConverter.ValueToByteArray((UInt16)intValue));
}
if (ParseInt(tableRow, "DB Calib Fact 3", out strValue, out intValue))
{
if (intValue != null)
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor3",
RegisterConverter.ValueToByteArray((UInt16)intValue));
}
#endregion
#region ----------------------------------- Parse Kill settings -------------------------------------------
// Use the single selection lock to leave all following settings on default, as the method may exit here
// on the first occurrence and all assumptions for further checks must be skipped.
var singleSelectionLock = false;
if (ParseString(tableRow, "Kill Registers", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.Meter?.KillConfigRegisters();
parsedTableRow.RequiredEOLProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
parsedTableRow.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill WriteParams", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.RequiredEOLProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
parsedTableRow.RequiredEOLProgress.ParametrizationDoneChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill StoreParams", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
// Force to skip store calibration
parsedTableRow.RequiredEOLProgress.StoreConfigurationDoneChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill Reboot", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
// force to skip reboot
parsedTableRow.RequiredEOLProgress.RebootDoneChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill VerifyParams", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.RequiredEOLProgress.ParametrizationCompareChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill EOL", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
parsedTableRow.RequiredEOLProgress.Id = 0;
}
}
if (ParseString(tableRow, "Kill Requ", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.StandardRequirementId = null;
parsedTableRow.RequiredEOLProgress.StandardRequirementVersion = null;
parsedTableRow.RequiredEOLProgress.SpecialRequirementId = null;
parsedTableRow.RequiredEOLProgress.SpecialRequirementVersion = null;
parsedTableRow.RequiredEOLProgress.RequirementRequestChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
parsedTableRow.ProductionRequirements.IsStandardActive = false;
parsedTableRow.RequiredEOLProgress.StandardRequirementId = null;
parsedTableRow.RequiredEOLProgress.StandardRequirementVersion = null;
parsedTableRow.RequiredEOLProgress.SpecialRequirementId = null;
parsedTableRow.RequiredEOLProgress.SpecialRequirementVersion = null;
parsedTableRow.RequiredEOLProgress.RequirementRequestChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill Order", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.RequirementOrderChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X") && !singleSelectionLock)
{
singleSelectionLock = true;
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.OrderNumber = 0;
parsedTableRow.RequiredEOLProgress.RequirementOrderChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill PcbId", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.RequirementPcbIdChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
parsedTableRow.ProductionRequirements.PcbId = 0;
parsedTableRow.RequiredEOLProgress.RequirementPcbIdChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill Region", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.RequirementRegionChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.Region = "?";
parsedTableRow.RequiredEOLProgress.RequirementRegionChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill LUT", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusLutCrc = "";
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.LutCrc = "?";
parsedTableRow.RequiredEOLProgress.ProductionStatusLutCrc = "";
}
}
if (ParseString(tableRow, "Kill Size", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusMeterSize = "";
}
else if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
if (parsedTableRow.Meter != null)
{
parsedTableRow.Meter.EnablePropertyChangeAbility(true);
parsedTableRow.Meter.SetMeterSize("?");
}
parsedTableRow.RequiredEOLProgress.ProductionStatusMeterSize = "";
}
}
if (ParseString(tableRow, "Kill FW", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusFwVersion = "";
}
else if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
singleSelectionLock = true;
parsedTableRow.ProductionRequirements.FwVersion = "?";
parsedTableRow.RequiredEOLProgress.ProductionStatusFwVersion = "";
}
}
if (ParseString(tableRow, "Kill Load", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusBatteryLoadChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.ProductionRequirements.MaxDrainedBatteryLoadPercentShipping = 0;
parsedTableRow.RequiredEOLProgress.ProductionStatusBatteryLoadChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill LifeTime", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusRemainingLifeTimeChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.ProductionRequirements.RequiredLifeTimeYearsShipping = 1000;
parsedTableRow.RequiredEOLProgress.ProductionStatusRemainingLifeTimeChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill StoreTime", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.MaxStorageMonths = null;
parsedTableRow.RequiredEOLProgress.MaxStorageMonthsChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.ProductionRequirements.MaxStorageMonths = 0;
parsedTableRow.RequiredEOLProgress.MaxStorageMonths = 0;
parsedTableRow.RequiredEOLProgress.MaxStorageMonthsChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill PreAdj", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.TestBenchState[4] = "";
parsedTableRow.RequiredEOLProgress.ProductionStatusZeroFlowChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill FlowTest", out strValue))
{
if (singleSelectionLock)
{
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.NULL;
}
else
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X"))
{
parsedTableRow.TestBenchState[5] = "";
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill Calib", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X") && !singleSelectionLock)
{
if (parsedTableRow.Meter != null)
parsedTableRow.Meter.ReLogin();
PresetRegisterRestorer(parsedTableRow.CalibCheckRegisterRestorer, "GENESISFLOW_CalFactor1",
RegisterConverter.ValueToByteArray((UInt16)0), RegisterConverter.ValueToByteArray((UInt16)1));
parsedTableRow.RequiredEOLProgress.ProductionStatusCalibrationChecked = EOLStatus.FAIL;
}
}
if (ParseString(tableRow, "Kill RadioParam", out strValue))
{
if (!string.IsNullOrEmpty(strValue) && strValue.Equals("X") && !singleSelectionLock)
{
parsedTableRow.KillRadioParam = true;
}
}
#endregion
#region ----------------------------------- Parse Stati ---------------------------------------------------
strHeader = "EOL Status";
if (ParseString(tableRow, "EOL Status", out strValue))
{
if (ObjectTypeName.Equals(strHeader.Replace(" ", "")))
{
ClassAccess.SetPropertyValue(parsedTableRow.RequiredEOLProgress, PropertyToCheck,
GetEOLStatusFromName(strValue));
}
}
strHeader = "StringReturn";
if (ParseString(tableRow, strHeader, out strValue))
{
if (ObjectTypeName.Equals(strHeader))
{
ClassAccess.SetPropertyValue(parsedTableRow.RequiredEOLProgress, PropertyToCheck, strValue);
}
}
strHeader = "StatusReturn";
if (ParseString(tableRow, strHeader, out strValue))
{
parsedTableRow.RequiredStatusReturn = GetStatusReturnFromName(strValue);
}
#endregion
}
/// <summary>
/// Preset the register values
/// </summary>
/// <param name="regRestorer"></param>
/// <param name="regName"></param>
/// <param name="writeValue"></param>
/// <param name="readBackValue"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-29" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static void PresetRegisterRestorer(RegisterRestorer regRestorer, String regName,
Byte[]? writeValue = null, Byte[]? readBackValue = null)
{
try
{
// Check if register exists and overwrite content
foreach (var reg in regRestorer.RecoveryRegisters.Where(reg => reg.RegisterIdent == regName))
{
if (writeValue != null)
reg.WriteValue = writeValue;
if (readBackValue != null)
reg.ReadBackValue = readBackValue;
return;
}
regRestorer.RecoveryRegisters.Add(new RecoveryRegisterItem(regName, writeValue, readBackValue));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
/// <summary>
/// Assign new test requirement filled with all properties
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Nov-19" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2025-Nov-22" author="Thomas Wiedebusch">
/// - Init meter and enable change of values for register restorers.
/// </remarks>
/// <remarks date="2025-Nov-26" author="Thomas Wiedebusch">
/// - CSD.
/// </remarks>
private static TestRequirement NewTestRequirement()
{
var testCase = new TestRequirement
{
Meter = new MockGenesis(),
ProductionRequirements = new CordonelRequirements(),
RequiredEOLProgress = new EOLProgressModel(),
FwUpdatePackage = new List<FilePart>(),
TestBenchState = new List<String>(),
CordonelDeviceStatus = new GenesisStatus
{
InitialBatteryLoad_mAh = 19000,
BatteryQuantity = 2
}
};
testCase.ProductionRequirements.CSD = new CordonelCSD();
testCase.RegisterRestorer = new RegisterRestorer(testCase.Meter);
testCase.CalibCheckRegisterRestorer = new RegisterRestorer(testCase.Meter);
// Enable register compare even if not physically read from meter
testCase.RegisterRestorer.EnableCompareExternalSetRegisters();
testCase.CalibCheckRegisterRestorer.EnableCompareExternalSetRegisters();
// Login is needed for building the MeterAppListVersion and generating the accessible register list.
// This register list will be used to check for valid ranges.
testCase.Meter?.Login();
return testCase;
}
/// <summary>
/// Read and parse a floating point number from the table row
/// </summary>
/// <param name="tableRow"></param>
/// <param name="strHeader"></param>
/// <param name="strValue">output read string from table-row</param>
/// <param name="value">output Double? value converted from table-row string</param>
/// <returns>okay if conversion is an Int32 or null</returns>
/// <remarks date="2024-Nov-19" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static Boolean ParseFloat(TableRow tableRow, String strHeader, out String? strValue,
out Double? value)
{
value = null;
var isNumber = false;
if (tableRow.TryGetValue(strHeader, out strValue))
{
if (strValue.Equals("null") || strValue.Equals("\"\""))
{
strValue = null;
return true;
}
isNumber = double.TryParse(strValue, out var parsedValue);
if (isNumber)
{
value = parsedValue;
}
}
return isNumber;
}
/// <summary>
/// Read and parse an integer from the table row
/// </summary>
/// <param name="tableRow"></param>
/// <param name="strHeader"></param>
/// <param name="strValue">output read string from table-row</param>
/// <param name="value">output Int32? value converted from table-row string</param>
/// <returns>okay if conversion is an Int32 or null</returns>
/// <remarks date="2024-Nov-07" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Nov-18" author="Thomas Wiedebusch">
/// - Accept "null" as input.
/// </remarks>
private static Boolean ParseInt(TableRow tableRow, String strHeader, out String? strValue,
out Int32? value)
{
value = null;
var isNumber = false;
if (tableRow.TryGetValue(strHeader, out strValue))
{
if (strValue.Equals("null") || strValue.Equals("\"\""))
{
strValue = null;
return true;
}
isNumber = int.TryParse(strValue, out var parsedValue);
if (isNumber)
{
value = parsedValue;
}
}
return isNumber;
}
/// <summary>
/// Read and parse a string from the table row
/// - If the string is "null" or "\"\"" it will be set to empty string.
/// </summary>
/// <param name="tableRow"></param>
/// <param name="strHeader"></param>
/// <param name="strValue">output read string from table-row</param>
/// <returns>okay if conversion is a String or null</returns>
/// <remarks date="2024-Nov-18" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-10" author="Thomas Wiedebusch">
/// - Returns empty string "" instead of null.
/// </remarks>
private static Boolean ParseString(TableRow tableRow, String strHeader, out String strValue)
{
if (tableRow.TryGetValue(strHeader, out strValue))
{
if (strValue.Equals("null") || strValue.Equals("\"\"") || strValue.Equals(""))
{
strValue = "";
}
return true;
}
return false;
}
#endregion --------------------------------- Parse test tables ------------------------------------------------
#region ------------------------------------ Tools ------------------------------------------------------------
/// <summary>
/// Copy entire directory with all elements from source to destination directory and overwrite files on command
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="destinationPath"></param>
/// <param name="overwrite">overwrite file if exist</param>
/// <returns></returns>
/// <remarks date="2024-Dec-13" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void CopyEntireDirectory(String sourcePath, String destinationPath, Boolean overwrite)
{
if (string.IsNullOrEmpty(sourcePath) ||
string.IsNullOrEmpty(destinationPath) ||
!Directory.Exists(sourcePath))
return;
try
{
// First build the subdirectories in the destination folder
foreach (var path in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(path.Replace(sourcePath, destinationPath));
}
// Get all files including the absolute path
var sourceFilePathNames = Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories);
// Build destination
foreach (var sourceFilePathName in sourceFilePathNames)
{
var destinationFilePathName = sourceFilePathName.Replace(sourcePath, destinationPath);
// Skip if file already exists and should not be overwritten
if (!overwrite && !File.Exists(destinationFilePathName))
File.Copy(sourceFilePathName, destinationFilePathName, overwrite);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
#endregion --------------------------------- Tools ------------------------------------------------------------
}
}