using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; 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.Logic.ProductionOrderCore.OrderData; 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; /// /// Threshold for power correction EMEA version /// public const UInt32 EmeaFwThresholdForLutFile= 0x1200; /// /// Threshold for power correction NA version /// public const UInt32 NaFwThresholdForLutFile = 0x2000; //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 IGenesisMeter _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(IGenesisMeter 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.Metrologyasst.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(IGenesisMeter 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, webRequest: 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 /// /// /// - Started state machine with download of new mettbl. /// 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.DownloadLutFile; 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 /// /// /// - LUT file validation exported to /// private Boolean ValidateLutFile() { // Take the file content to validate the format based var lutFileFormatValid = LutFile.ValidateLutFormat(); if (LutFile.CrcError) { ErrorMessage = "CRC mismatch!"; return false; } // check if LUT file contains data if (LutFile?.BinData == null || LutFile.BinData.Count != LutFile.SizeLutFile) { ErrorMessage = "File size mismatch!"; return false; } return lutFileFormatValid; } /// /// Download the CRC, the meter size and store calibration as preparation for the lookup table /// /// /// /// - Initial /// /// /// - Used genesis meter lut crc, region and meter size read back procedure. /// 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; } // read back all values _genesisMeter.CheckRegionSizeLutCrc(); var lutFileCrc = $"0x{LutFile.FileCrc:X4}"; if (!_genesisMeter.LutCrc.Equals(lutFileCrc)) { 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 {_genesisMeter.LutCrc}")); var lutMeterSize = MeterSizeConverter.ConvertMeterSizeEnumToSizeName(LutFile.MeterSize); if (!_genesisMeter.MeterSize.Equals(lutMeterSize)) { 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 {lutMeterSize}")); } 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)) 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! /// /// /// /// - 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); 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[] { //"1\\upg00", "1\\upg01", "1\\upg04","1\\upg08","1\\upg09","1\\upg12","1\\upg14","1\\upg15", MeterLutFile.StrLutMeterFileName, MeterLutFile.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 /// /// /// - Removed list file drive 1 to speed up process. Files will be read back and binary compared! /// 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[] { MeterLutFile.StrLutMeterFileName, MeterLutFile.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 (!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. /// /// /// - Exported file compare to . /// 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; //verify LUT files and retry if not successful do { var filesToUpload = new[] { MeterLutFile.StrLutMeterFileName, MeterLutFile.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.VerifyMeterFile(fileToUpload, LutFile.BinData.ToArray())) { ErrorMessage = $"Meter LUT file is unequal to downloaded {fileToUpload}!"; returnValue = false; _operationCtr--; } else { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($"LUT file {fileToUpload} of meter successfully compared!")); } } 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(); } /// /// Load file from memory /// /// origin Name /// list of bytes of the file strean /// /// /// - Initial /// public Boolean LoadLutFromStream(String name, List streamData ) { LutFile = null; LutFile = new MeterLutFile(name) { BinData = streamData }; // validate the file return ValidateLutFile(); } #endregion } }