diff --git a/Common/LaaPackages b/Common/LaaPackages index ce1dc260..e7aa3e27 160000 --- a/Common/LaaPackages +++ b/Common/LaaPackages @@ -1 +1 @@ -Subproject commit ce1dc26047374fd7a9328914a6c2bd7eb591f012 +Subproject commit e7aa3e2723a435f0a714df661dc5d86dfd45a71c diff --git a/Common/Production/ProductionUiCordonel/App.config b/Common/Production/ProductionUiCordonel/App.config index 728a665b..c940b21c 100644 --- a/Common/Production/ProductionUiCordonel/App.config +++ b/Common/Production/ProductionUiCordonel/App.config @@ -34,7 +34,7 @@ - + diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs index 563b2bb1..2bd5c005 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/CheckProductionState.cs @@ -727,7 +727,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr { if (EolProgress != null) EolProgress.RequirementPcbIdChecked = EOLStatus.FAIL; - feedbackMsg = Resources.StrSuccessMsgOrderBasedRequirements; + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; return EOLStatus.FAIL; } @@ -1034,14 +1034,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // Create an output message addon for alternative power consumption calculation var strAlternativeCalc = ProductionStatus.AlternativeRequiredBatteryLoadPercent == null ? "" - : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBatteryAtEol}: " + + : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBattery}: " + $"{ProductionRequirements.MaxDrainedBatteryLoadPercentShipping:F2} %" + $" ({Resources.StrMsgAlternativeDrainedBatteryCalculation})"; - var batteryLoad = $"{Resources.StrMsgDrainedBatteryAtEol}: " + + var batteryLoad = $"{Resources.StrMsgDrainedBattery}: " + // Actual calculated battery load based on readings from meter $"{ProductionStatus.DrainedBatteryLoadPercent:F2} %" + - $" - {Resources.StrMsgRequirementLimitDrainedBatteryAtEol}: " + + $" - {Resources.StrMsgRequirementLimitDrainedBattery}: " + // Required battery load from standard or special requirement $"{threshold:F2} % {strAlternativeCalc}"; @@ -1109,11 +1109,11 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr } // Build message for required versus calculated lifetime - var lifeTime = $"{Resources.StrMsgRemainingLifeTimeAtEol}: " + + var lifeTime = $"{Resources.StrMsgRemainingLifeTime}: " + // Actual calculated lifetime based on readings from meter $"{ProductionStatus.RemainingLifeTimeYears:F2} " + $"{Resources.StrMsgYears}" + - $" - {Resources.StrMsgRequiredLifeTimeAtEol}: " + + $" - {Resources.StrMsgRequiredLifeTime}: " + // Required lifetime from special or standard requirement $"{ProductionRequirements.RequiredLifeTimeYearsShipping:F2} " + $"{Resources.StrMsgYears}"; diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs index c7b779db..066095b3 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/EolProcesses/EolProcessStatesDef.cs @@ -151,19 +151,19 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr // This scans the order number, it has to be executed exclusively with automatic new ProcessStateStruct( - new CheckOrderNumber(Resources.StrStateCheckOrderNumber), - ProcessState.CheckOrderNumber, - Resources.StrStateCheckOrderNumber, - 11, - ProcessState.CheckSerialNumber), + productionProcess: new CheckOrderNumber(Resources.StrStateCheckOrderNumber), + processState: ProcessState.CheckOrderNumber, + processInfo: Resources.StrStateCheckOrderNumber, + productionProcessNo: 11, + nextStateOnSuccess: ProcessState.CheckSerialNumber), // This scans the serial number, it has to be executed exclusively with automatic new ProcessStateStruct( - new CheckSerialNumber( Resources.StrStateCheckSerialNumber), - ProcessState.CheckSerialNumber, - Resources.StrStateCheckSerialNumber, - 12, - ProcessState.CheckAttachments), + productionProcess: new CheckSerialNumber( Resources.StrStateCheckSerialNumber), + processState: ProcessState.CheckSerialNumber, + processInfo: Resources.StrStateCheckSerialNumber, + productionProcessNo: 12, + nextStateOnSuccess: ProcessState.CheckAttachments), #endregion -------------------------------- GENERIC-STATES -------------------------------------------- diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs new file mode 100644 index 00000000..1da6479e --- /dev/null +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/CheckProductionState.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Logic.ProductionToProductMapper.Files.Fw; +using Xylem.Common.CommonCore.Consts; +using Xylem.Common.Hardware.WaterMeter.Genesis.Applications; +using Xylem.Common.Hardware.WaterMeter.Genesis.Applications.Const; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; +using Xylem.Common.Logic.ProductionOrderCore.OrderData; +using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses; +using Xylem.Common.Production.ProductionUiCordonel.Properties; + +namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.PickingProcesses +{ + /// + /// Check all parameters needed as preparation for assembly. + /// + public class CheckProductionState : BaseProductionProcess + { + /// + /// Maximum of expected processing steps for this process to feed the GUI progress bar. + /// + private const Int32 MaxActualProgressSteps = 13; + + /// + /// FW update package containing the ABC and the ADF. + /// + public List FwUpdatePackage + { + get; set; + } + + /// + /// Ctor + /// + public CheckProductionState(String name) : base(name) + { + } + + /// + /// Start database calls and meter readouts here for better testability and ability to fill the + /// objects manually by the test routine. + /// + /// + /// - Initial. + /// + public override StatusReturn PreExecuteProcess() + { + if (Meter == null || string.IsNullOrEmpty(Meter.PcbId) || Meter.PcbId.Length < GenesisMeter.MinPcbIdLength) + { + Meter?.WriteLog("PcbId requirements do not match"); + return StatusReturn.Failed; + } + + // Some statistics for GUI and internal processing + SignalRunningState(MaxActualProgressSteps); + + // Get the requirement (Standard or special) + if (!GenesisStatusHandler.GetCordonelRequirementInfoFromDb(Meter.PcbId, ProductionRequirements, + out var error) || ProductionRequirements == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgProductionRequirements); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Get clustered FileId of firmware package for production or on special requirement release candidates + var isSpecialActive = ProductionRequirements.IsSpecialActive != null && + (Boolean)ProductionRequirements.IsSpecialActive && + ProductionRequirements.IsSpecialApproved != null && + (Boolean)ProductionRequirements.IsSpecialApproved; + + if (string.IsNullOrEmpty(ProductionRequirements.FwVersion) || + !MeterFwUpdate.SearchProdFwInDb(ProductionRequirements.FwVersion, out var parentFileId, out error, + !isSpecialActive) || parentFileId == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLoadingFwFromDb); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Get the real content of the FW package including all binaries of each app and the application description file + FwUpdatePackage = new List(); + if (!MeterFwUpdate.GetFwPackageFromDb((Int32)parentFileId, FwUpdatePackage, out error) || + FwUpdatePackage == null) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLoadingFwFromDb); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + // Read out the Cordonel and calculate remaining lifetime and fill the ProductionStatus + if (!GenesisStatusHandler.BuildLifeTimeInformation(Meter, ProductionStatus, out error)) + { + Meter.WriteLog(error); + ErrorMsgDispatcher(Resources.StrErrorMsgLifeTimeCalculation); + return StatusReturn.Failed; + } + + // Exit if meanwhile cancellation is required + if (CancellationToken.IsCancellationRequested) + return CancellationProcedure(); + + ActualProcessProgress++; + + return StatusReturn.Okay; + } + + /// + /// Check all parameters needed as preparation for completion to validate the status and the + /// remaining operational time. This includes special permissions for individual parameters. + /// The picking requirements including the special permissions are taken from the DB and can be + /// assigned to an order number (meaning for all devices produced under this order) or an individual + /// pcbId. The setup and calculation of the exceeded time and drained will be taken from the Cordonel + /// (read from registers in POWERMON). + /// Precondition: - The process has to + /// be completed as the PcbId and the order number is needed. + /// Checks: + /// - Requirements are collected, + /// - PcbId, + /// - Region, + /// - FW, + /// - LUT CRC, + /// - Meter size + /// - Drained battery load versus required remaining battery load in percent, + /// - Remaining run time in years, + /// + /// + /// + /// - Initial. + /// + public override StatusReturn ExecuteProcess() + { + #region ----------------------------------- Check Requirements -------------------------------------------- + + var statusReturn = CheckRequirements(out var feedbackMsg); + if (StatusReturn.Failed == statusReturn) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Production Order ---------------------------------------- + + // Production order + if (StatusReturn.Failed == CheckProductionOrderNumber(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check PCBID --------------------------------------------------- + // PCBID read from meter compared with requirement PcbIds + statusReturn = CheckPcbId(out feedbackMsg); + + // Exit here with error + if (StatusReturn.Failed == statusReturn) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Region -------------------------------------------------- + + if (StatusReturn.Failed == CheckRegion(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check LUT CRC ------------------------------------------------- + + if (string.IsNullOrEmpty(CheckLutCrc(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Meter Size ---------------------------------------------- + + if (string.IsNullOrEmpty(CheckMeterSize(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Firmware and Apps --------------------------------------- + + if (string.IsNullOrEmpty(CheckFwAndApps(out feedbackMsg))) + { + ErrorMsgDispatcher(feedbackMsg); + return StatusReturn.Failed; + } + + SuccessMsgDispatcher(feedbackMsg); + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Remaining Battery Load ---------------------------------- + + var erroneousSequenceStep = false; + if (StatusReturn.Failed == CheckRemainingBatteryLoad(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Remaining Life Time ------------------------------------- + + if (StatusReturn.Failed == CheckRemainingLifetime(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Check Storage Months ------------------------------------------ + + if (StatusReturn.Failed == CheckStorageMonths(out feedbackMsg)) + { + ErrorMsgDispatcher(feedbackMsg); + //return StatusReturn.Failed; + // Do not return immediately to avoid multiple special-requirements on multiple errors + erroneousSequenceStep = true; + } + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + #endregion + + #region ----------------------------------- Summarize Check Results --------------------------------------- + + // One or more checks failed + if (erroneousSequenceStep) + return StatusReturn.Failed; + + // Here everything went fine + SuccessMsgDispatcher(feedbackMsg); + + ActualProcessProgress++; + + // All test successfully passed reaching this line + return StatusReturn.Okay; + #endregion -------------------------------- Summarize Check Results --------------------------------------- + } + + /// + /// Common routine to exit with error and user window with error message + /// + /// + /// + /// - Extracted from for better testability. + /// + private StatusReturn ExitWithErrorAndUserWindow(String errorMsg) + { + ErrorMsgDispatcher(errorMsg); + MessageBox.Show(errorMsg, Resources.StrProcessStateError, MessageBoxButton.OK, MessageBoxImage.Error); + return StatusReturn.Failed; + } + + /// + /// Check for requirement permission special or standard or none: + /// - If the special requirement is active all fields a filled with the according values from this "special" + /// requirement, + /// - Else those fields contain the "standard" requirements! + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_001]_Check_Requirements + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRequirements(out String feedbackMsg) + { + feedbackMsg = ""; + if (ProductionRequirements == null) + { + feedbackMsg = Resources.StrErrorMsgProductionRequirements; + return StatusReturn.Failed; + } + + // Check if requirement is active (free for production) and approved by a second person. Priority special + // requirement. + var isSpecialActive = false; + var isStandardActive = false; + if (ProductionRequirements.IsSpecialActive != null && (Boolean)ProductionRequirements.IsSpecialActive && + ProductionRequirements.IsSpecialApproved != null && (Boolean)ProductionRequirements.IsSpecialApproved) + isSpecialActive = true; + if (ProductionRequirements.IsStandardActive != null && (Boolean)ProductionRequirements.IsStandardActive && + ProductionRequirements.IsStandardApproved != null && (Boolean)ProductionRequirements.IsStandardApproved) + isStandardActive = true; + + // At least one requirement needs to be active and a requirement number and version has to be set + if ((!isStandardActive && !isSpecialActive) || + // The standard Id and version have always to be set even if the special is active + ProductionRequirements.StandardId == null || ProductionRequirements.StandardVersion == null || + // If the special is active the version and Id have to be defined + (isSpecialActive && (ProductionRequirements.SpecialId == null || + ProductionRequirements.SpecialVersion == null))) + { + feedbackMsg = Resources.StrErrorMsgRequirementInactive; + return StatusReturn.Failed; + } + + feedbackMsg = isSpecialActive && + ProductionRequirements.SpecialId != null && + ProductionRequirements.SpecialVersion != null ? + Resources.StrWarningMsgSpecialRequirementActive + + $" Id.Ver: {ProductionRequirements.SpecialId}.{ProductionRequirements.SpecialVersion}" : + Resources.StrSuccessMsgRequirementActive + + $" Id.Ver {ProductionRequirements.StandardId}.{ProductionRequirements.StandardVersion}"; + + + return StatusReturn.Okay; + } + + /// + /// Check production order number between meter and requirements. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_002]_Check_ProductionOrderNo + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckProductionOrderNumber(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + ProductionRequirements.ProductionOrderNr != Meter.OrderNumber) + { + feedbackMsg = Resources.StrErrorMsgRequirementProductionOrder; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementProductionOrder} - " + + $"{Resources.StrLblOrderNumberTxt}: {Meter.OrderNumber}"; + return StatusReturn.Okay; + } + + /// + /// Check PcbId of meter and requirements. Mark PcbId as skipped if + /// production requirements PcbId is null as this is the indication that all Cordonels + /// assigned to this production order are produced according to this requirement. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_003]_Check_PcbId + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckPcbId(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + Meter.PcbId == null || + Meter.PcbId.Length < GenesisMeter.MinPcbIdLength) + { + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; + return StatusReturn.Failed; + } + + if (ProductionRequirements.PcbId == null) + { + feedbackMsg = Resources.StrSuccessMsgOrderBasedRequirements; + return StatusReturn.Okay; + } + if (Meter.PcbId.Equals(ProductionRequirements.PcbId.ToString())) + { + feedbackMsg = $"{Resources.StrSuccessMsgRequirementPcbId} - PcbId: {Meter.PcbId}"; + return StatusReturn.Okay; + } + + feedbackMsg = Resources.StrErrorMsgRequirementPcbId; + return StatusReturn.Failed; + } + + /// + /// Check region between meter and requirements + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_004]_Check_Region + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRegion(out String feedbackMsg) + { + feedbackMsg = ""; + if (ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.Region) || + string.IsNullOrEmpty(Meter.Region) || + !Meter.Region.Equals(ProductionRequirements.Region)) + { + feedbackMsg = Resources.StrErrorMsgRequirementRegion; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementRegion} - Region: {Meter.Region}"; + return StatusReturn.Okay; + } + + /// + /// Check Look-Up-Table (LUT) for metrology calibration values for the checksum (CRC) of this table + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_005]_Check_LUT_CRC + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckLutCrc(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.LutCrc) || + string.IsNullOrEmpty(Meter.LutCrc) || + !Meter.LutCrc.Equals(ProductionRequirements.LutCrc)) + { + feedbackMsg = Resources.StrErrorMsgRequirementLut; + return ""; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementLut} - LUT CRC: {Meter.LutCrc}"; + + return Meter.LutCrc; + } + + /// + /// Check meter size + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_006]_Check_Meter_Size + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckMeterSize(out String feedbackMsg) + { + feedbackMsg = ""; + if (Meter == null || + ProductionRequirements == null || string.IsNullOrEmpty(ProductionRequirements.MeterSize) || + string.IsNullOrEmpty(Meter.MeterSize) || + MeterSizeConverter.ConvertMeterSizeNameToEnum(Meter.MeterSize) != + MeterSizeConverter.ConvertMeterSizeNameToEnum(ProductionRequirements.MeterSize)) + { + feedbackMsg = Resources.StrErrorMsgRequirementMeterSize; + return ""; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementMeterSize} - " + + $"{Resources.StrLblMeterSizeTxt}: {Meter.MeterSize}"; + + return ProductionRequirements.MeterSize; + } + + /// + /// Check the installed firmware (FW) and all individual applications (Apps) installed to the meter: + /// - Take the FLEXNETVERSION as base information which FW is installed in the Cordonel and compare this with + /// the production-requirement, + /// - The downloaded FW package containing all Apps as application-binary-container (ABC) will be compared with + /// the reported Apps by the Cordonel for their version and CRC using the " class, + /// - Crosscheck if the application-description-file (ADF) states all Apps which are installed in the Cordonel, + /// - Check the Core-Revision of the Cordonel against the ADF to guarantee that this FW is supported by the + /// Cordonel operational system (Breeze). + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_007]_Check_FW_and_Apps + /// + /// + /// + /// + /// + /// - Initial. + /// + private String CheckFwAndApps(out String feedbackMsg) + { + feedbackMsg = ""; + // Check installed FW version with FLEXNET information + var fwUpdate = new MeterFwUpdate(Meter); + if (Meter == null || + ProductionRequirements == null || + string.IsNullOrEmpty(ProductionRequirements.FwVersion) || + string.IsNullOrEmpty(Meter.FwVersion) || + !Meter.FwVersion.Equals(ProductionRequirements.FwVersion) || + // Check if FW package has been loaded and contains at least the minimal required files + FwUpdatePackage == null || + FwUpdatePackage.Count < MeterFwUpdate.MinAppsRequiredForOperation) + { + feedbackMsg += $"{Resources.StrErrorMsgRequirementFw}\n"; + return ""; + } + + var fileApps = new List(); + if (FwUpdatePackage != null) + { + foreach (var file in FwUpdatePackage) + { + // load the ABC (application binary container) + if (file.FileName.Contains("binfile") && + file.FileName.EndsWith(".bin") && + // Sometimes the * complete *.bin is part of the binaries containing all app in one file. + // As this is not part of the ADF it has to be skipped from loading!!!! + !file.FileName.Contains("complete")) + { + var fileApplication = new FileApplications(file.FileName) + { + BinData = new List(file.FileContent.ToList()) + }; + fileApps.Add(fileApplication); + } + // Load the ADF (application description file, containing the information + // of each application) + else if (file.FileName.Contains("product") && file.FileName.EndsWith(".txt")) + { + var enc = new System.Text.ASCIIEncoding(); + var adf = enc.GetString(file.FileContent.ToArray()); + fwUpdate.LoadPackageFileFromText(adf); + } + } + + fwUpdate.LoadFileApps(fileApps); + } + + var checkSucceeded = true; + // Compare every installed application for version and CRC with the required to be defined by the downloaded + // ABC (application binary container) + fwUpdate.CompareAllMeterAndFileApps(); + + if (// Validate package description file with required Apps (fileApps) and build the CORE REVISIONS MIN MAX + !fwUpdate.ValidateFileAppsWithPackageFile() || + // Check if any meter Apps is outdated or does contain an un-matching CRC + Meter.MeterAppListVersion.Any(app => app.Status != MeterAppState.MeterAppUpToDate && + app.Status != MeterAppState.MeterAppNotRequired)) + { + feedbackMsg += $"{Resources.StrErrorMsgAppVersionOrCrc}\n"; + checkSucceeded = false; + // Do not immediately return to summarize all errors following up + } + + // The package description file has to be parsed in advance using the fwUpdate.ValidateFileAppsWithPackageFile()!! + if (!fwUpdate.CheckCoreRevision(Meter.CoreRevision)) + { + feedbackMsg += $"{Resources.StrErrorMsgCoreRevision}\n"; + // Do not immediately return to summarize all errors following up + checkSucceeded = false; + } + + if (!checkSucceeded) + { + feedbackMsg += $"{Resources.StrErrorMsgRequirementFw}\n"; + return ""; + } + feedbackMsg = $"{Resources.StrSuccessMsgRequirementFw} - Firmware: {Meter.FwVersion}"; + return Meter.FwVersion; + } + + /// + /// Check the remaining battery load based on the thresholds which can be either: + /// - The AlternativeRequiredBatteryLoadPercent if the QuiescentCurrent_uA is set to a value greater than 50 uA + /// and the EstimatedProductionBatteryLoadPercent is greater than 0.0 % + /// and + /// - the MaxDrainedBatteryLoadPercentAssembly limits the threshold, + /// or only + /// - the MaxDrainedBatteryLoadPercentAssembly limits the threshold as absolut limit. + /// + /// ATTENTION: The MaxDrainedBatteryLoadPercentAssembly forces the absolut limit even if the alternative + /// calculation is being used. + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_008]_Check_Remaining_Battery_Load + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRemainingBatteryLoad(out String feedbackMsg) + { + if (Meter == null || + ProductionRequirements?.MaxDrainedBatteryLoadPercentAssembly == null || + ProductionStatus == null) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // If the values for calculation of drained battery load and exceeded lifetime are not accessible + if (ProductionStatus.DrainedBatteryLoad_uAs == 0 || ProductionStatus.ExceededLifeTime_s == 0) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Set up the alternative calculation values needed for given current and supplement for production. + // These values will be set by the requirement and have a higher priority than the standard calculation + // based on the drained battery load and the estimated prediction based on this and the time of operation. + if (ProductionRequirements.QuiescentCurrent_uA > GenesisMeter.QuiescentCurrent_uA && + ProductionRequirements.EstimatedProductionBatteryLoadPercent > 0) + { + ProductionStatus.QuiescentCurrent_uA = ProductionRequirements.QuiescentCurrent_uA; + ProductionStatus.EstimatedProductionBatteryLoadPercent = + ProductionRequirements.EstimatedProductionBatteryLoadPercent; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus)) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Check battery load in percent and compare with requirements for limits for drained battery load + // For the alternative calculation based quiescent current and production supplement this will be used. + // Build message for required versus calculated lifetime + var threshold = ProductionStatus.AlternativeRequiredBatteryLoadPercent ?? + ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly; + + // Create an output message addon for alternative power consumption calculation + var strAlternativeCalc = ProductionStatus.AlternativeRequiredBatteryLoadPercent == null + ? "" + : $" - {Resources.StrMsgRequirementAbsolutLimitDrainedBattery}: " + + $"{ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly:F2} %" + + $" ({Resources.StrMsgAlternativeDrainedBatteryCalculation})"; + + var batteryLoad = $"{Resources.StrMsgDrainedBattery}: " + + // Actual calculated battery load based on readings from meter + $"{ProductionStatus.DrainedBatteryLoadPercent:F2} %" + + $" - {Resources.StrMsgRequirementLimitDrainedBattery}: " + + // Required battery load from standard or special requirement + $"{threshold:F2} % {strAlternativeCalc}"; + + + // This is the absolut limit even if the "AlternativeRequiredBatteryLoadPercent" will be used + if (ProductionStatus.DrainedBatteryLoadPercent > + ProductionRequirements.MaxDrainedBatteryLoadPercentAssembly) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {batteryLoad}"; + return StatusReturn.Failed; + } + + // This is the limit given by the "AlternativeRequiredBatteryLoadPercent" + if (ProductionStatus.AlternativeRequiredBatteryLoadPercent != null && + ProductionStatus.DrainedBatteryLoadPercent > ProductionStatus.AlternativeRequiredBatteryLoadPercent) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {batteryLoad}"; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsg}: {batteryLoad}"; + + return StatusReturn.Okay; + } + + /// + /// Check the remaining lifetime + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_009]_Check_Remaining_Lifetime + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckRemainingLifetime(out String feedbackMsg) + { + if (Meter == null || + ProductionRequirements?.RequiredLifeTimeYearsAssembly == null || + ProductionRequirements.RequiredLifeTimeYearsAssembly <= 0 || + ProductionStatus == null) + { + feedbackMsg = Resources.StrErrorMsgLifeTimeCalculation; + return StatusReturn.Failed; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus)) + { + feedbackMsg = Resources.StrErrorMsgBatteryLoadCalculation; + return StatusReturn.Failed; + } + + // Build message for required versus calculated lifetime + var lifeTime = $"{Resources.StrMsgRemainingLifeTime}: " + + // Actual calculated lifetime based on readings from meter + $"{ProductionStatus.RemainingLifeTimeYears:F2} " + + $"{Resources.StrMsgYears}" + + $" - {Resources.StrMsgRequiredLifeTime}: " + + // Required lifetime from special or standard requirement + $"{ProductionRequirements.RequiredLifeTimeYearsAssembly:F2} " + + $"{Resources.StrMsgYears}"; + + // Check required years for assembly + if (ProductionRequirements.RequiredLifeTimeYearsAssembly > ProductionStatus.RemainingLifeTimeYears) + { + feedbackMsg = $"{Resources.StrErrorMsg}: {lifeTime}"; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsg}: {lifeTime}"; + return StatusReturn.Okay; + } + + /// + /// Check the months between first battery activation and now + /// + /// Software Requirement Specification: + /// [SRS_TBF_PICK_PP_CHECK_PROD_STATE_010]_Check_Storage_Months + /// + /// + /// + /// + /// + /// - Initial. + /// + private StatusReturn CheckStorageMonths(out String feedbackMsg) + { + // Based on first attached battery and the POWEMON_TotalUsedSeconds (Kitron production date) and actual date + if (Meter == null || + ProductionRequirements?.MaxStorageMonths == null || + ProductionRequirements.MaxStorageMonths < 0.0 || + ProductionStatus == null || + ProductionStatus.DrainedBatteryLoad_uAs == 0 || + ProductionStatus.ExceededLifeTime_s == 0) + { + // Storage months requirement or Kitron production date unknown + feedbackMsg = Resources.StrErrorMsgRequirementMaximumStorageMonthUnknown; + return StatusReturn.Failed; + } + + // Calculate the power consumption + if (!GenesisStatusHandler.CalculateLifeTime(ProductionStatus) || ProductionStatus.BatteryAssemblyDateTimeUtc == null) + { + feedbackMsg = Resources.StrErrorMsgRequirementMaximumStorageMonthUnknown; + return StatusReturn.Failed; + } + + var actualDate = DateTime.UtcNow.Date; + var batteryAssemblyDate = ProductionStatus.BatteryAssemblyDateTimeUtc.Value.Date; + // Give one day more as rounding issue + var expirationDate = batteryAssemblyDate.AddDays((Double)ProductionRequirements.MaxStorageMonths * GenesisStatusHandler.DAYS_PER_MONTH + 1.0); + var strStorageProduction = $"{Resources.StrLblKitronProductionDate}: " + + $"{ProductionStatus.BatteryAssemblyDateTimeUtc.Value:dd-MMM-yyyy} - " + + $"{Resources.StrLblExpirationDate}: {expirationDate:dd-MMM-yyyy} - " + + $"{Resources.StrLblMaxStorageMonths}: " + + $"{(Int32)ProductionRequirements.MaxStorageMonths} {Resources.StrLblMonths}"; + + if (actualDate >= expirationDate) + { + // Storage months exceeded + feedbackMsg = $"{Resources.StrErrorMsgRequirementMaximumStorageMonth} - " + strStorageProduction; + return StatusReturn.Failed; + } + + feedbackMsg = $"{Resources.StrSuccessMsgRequirementMaximumStorageMonth} - " + strStorageProduction; + return StatusReturn.Okay; + } + } +} + diff --git a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs index 121408c7..d0adec0d 100644 --- a/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs +++ b/Common/Production/ProductionUiCordonel/ProductionProcesses/PickingProcesses/PickingProcessStatesDef.cs @@ -153,11 +153,21 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Picki processState: ProcessState.CheckOrderNumber, processInfo: Resources.StrStateCheckOrderNumber, productionProcessNo:3, - nextStateOnSuccess: ProcessState.Idle), + nextStateOnSuccess: ProcessState.CheckProductionState), #endregion -------------------------------- GENERIC-STATES -------------------------------------------- #region ----------------------------------- PICKING-STATES -------------------------------------------- + // Precondition: - The connection process has to be completed as a readout of some register + // values from the Cordonel are needed to check the update capability: + // - Wait on completed connection process before starting this process. + new ProcessStateStruct( + productionProcess: new CheckProductionState(Resources.StrStateCheckProductionState), + processState: ProcessState.CheckProductionState, + processInfo: Resources.StrStateCheckProductionState, + productionProcessNo: 4, + nextStateOnSuccess: ProcessState.Idle), + //TODO Roland: implement your states here #endregion -------------------------------- PICKING-STATES -------------------------------------------- diff --git a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj index 4565ffaf..aacaff13 100644 --- a/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj +++ b/Common/Production/ProductionUiCordonel/ProductionUiCordonel.csproj @@ -299,6 +299,7 @@ + diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs index 6c2b205e..128dfc26 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -864,18 +864,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to ERROR: Maximum drained battery load overflow. /// - internal static string StrErrorMsgThresholdDrainedBatteryAtEol { + internal static string StrErrorMsgThresholdDrainedBattery { get { - return ResourceManager.GetString("StrErrorMsgThresholdDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrErrorMsgThresholdDrainedBattery", resourceCulture); } } /// /// Looks up a localized string similar to ERROR: Minimum required lifetime underflow. /// - internal static string StrErrorMsgThresholdRemainingLifeTimeAtEol { + internal static string StrErrorMsgThresholdRemainingLifeTime { get { - return ResourceManager.GetString("StrErrorMsgThresholdRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrErrorMsgThresholdRemainingLifeTime", resourceCulture); } } @@ -1269,9 +1269,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Drained battery load. /// - internal static string StrMsgDrainedBatteryAtEol { + internal static string StrMsgDrainedBattery { get { - return ResourceManager.GetString("StrMsgDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgDrainedBattery", resourceCulture); } } @@ -1341,36 +1341,36 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Estimated remaining lifetime. /// - internal static string StrMsgRemainingLifeTimeAtEol { + internal static string StrMsgRemainingLifeTime { get { - return ResourceManager.GetString("StrMsgRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRemainingLifeTime", resourceCulture); } } /// /// Looks up a localized string similar to Minimum required lifetime. /// - internal static string StrMsgRequiredLifeTimeAtEol { + internal static string StrMsgRequiredLifeTime { get { - return ResourceManager.GetString("StrMsgRequiredLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequiredLifeTime", resourceCulture); } } /// /// Looks up a localized string similar to Absolut restriction. /// - internal static string StrMsgRequirementAbsolutLimitDrainedBatteryAtEol { + internal static string StrMsgRequirementAbsolutLimitDrainedBattery { get { - return ResourceManager.GetString("StrMsgRequirementAbsolutLimitDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequirementAbsolutLimitDrainedBattery", resourceCulture); } } /// /// Looks up a localized string similar to Maximum allowed. /// - internal static string StrMsgRequirementLimitDrainedBatteryAtEol { + internal static string StrMsgRequirementLimitDrainedBattery { get { - return ResourceManager.GetString("StrMsgRequirementLimitDrainedBatteryAtEol", resourceCulture); + return ResourceManager.GetString("StrMsgRequirementLimitDrainedBattery", resourceCulture); } } @@ -2061,9 +2061,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.Properties { /// /// Looks up a localized string similar to Success: Minimum required lifetime is in range. /// - internal static string StrSuccessMsgThresholdRemainingLifeTimeAtEol { + internal static string StrSuccessMsgThresholdRemainingLifeTime { get { - return ResourceManager.GetString("StrSuccessMsgThresholdRemainingLifeTimeAtEol", resourceCulture); + return ResourceManager.GetString("StrSuccessMsgThresholdRemainingLifeTime", resourceCulture); } } diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx index 6ed0cd2f..f369a09e 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.de.resx @@ -700,34 +700,34 @@ Erfolg: Datenbank und Cordonel Funkschlüssel erfolgreich abgeglichen - + Verbrauchte Batterieladung Berechnung basierend auf Ruhestrom und Produktionszuschlag - + FEHLER: Maximale erlaubte Batterieentladung überschritten - + Absoluter Grenzwert - + Maximal erlaubt - + Abgeschätzte Restlaufzeit - + FEHLER: Minimale geforderte Restlaufzeit unterschritten FEHLER: Batterieentladung konnte nicht berechnet werden - + Erfolg: Minimale Restlaufzeit eingehalten - + Minimal geforderte Restlaufzeit diff --git a/Common/Production/ProductionUiCordonel/Properties/Resources.resx b/Common/Production/ProductionUiCordonel/Properties/Resources.resx index 2810476c..2a3d91c6 100644 --- a/Common/Production/ProductionUiCordonel/Properties/Resources.resx +++ b/Common/Production/ProductionUiCordonel/Properties/Resources.resx @@ -700,34 +700,34 @@ Success: Database with Cordonel radio encryption key successfully crosschecked - + Drained battery load Calculation based on quiescent current and production supplement - + ERROR: Maximum drained battery load overflow - + Absolut restriction - + Maximum allowed - + Estimated remaining lifetime - + ERROR: Minimum required lifetime underflow ERROR: Failed to calculate battery load - + Success: Minimum required lifetime is in range - + Minimum required lifetime