using System; using System.Collections.Generic; using System.Linq; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.Properties; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; using Xylem.Common.Utils.Crc16Ccitt; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile { /// /// Power correction file stored as reminder and log to meter /// public class MeterPowerCorrectionFile { /// /// Drive and name for power correction file stored on meter /// public const String StrMeterPowCorrFileName = "1\\powcorr"; private readonly IGenesisMeter _currentGenesis; private readonly MeterFile _meterFile; // position of data in binary data of meter powcorr file // file information private const Int32 FileLengthIdx = 0; private const Int32 FileLengthSize = sizeof(UInt16); private const Int32 FileCrcIdx = FileLengthIdx + FileLengthSize; private const Int32 FileCrcSize = sizeof(UInt16); private const Int32 FileDataIdx = FileCrcIdx + FileCrcSize; private const Int32 FileHeaderSize = FileLengthSize + FileCrcSize; // common data size private const Int32 ChargeSize = sizeof(UInt64); private const Int32 TimeSize = sizeof(UInt32); private const Int32 FwVersionSize = sizeof(UInt32); private const Int32 RadioStateSize = sizeof(Byte); private const Int32 PulseAdaptInstalledSize = sizeof(Boolean); private const Int32 PulseModeSize = sizeof(Byte); private const Int32 PulseSequenceCtrSize = sizeof(Byte); private const Int32 PulseEvenDistributionSize = sizeof(Boolean); // production data private const Int32 ProdDateTimeIdx = FileDataIdx; private const Int32 ProdTimeIdx = ProdDateTimeIdx + TimeSize; private const Int32 ProdChargeIdx = ProdTimeIdx + TimeSize; private const Int32 ProdRadioStateIdx = ProdChargeIdx + ChargeSize; private const Int32 ProdPulseAdaptInstalledIdx = ProdRadioStateIdx + RadioStateSize; private const Int32 ProdPulseModeIdx = ProdPulseAdaptInstalledIdx + PulseAdaptInstalledSize; private const Int32 ProdPulseSequenceCtrIdx = ProdPulseModeIdx + PulseModeSize; private const Int32 ProdPulseEvenDistributionIdx = ProdPulseSequenceCtrIdx + PulseSequenceCtrSize; private const Int32 InitialBatteryLoadIdx = ProdPulseEvenDistributionIdx + PulseEvenDistributionSize; private const Int32 InitialBatteryLoadSize = sizeof (UInt16); private const Int32 BatteryQuantityIdx = InitialBatteryLoadIdx + InitialBatteryLoadSize; private const Int32 BatteryQuantitySize = sizeof(Byte); // field data private const Int32 FieldFwVersionIdx = BatteryQuantityIdx + BatteryQuantitySize; private const Int32 FieldDateTimeIdx = FieldFwVersionIdx + FwVersionSize; private const Int32 FieldTimeIdx = FieldDateTimeIdx + TimeSize; private const Int32 FieldChargeIdx = FieldTimeIdx + TimeSize; private const Int32 FieldRadioStateIdx = FieldChargeIdx + ChargeSize; private const Int32 FieldPulseAdaptInstalledIdx = FieldRadioStateIdx + RadioStateSize; private const Int32 FieldPulseModeIdx = FieldPulseAdaptInstalledIdx + PulseAdaptInstalledSize; private const Int32 FieldPulseSequenceCtrIdx = FieldPulseModeIdx + PulseModeSize; private const Int32 FieldPulseEvenDistributionIdx = FieldPulseSequenceCtrIdx + PulseSequenceCtrSize; // calculated private const Int32 RequiredFwVersionIdx = FieldPulseEvenDistributionIdx + PulseEvenDistributionSize; private const Int32 CalcPulseReportLengthIdx = RequiredFwVersionIdx + FwVersionSize; private const Int32 CalcPulseReportLengthSize = sizeof(UInt32); private const Int32 CalcPostProdTimeIdx = CalcPulseReportLengthIdx + CalcPulseReportLengthSize; private const Int32 CalcOverestimatedChargeIdx = CalcPostProdTimeIdx + TimeSize; private const Int32 CalcFixedEstimatedChargeIdx = CalcOverestimatedChargeIdx + ChargeSize; private const Int32 CalcMinimumChargeIdx = CalcFixedEstimatedChargeIdx + ChargeSize; private const Int32 CalcCorrectedChargeIdx = CalcMinimumChargeIdx + ChargeSize; // estimate file size private const Int32 FileSize = CalcCorrectedChargeIdx + ChargeSize; // raw data private Byte[] _meterBinData = new Byte[FileSize]; /// /// Content of power correction meter file /// public readonly List FileContent = new List(); /// /// Ctor /// /// /// /// - Initial. /// public MeterPowerCorrectionFile(IGenesisMeter currentGenesis) { _currentGenesis = currentGenesis; _meterFile = new MeterFile(_currentGenesis); } /// /// Read power correction meter file. /// /// true: file found /// /// - Initial /// public Boolean ReadPowCorrMeterFile() { if (_currentGenesis == null || _meterFile == null) return false; try { var loginReminder = _currentGenesis.IsLoggedOn; var retVal = _currentGenesis.ReLogin(); if (retVal) { FileContent.Clear(); retVal = _meterFile.ReadMeterFile(StrMeterPowCorrFileName, out var meterBinDataList); _meterBinData = meterBinDataList.ToArray(); // Build a string list of recorded data if (retVal) { retVal = BuildStringListOfPowCorrFileContent(); } } if (!loginReminder) _currentGenesis.Logout(); return retVal; } catch (Exception e) { _currentGenesis.WriteLog(e.Message); return false; } } /// /// Build a string list of logged power correction file content. /// /// true: file found /// /// - Initial /// /// /// - Formatting changed. /// private Boolean BuildStringListOfPowCorrFileContent() { if (_meterBinData == null || _meterBinData.Length == 0 || _currentGenesis == null) return false; try { var fc = FileContent; var bin = _meterBinData; Byte[] value_u8a; var ltlStatus = new GenesisStatus.GenesisStatus(); fc.Clear(); fc.Add(Resources.StrPowCorrFileInfo); if (CopyByteArray(value_u8a = new Byte[TimeSize], bin, TimeSize, 0, ProdDateTimeIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrDateTime}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(TimeT))}"); if (CopyByteArray(value_u8a = new Byte[TimeSize], bin, TimeSize, 0, ProdTimeIdx)) { ltlStatus.ExceededLifeTime_s = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrTime_s}" + $": {ltlStatus.ExceededLifeTime_s:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, ProdChargeIdx)) { ltlStatus.DrainedBatteryLoad_uAs = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrCharge_uAs}" + $": {ltlStatus.DrainedBatteryLoad_uAs:N0}"); } if (CopyByteArray(value_u8a = new Byte[InitialBatteryLoadSize], bin, InitialBatteryLoadSize, 0, InitialBatteryLoadIdx)) { ltlStatus.InitialBatteryLoad_mAh = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrInitialBatteryLoad_mAh}" + $": {ltlStatus.InitialBatteryLoad_mAh:N0}"); } if (CopyByteArray(value_u8a = new Byte[BatteryQuantitySize], bin, BatteryQuantitySize, 0, BatteryQuantityIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrBatteryQuantity}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); ltlStatus.BatteryQuantity = RegisterConverter.ByteArrayToValue(value_u8a); if (GenesisStatusHandler.CalculateLifeTime(ltlStatus)) { fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrDrainedLoad_percent}" + $": {ltlStatus.DrainedBatteryLoadPercent:F2}"); } if (CopyByteArray(value_u8a = new Byte[TimeSize], bin, TimeSize, 0, FieldDateTimeIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrDateTime}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(TimeT))}"); if (CopyByteArray(value_u8a = new Byte[TimeSize], bin, TimeSize, 0, FieldTimeIdx)) { ltlStatus.ExceededLifeTime_s = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrTime_s}" + $": {ltlStatus.ExceededLifeTime_s:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, FieldChargeIdx)) { ltlStatus.DrainedBatteryLoad_uAs = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrCharge_uAs}" + $": {ltlStatus.DrainedBatteryLoad_uAs:N0}"); } if (GenesisStatusHandler.CalculateLifeTime(ltlStatus)) { fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrDrainedLoad_percent}" + $": {ltlStatus.DrainedBatteryLoadPercent:F2}"); } if (CopyByteArray(value_u8a = new Byte[RadioStateSize], bin, RadioStateSize, 0, ProdRadioStateIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrRadioSystemState}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[RadioStateSize], bin, RadioStateSize, 0, FieldRadioStateIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrRadioSystemState}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[PulseAdaptInstalledSize], bin, PulseAdaptInstalledSize, 0, ProdPulseAdaptInstalledIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrPulseAdapterInstallationState}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Boolean))}"); if (CopyByteArray(value_u8a = new Byte[PulseAdaptInstalledSize], bin, PulseAdaptInstalledSize, 0, FieldPulseAdaptInstalledIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrPulseAdapterAutoDetected}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Boolean))}"); if (CopyByteArray(value_u8a = new Byte[PulseModeSize], bin, PulseModeSize, 0, ProdPulseModeIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrPulseMode}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[PulseModeSize], bin, PulseModeSize, 0, FieldPulseModeIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrPulseMode}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[PulseSequenceCtrSize], bin, PulseSequenceCtrSize, 0, ProdPulseSequenceCtrIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrPulseSequenceCounter}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[PulseSequenceCtrSize], bin, PulseSequenceCtrSize, 0, FieldPulseSequenceCtrIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrPulseSequenceCounter}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Byte))}"); if (CopyByteArray(value_u8a = new Byte[PulseEvenDistributionSize], bin, PulseEvenDistributionSize, 0, ProdPulseEvenDistributionIdx)) fc.Add($"{Resources.StrPowCorrFactoryValues} - {Resources.StrPowCorrPulseEvenDistribution}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Boolean))}"); if (CopyByteArray(value_u8a = new Byte[PulseEvenDistributionSize], bin, PulseEvenDistributionSize, 0, FieldPulseEvenDistributionIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrPulseEvenDistribution}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(Boolean))}"); if (CopyByteArray(value_u8a = new Byte[FwVersionSize], bin, FwVersionSize, 0, FieldFwVersionIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrOldFwVersion}" + $": {GenesisMeter.BuildFlexnetFwVersion(out _, null, value_u8a[1], value_u8a[0])}"); if (CopyByteArray(value_u8a = new Byte[FwVersionSize], bin, FwVersionSize, 0, RequiredFwVersionIdx)) fc.Add($"{Resources.StrPowCorrActualValues} - {Resources.StrPowCorrNewFwVersion}" + $": {GenesisMeter.BuildFlexnetFwVersion(out _, null, value_u8a[1], value_u8a[0])}"); if (CopyByteArray(value_u8a = new Byte[CalcPulseReportLengthSize], bin, CalcPulseReportLengthSize, 0, CalcPulseReportLengthIdx)) fc.Add($"{Resources.StrPowCorrPulseReportLength}" + $": {RegisterConverter.ConvertToText(value_u8a, typeof(UInt32))}"); if (CopyByteArray(value_u8a = new Byte[TimeSize], bin, TimeSize, 0, CalcPostProdTimeIdx)) { var temp = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrPostProdTime_s}" + $": {temp:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, CalcOverestimatedChargeIdx)) { var temp = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrOverestimate_uAs}" + $": {temp:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, CalcFixedEstimatedChargeIdx)) { var temp = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrFixedEstimateCharge_uAs}" + $": {temp:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, CalcMinimumChargeIdx)) { var temp = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrOverestimateLimit_uAs}" + $": {temp:N0}"); } if (CopyByteArray(value_u8a = new Byte[ChargeSize], bin, ChargeSize, 0, CalcCorrectedChargeIdx)) { ltlStatus.DrainedBatteryLoad_uAs = RegisterConverter.ByteArrayToValue(value_u8a); fc.Add($"{Resources.StrPowCorrCorrectedCharge_uAs}" + $": {ltlStatus.DrainedBatteryLoad_uAs:N0}"); } if (GenesisStatusHandler.CalculateLifeTime(ltlStatus)) { fc.Add($"{Resources.StrPowCorrCalculation} - {Resources.StrPowCorrDrainedLoad_percent}" + $": {ltlStatus.DrainedBatteryLoadPercent:F2}"); } return true; } catch (Exception e) { _currentGenesis.WriteLog(e.Message); return false; } } /// /// Copy byte arrays /// /// destination array to copy to /// array to copy from /// /// /// /// true: success /// /// - Initial /// private Boolean CopyByteArray(Byte[] destination, Byte[] source, Int32 size, Int32 destinationStartIndex = 0, Int32 sourceStartIndex = 0) { if (_currentGenesis == null) return false; // avoid OOR copy if (destination.Length < size + destinationStartIndex || source.Length < size + sourceStartIndex) return false; try { for (var c = 0; c < size; c++) { destination[destinationStartIndex + c] = source[sourceStartIndex + c]; } return true; } catch (Exception e) { _currentGenesis.WriteLog(e.Message); return false; } } /// /// Build the binary data as preparation before sending those to the meter file. /// /// PRECONDITION: PowerCorrectionData has to be filled in advance! /// /// true: success /// /// - Initial /// /// power correction /// if successfully copied private Boolean BuildBinData(PowerCorrection powerCorrectionData) { if (powerCorrectionData == null || _currentGenesis == null) return false; try { if (_meterBinData == null) _meterBinData = new Byte[FileSize]; var pc = powerCorrectionData; // production var timeT = new TimeT(); var dateTimeUtc = pc.ProductionDateTimeUtc ?? timeT.DateTimeUtc; var secSince2000 = timeT.UtcAnyToSecondsSince2000(dateTimeUtc); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(secSince2000), TimeSize, ProdDateTimeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionTotalUsedSeconds_s), TimeSize, ProdTimeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionTotalUsedCharge_uAs), ChargeSize, ProdChargeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionRadioSystemState), RadioStateSize, ProdRadioStateIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionPulseAdapterIsInstalled), PulseAdaptInstalledSize, ProdPulseAdaptInstalledIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionPulseMode), PulseModeSize, ProdPulseModeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionPulseSequenceCounter), PulseSequenceCtrSize, ProdPulseSequenceCtrIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ProductionPulseEvenDistribution), PulseEvenDistributionSize, ProdPulseEvenDistributionIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.InitialBatteryLoad_mAh), InitialBatteryLoadSize, InitialBatteryLoadIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.BatteryQuantity), BatteryQuantitySize, BatteryQuantityIdx); // field CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualFwVersion), FwVersionSize, FieldFwVersionIdx); // reset timeT to 2000 timeT.SecondsSince2000 = 0; dateTimeUtc = pc.ActualDateTimeUtc ?? timeT.DateTimeUtc; secSince2000 = timeT.UtcAnyToSecondsSince2000(dateTimeUtc); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(secSince2000), TimeSize, FieldDateTimeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualTotalUsedSeconds_s), TimeSize, FieldTimeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualTotalUsedCharge_uAs), ChargeSize, FieldChargeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualRadioSystemState), RadioStateSize, FieldRadioStateIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualPulseAdapterIsInstalled), PulseAdaptInstalledSize, FieldPulseAdaptInstalledIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualPulseMode), PulseModeSize, FieldPulseModeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualPulseSequenceCounter), PulseSequenceCtrSize, FieldPulseSequenceCtrIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.ActualPulseEvenDistribution), PulseEvenDistributionSize, FieldPulseEvenDistributionIdx); // calculations CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.RequiredFwVersion), FwVersionSize, RequiredFwVersionIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.PulseReportLength), CalcPulseReportLengthSize, CalcPulseReportLengthIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.PostProductionTotalUsedSeconds_s), TimeSize, CalcPostProdTimeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.OverestimatedTotalUsedCharge_uAs), ChargeSize, CalcOverestimatedChargeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.FixedEstimateTotalUsedCharge_uAs), ChargeSize, CalcFixedEstimatedChargeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.MinimumTotalUsedCharge_uAs), ChargeSize, CalcMinimumChargeIdx); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(pc.CorrectedTotalUsedCharge_uAs), ChargeSize, CalcCorrectedChargeIdx); // add file information CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(FileSize), FileLengthSize); var crc = Crc16Ccitt.CalculateReversedLsb8408(_meterBinData); CopyByteArray(_meterBinData, RegisterConverter.ValueToByteArray(crc), FileCrcSize, FileCrcIdx); return true; } catch (Exception e) { _currentGenesis.WriteLog(e.Message); return false; } } /// /// Write the meter power correction file. /// /// true successfully written /// /// - Initial. /// /// /// true if successfully written public Boolean WritePowCorrMeterFile(PowerCorrection powerCorrectionData) { if (_currentGenesis == null || _meterFile == null || powerCorrectionData == null) return false; try { if (!BuildBinData(powerCorrectionData)) return false; BuildStringListOfPowCorrFileContent(); var loginReminder = _currentGenesis.IsLoggedOn; var retVal = _currentGenesis.ReLogin(); if (retVal) { _meterFile.UnlockEraseWriteMeterFile(StrMeterPowCorrFileName); retVal = _meterFile.WriteMeterFile(StrMeterPowCorrFileName, _meterBinData.ToArray()); } if (!loginReminder) _currentGenesis.Logout(); return retVal; } catch (Exception e) { _currentGenesis.WriteLog(e.Message); return false; } } } }