using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using Logic.ProductionToProductMapper.Cordonel; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts; using Xylem.Common.Utils.Crc16Ccitt; using Xylem.Common.Utils.ProcessExec; using Xylem.Common.Utils.ProcessExec.EventArguments; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile { /// /// Meter lookup table for metrology update procedure /// public class MeterLutUpdate : IProcessState { #region Variables /// /// Port scan result event for message dispatcher to caller /// public event EventHandler OnProcessUpdate; //error mask for unsuccessfully written LUT CRC private const UInt32 LutCrcErrorMask = 0x8000000; //disk and name of lookup file private const String StrLutMeterFileName = "1\\mettbl"; //disk and name of lookup file private const String StrLutBackupMeterFileName = "1\\mettbl_b"; // identifier for Genesis lookup table private const String StrLutFileId = "GEN_LUT\0"; private const Int32 IndexLutFileId = 0; private const Int32 LengthLutFileId = 8; // data length, calculated from version field to the end of the data section private const Int32 IndexDataLength = 8; private const Int32 LengthDataLength = 4; // CCITT-CRC-16 , calculated from version field to the end of the data section private const Int32 IndexCrc = 12; private const Int32 LengthCrc = 2; // version private const Int32 IndexVersion = 14; private const Int32 LengthVersion = 2; // meter size as enumeration private const Int32 IndexMeterSize = 16; // header size of file private const Int32 LutFileHeaderLength = LengthLutFileId + LengthDataLength + LengthCrc + LengthVersion; // data size of file private const Int32 LutFileDataLength = 730; // LUT file entire file size private const Int32 SizeLutFile = LutFileHeaderLength + LutFileDataLength; //trigger update retries private const Int32 RegisterWriteRetries = 0; //update reties for files private const Int32 FileWriteRetries = 1; //retry of entire update procedure private const Int32 UpdateProcedureRetries = 1; //threshold to increase the file write timing private const Int32 IncreaseTimeoutUpdateProcedureRetries = 4; private Int32 _updateProcedureRetryCtr; private GenesisMeter _genesisMeter; private MeterFile _meterFile; // Text for caller to inform about actual process being carried out private String _actualOperation; private LutUpdateState _lutUpdateState; private LutUpdateState _lutBackupUpdateState; // Look up table file public MeterLutFile LutFile; // list of names of installed meter files on drive 1 private List _meterFilesDrive1; // number of operations for over all access private const Int32 NumberOfOperations = 11; private Int32 _operationCtr; // error message for display of error reason public String ErrorMessage; // manual stop of update process private Boolean _stopUpdateProcess; /// /// Register subset needed to adjust for update performance content before update /// private readonly Dictionary _registersBeforeUpdate = new Dictionary(); /// /// Register to restore after update /// private Dictionary _registersAfterUpdate = new Dictionary(); #endregion #region Events /// /// Process update event /// /// /// /// /// - Initial /// public virtual void ProcessUpdate_Event(Object sender, ProcessExecEventArgs e) { //// copy text from overall messages to actual, because this is from sub-routine //OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(_overallOperation, // OverallProcessCtrPercent, _actualOperation, e.OverallProcessPercent)); } #endregion #region Information /// /// Process counter in percent /// /// /// - Initial /// public Double OverallProcessCtrPercent => 100.0 * _operationCtr / NumberOfOperations; /// /// Single file process counter in percent /// /// /// - Initial /// public Double SingleFileProcessCtrPercent => _meterFile?.ProcessCtrPercent ?? 0; #endregion #region CtorAssignment /// /// Ctor /// /// /// /// - Initial /// public MeterLutUpdate(GenesisMeter genesisMeter) { if (genesisMeter == null) return; AssignGenesis(genesisMeter); if (genesisMeter.MeterAppListVersion.Any(f => f.AppName == "GENESISFLOW" && f.IsInstalled)) { _registersBeforeUpdate.Add(Register.Genesisflow.LedMode, new[] { (Byte)LedMode.Off }); _registersBeforeUpdate.Add(Register.Genesisflow.SampleRate, new Byte[] { 1 }); } if (genesisMeter.MeterAppListVersion.Any(f => f.AppName == "METROLOGYASST" && f.IsInstalled)) _registersBeforeUpdate.Add(Register.Mertrologyasst.PulseMode, new Byte[] { 0 }); //deny access if NA product, because this does not have the radio app installed if (genesisMeter.MeterAppListVersion.Any(f => f.AppName == "SENSUSRADIO" && f.IsInstalled)) _registersBeforeUpdate.Add(Register.Sensusradio.WakeupInterval, new Byte[] { 6 }); } /// /// Assign new genesis /// /// /// /// - Initial /// public void AssignGenesis(GenesisMeter genesisMeter) { _genesisMeter = genesisMeter; _actualOperation = ""; _lutUpdateState = LutUpdateState.Idle; } /// /// Dispose /// public void Dispose() { if (_meterFile == null) return; _meterFile.OnProcessUpdate -= ProcessUpdate_Event; _meterFile = null; } #endregion #region ProcessInformation /// /// Returns the actual file in process for update /// /// /// /// - Initial /// public String GetActualOperation() { return _actualOperation; } /// /// Returns the LUT Update state /// /// /// /// - Initial /// public String GetLutUpdateStateOperation() { // search text assigned to current state var text = LutUpdateStateInfo.GetTextFromState(_lutUpdateState); if (_lutUpdateState == LutUpdateState.Idle) { return text; } text += _updateProcedureRetryCtr > 0 ? $"Retry {_updateProcedureRetryCtr}" : ""; return text; } #endregion #region Actions /// /// Try to re-establish the file system, therefor logout to close all open files /// and login again. /// /// /// - Initial /// /// /// - Enable auto-logon added /// /// /// - Removed auto-logon /// public void ReestablishMeterFileSystem() { _genesisMeter?.Logout(); _genesisMeter?.ReLogin(); } /// /// Stop the update process /// public Boolean StopUpdateProcess { get => _stopUpdateProcess; set { _stopUpdateProcess = value; if (_meterFile != null) { _meterFile.StopProcess = value; } _lutUpdateState = LutUpdateState.UpdateFailed; } } /// /// Update meter lookup table /// /// /// /// - Initial /// public Boolean UpdateMeterLut() { if (!UpdatePreparation()) { return false; } _operationCtr = 0; _actualOperation = ""; ErrorMessage = ""; _lutUpdateState = LutUpdateState.StartInitial; _lutBackupUpdateState = LutUpdateState.Idle; while (_lutUpdateState != LutUpdateState.Idle && !StopUpdateProcess) { LutUpdateStateMachine(); } return ExitPreparation(true); } /// /// Upload lookup table file and compare with the loaded from file system /// /// /// - Initial /// public Boolean VerifyLutFile() { if (!UpdatePreparation()) { return false; } return ExitPreparation(true); } /// /// Reset lookup table update process and all lists and counters /// /// /// - Initial /// public void ResetAll() { LutFile = null; //remove the update stop action StopUpdateProcess = false; //reset all counters _updateProcedureRetryCtr = 0; //set state machine for automatic FW update _lutUpdateState = LutUpdateState.Idle; _lutBackupUpdateState = LutUpdateState.Idle; _actualOperation = ""; _operationCtr = 0; //logout/login/auto-login ReestablishMeterFileSystem(); _genesisMeter?.Logout(); } /// /// Common update preparation and check /// /// /// - Initial /// public Boolean UpdatePreparation() { _actualOperation = "Update preparation"; _lutUpdateState = LutUpdateState.Idle; _meterFile = new MeterFile(_genesisMeter); if (_meterFile != null) _meterFile.OnProcessUpdate += ProcessUpdate_Event; _genesisMeter?.ReLogin(); _genesisMeter?.SetProcessState(DisplayCodes.FwUpdateActive, false); if (_genesisMeter == null || !_genesisMeter.IsLoggedOn) { return false; } //set default timeout for file write operation _meterFile.SetDefaultFileWriteTimeout(); //switch LED off set sample rate to 1 -> 1Hz to slow down CPU load _registersAfterUpdate = new Dictionary(); foreach (var item in _registersBeforeUpdate) { var writeBackValue = _genesisMeter.ReadRegister(item.Key); _registersAfterUpdate.Add(item.Key, writeBackValue); _genesisMeter.WriteRegister(item.Key, item.Value); } //remove the update stop action StopUpdateProcess = false; return true; } /// /// Common exit routine /// /// /// - Initial /// public Boolean ExitPreparation(Boolean returnValue) { if (_meterFile != null) _meterFile.OnProcessUpdate -= ProcessUpdate_Event; _meterFile = null; ReestablishMeterFileSystem(); foreach (var item in _registersAfterUpdate) { _genesisMeter.WriteRegister(item.Key, item.Value); } _genesisMeter?.Logout(); if (!StopUpdateProcess) { return returnValue; } _actualOperation = ""; return false; } #endregion #region StateMachine /// /// State machine for metrology lookup table update process /// /// /// - Initial /// /// /// - Introduced lock for repeated execution of state machine /// private void LutUpdateStateMachine() { if (_lutBackupUpdateState == _lutUpdateState) { Thread.Sleep(1); } else { // remind backup state to avoid repeated execution and side effects _lutBackupUpdateState = _lutUpdateState; switch (_lutUpdateState) { case LutUpdateState.Idle: break; case LutUpdateState.StartInitial: _updateProcedureRetryCtr = 0; _lutUpdateState = LutUpdateState.EraseLutFile; break; case LutUpdateState.EraseLutFile: _lutUpdateState = EraseLutFiles() ? LutUpdateState.DownloadLutFile : LutUpdateState.StepFailed; break; case LutUpdateState.DownloadLutFile: _lutUpdateState = DownloadLutFiles() ? LutUpdateState.UploadLutFile : LutUpdateState.StepFailed; break; case LutUpdateState.UploadLutFile: _lutUpdateState = UploadAndCompareLutFiles() ? LutUpdateState.FinalizeLutDownload : LutUpdateState.StepFailed; break; case LutUpdateState.FinalizeLutDownload: _lutUpdateState = FinalizeLutDownload() ? LutUpdateState.Idle : LutUpdateState.StepFailed; break; case LutUpdateState.CompareLutFile: break; case LutUpdateState.StepFailed: _lutUpdateState = _updateProcedureRetryCtr++ < UpdateProcedureRetries ? LutUpdateState.RepeatLutUpdate : LutUpdateState.UpdateFailed; break; case LutUpdateState.RepeatLutUpdate: _lutUpdateState = LutUpdateState.DownloadLutFile; if (_updateProcedureRetryCtr >= IncreaseTimeoutUpdateProcedureRetries) { _meterFile?.SetExtremeFileWriteTimeout(); } break; case LutUpdateState.UpdateFailed: StopUpdateProcess = true; _lutUpdateState = LutUpdateState.Idle; break; case LutUpdateState.LutUpdateStateListDelimiter: _lutUpdateState = LutUpdateState.Idle; break; default: _lutUpdateState = LutUpdateState.Idle; break; } }// lock for identical state } #endregion #region LutFile /// /// Check the content of the metrology lookup table file /// /// /// /// - Initial /// private Boolean ValidateLutFile() { // check if LUT file contains data if (LutFile?.BinData == null || LutFile.BinData.Count < SizeLutFile) { ErrorMessage = "File size mismatch!"; return false; } LutFile.IsValid = false; // extract the identifier and check, if this is a LUT file LutFile.StrLutFileId = Encoding.UTF8.GetString(LutFile.BinData.ToArray(), IndexLutFileId, IndexDataLength); if (LutFile.StrLutFileId != StrLutFileId) return false; LutFile.MeterSize = (MeterSize)LutFile.BinData[IndexMeterSize]; LutFile.FileCrc = (UInt16)(LutFile.BinData[IndexCrc] + (LutFile.BinData[IndexCrc + 1] << 8)); LutFile.Version = (UInt16)(LutFile.BinData[IndexVersion] + (LutFile.BinData[IndexVersion + 1] << 8)); LutFile.DataLength = (UInt32)(LutFile.BinData[IndexDataLength] + (LutFile.BinData[IndexDataLength + 1] << 8) + (LutFile.BinData[IndexDataLength + 2] << 16) + (LutFile.BinData[IndexDataLength + 3] << 24)); // calculate CRC and compare this with the file CRC var entireFile = new List(); entireFile.AddRange(LutFile.BinData); var payload = new List(); payload.AddRange(entireFile.GetRange(IndexVersion, LutFileDataLength + LengthVersion)); var calculatedCrc = Crc16Ccitt.CalculateMsb1021(payload.ToArray()); if (calculatedCrc != LutFile.FileCrc) { ErrorMessage = "CRC mismatch!"; return false; } LutFile.CalculatedCrc = calculatedCrc; LutFile.IsValid = true; return true; } /// /// Download the CRC, the meter size and store calibration as preparation for the lookup table /// /// /// /// - Initial /// private Boolean FinalizeLutDownload() { var returnValue = true; var retryCtr = 0; if (LutFile == null || _genesisMeter == null || _meterFile == null) return false; // set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(5000); do { // for repeated entry if (!returnValue) { ReestablishMeterFileSystem(); } _actualOperation = "Write Lut CRC to meter"; _operationCtr++; returnValue = _genesisMeter.WriteRegister(Register.Genesisflow.LookupFileCrc, LutFile.FileCrc); if (!returnValue) { const String txt = "Meter LUT CRC could not be set!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); _operationCtr--; continue; } _operationCtr++; _actualOperation = "Write meter size"; returnValue = _genesisMeter.WriteRegister(Register.Genesisflow.MeterSize, new[] { (Byte)LutFile.MeterSize }); if (!returnValue) { const String txt = "Meter size could not be set!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); _operationCtr--; continue; } _operationCtr++; _actualOperation = "Store calibration to meter"; returnValue = _genesisMeter.WriteRegister(Register.Genesisflow.StoreCalibration, 1); if (!returnValue) { const String txt = "Meter store calibration failed!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); _operationCtr--; continue; } _operationCtr++; _actualOperation = "Read back LUT CRC from meter"; var lutCrc = RegisterConverter.ConvertTo( _genesisMeter.ReadRegister(Register.Genesisflow.LookupFileCrc)); if ((lutCrc & LutCrcErrorMask) != 0) { const String txt = "Meter LUT CRC could not be set!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); _operationCtr--; returnValue = false; continue; } OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($"Read back and validated LUT CRC 0x{lutCrc:X4}")); _operationCtr++; _actualOperation = "Read back meter size from meter"; var meterSize = (MeterSize)RegisterConverter.ConvertTo( _genesisMeter.ReadRegister(Register.Genesisflow.MeterSize)); if (meterSize != LutFile.MeterSize) { const String txt = "Meter size could not be set!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); _operationCtr--; returnValue = false; continue; } ErrorMessage = ""; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("Read back and validated meter size " + $"{MeterSizeConverter.ConvertMeterSizeEnumToSizeName(meterSize)}")); } while (!returnValue && retryCtr++ < RegisterWriteRetries && !StopUpdateProcess); // set timeout back to default value _genesisMeter.TransmitProtocol.SetDefaultResponseTimeout(); return returnValue && !StopUpdateProcess; } /// /// Read meter files of drive 1 /// /// /// /// - Initial /// private Boolean ListMeterFilesDrive1() { // logout and re-login to clear all ongoing processes and clear the Genesis buffers ReestablishMeterFileSystem(); if (_meterFilesDrive1 == null) { _meterFilesDrive1 = new List(); } _meterFilesDrive1.Clear(); OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("List meter files drive 1")); if (!_meterFile.ReadMeterFileCatalog(out var meterFilesDrive1, MeterFile.StrMeterDrive1, MeterFile.WildcardAll.ToString())) return false; _meterFilesDrive1.AddRange(meterFilesDrive1); // inform overlay system of found files foreach (var meterFile in _meterFilesDrive1) { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(meterFile)); } return true; } /// /// Erase file and check if done! /// /// /// /// - Initial /// private Boolean EraseFile(String fileDriveName) { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($"Erasing {fileDriveName}")); var returnValue = _meterFile.UnlockEraseWriteMeterFile(fileDriveName); // erase returns always TRUE, even if the erasure failed! Check with catalogue _meterFile.EraseMeterFile(fileDriveName); //if (ListMeterFilesDrive1()) //{ // if (_meterFilesDrive1.Contains(fileDriveName)) // { // returnValue = false; // var txt = $"Failed to erase {fileDriveName}!"; // ErrorMessage = txt; // OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); // } // else // { // ErrorMessage = ""; // OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($"Successfully erased {fileDriveName}")); // } //} //else //{ // returnValue = false; // const String txt = "Meter files read error!"; // ErrorMessage = txt; // OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); //} return returnValue; } /// /// Erase old LUT files from meter /// /// /// - Initial /// public Boolean EraseLutFiles() { //counters for actual process information var retryCtr = 0; if (LutFile == null) return false; //set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(10000); var returnValue = true; //erase loop and retry if not successful do { var filesToErase = new[] { StrLutMeterFileName, StrLutBackupMeterFileName }; // read all files which are already installed _actualOperation = "Catalogue installed meter files drive 1"; _operationCtr++; // read files on drive 1 to verify the LUT installation if (ListMeterFilesDrive1()) { foreach (var fileToErase in filesToErase) { if (!_meterFilesDrive1.Contains(fileToErase)) continue; _operationCtr++; _actualOperation = $"Erase {fileToErase}"; if (EraseFile(fileToErase)) { ErrorMessage = ""; continue; } ErrorMessage = $"LUT file {fileToErase} not erased!"; returnValue = false; _operationCtr--; } } if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess); //set timeout back to default value _genesisMeter.TransmitProtocol.SetDefaultResponseTimeout(); return !StopUpdateProcess && returnValue; } /// /// Download the metrology lookup table files and read the catalog of drive 1 /// /// /// /// - Initial /// private Boolean DownloadLutFiles() { //counters for actual process information var retryCtr = 0; if (LutFile == null) return false; //set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(1000); var returnValue = true; //write LUT files and retry if not successful do { var filesToDownload = new[] { StrLutMeterFileName, StrLutBackupMeterFileName }; foreach (var fileToDownload in filesToDownload) { _operationCtr++; _actualOperation = $"Write {fileToDownload}"; // logout and re-login to clear all ongoing processes and clear the Genesis buffers ReestablishMeterFileSystem(); _meterFile.UnlockEraseWriteMeterFile(fileToDownload); var txt = $"{fileToDownload} successfully written"; if (_meterFile.WriteMeterFile(fileToDownload, LutFile.BinData.ToArray())) { ErrorMessage = ""; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); continue; } txt = $"Writing of {fileToDownload} failed!"; ErrorMessage = txt; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); returnValue = false; _operationCtr--; } // read all files on drive 1 _operationCtr++; if (ListMeterFilesDrive1()) { foreach (var checkInstalledFile in filesToDownload) { if (_meterFilesDrive1.Contains(checkInstalledFile)) { ErrorMessage = ""; var txt = $"{checkInstalledFile} found in meter"; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(txt)); continue; } ErrorMessage = $"LUT file {StrLutMeterFileName} not installed!\n"; returnValue = false; } } else { returnValue = false; } if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess); //set timeout back to default value _genesisMeter.TransmitProtocol.SetDefaultResponseTimeout(); return !StopUpdateProcess && returnValue; } /// /// Upload the metrology lookup table files and read the catalog of drive 1 /// /// /// /// - Initial /// /// /// - Take the different size of original and receive buffer, which is an integer division by 4, into account. /// private Boolean UploadAndCompareLutFiles() { //counters for actual process information var retryCtr = 0; if (LutFile == null) return false; //set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(1000); var returnValue = true; //write LUT files and retry if not successful do { var filesToUpload = new[] { StrLutMeterFileName, StrLutBackupMeterFileName }; foreach (var fileToUpload in filesToUpload) { _operationCtr++; _actualOperation = $"Read {fileToUpload}"; // logout and re-login to clear all ongoing processes and clear the Genesis buffers ReestablishMeterFileSystem(); if (_meterFile.ReadMeterFile(fileToUpload, out var binData, SizeLutFile)) { // compare the entire buffer which also includes the header of the file, remind the original // buffer may be smaller due to the chunk size of the communication (always dividable by 4), // meaning the receive buffer is always and integer of this chunk size! if (LutFile.BinData.Where((t, ctr) => t != binData[ctr]).Any()) { ErrorMessage = $"Meter LUT file is unequal to downloaded {fileToUpload}!"; returnValue = false; _operationCtr--; } if (returnValue) { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($"LUT file {fileToUpload} of meter successfully compared!")); } } else { ErrorMessage += $"Reading of {fileToUpload} failed!"; returnValue = false; _operationCtr--; } } if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess); //set timeout back to default value _genesisMeter.TransmitProtocol.SetDefaultResponseTimeout(); return !StopUpdateProcess && returnValue; } /// /// Load file from memory /// /// to binary files /// /// /// - Initial /// public Boolean LoadLutFile(String filePathName) { if (!File.Exists(filePathName)) { return false; } LutFile = null; LutFile = new MeterLutFile(filePathName) { BinData = new List(File.ReadAllBytes(filePathName)) }; // validate the file return ValidateLutFile(); } #endregion } }