using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; 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.GenesisCore.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile.Properties; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts; using Xylem.Common.Utils.ProcessExec; using Xylem.Common.Utils.ProcessExec.EventArguments; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile { /// /// Meter FW update procedure /// public class MeterFwUpdate : IProcessState { #region Variables /// /// Port scan result event for message dispatcher to caller /// public event EventHandler OnProcessUpdate; //name for upgrade control file private const String StrUpgradeCtrlFileName = "1\\upgrade"; //erase application private const String StrEraseAppCtrlFilePost = "*\r"; //upgrade application private const String StrInstallAppCtrlFilePost = ":\r"; //start identifier for upgrade file name (full name is "1\\upg02") private const String StrUpgradeAppPartialFileName = "1\\upg"; //start of binary application file private const String StrFileAppStartId = "APP>"; //position of CRC (LSB first) in binary file private const Int32 FileAppCrcIndex = 0x04; //position of version in binary file private const Int32 FileAppVersionIndex = 0x06; //position of application ID in binary file private const Int32 FileAppAppIdIndex = 0x28; //package file search string for applications including blank!!!! private const String StrPackageFileAppId = "Id "; private const String StrPackageFileAppCrc = "CRC "; private const String StrPackageFileAppVersion = "version "; //binary file name is a combination of "binfile" application ID underline version //e.g. binfile0F_0268.bin //private const String StrBinaryFilePre = "binfile"; //extension is always "bin" private const String StrFileAppExtensionFilter = "*.bin"; //extension is always "bin" //private const String StrPackageCtrlFileFileFilter = "*.txt"; //separator file name application to version //private const String StrBinaryFileSepAppIdVersion = "_"; //multiplier for core major version (core major * x + minor) private const Int32 MajorMultiply = 100; //update reties for files private const Int32 FileWriteRetries = 0; //trigger update retries private const Int32 TriggerWriteRetries = 2; //retry of entire update procedure private const Int32 UpdateProcedureRetries = 5; //threshold to increase the file write timing private const Int32 IncreaseTimeoutUpdateProcedureRetries = 4; //retry threshold for retry of entire file instead of single file parts to minimize fragmentation private const Int32 RetryEntireFileAfterFailedPartialRetries = 3; private Int32 _updateProcedureRetryCtr; /// /// Size of parts for file splitting /// public Int32 MaxPartialFileDataSize = 2 * 1024; /// /// Core revision minimum as double for comparison /// public Double? CoreRevisionMinimum { get; private set; } /// /// Core revision maximum as double for comparison /// public Double? CoreRevisionMaximum { get; private set; } //reminder for the core minimum requirement as string private String _strCoreRevisionMinimum; private String _strCoreRevisionMaximum; //search string to find the minimum supported version in product*.txt private const String StrCoreRevisionMinId = "Minimum supported core revision: "; //search string to find the maximum supported version in product*.txt private const String StrCoreRevisionMaxId = "Maximum supported core revision: "; //search string to find the "Cordonel" identifier in product*.txt private const String StrCoreRevisionCordonelId = "Cordonel "; // example text: Minimum supported core revision: "Cordonel 1.62" // example text: Maximum supported core revision: "Cordonel 1.66" /// /// Update files to write to meter. /// An update package contains the complete set of files needed for a /// tested release even if they already exist in the meter with this version. /// public readonly List FileApps = new List(); //update file(parts) list remaining private readonly List _remainingFileAppsParts = new List(); //update file (parts) list which succeeded private readonly List _succeededFileAppsParts = new List(); //update file(parts) list which failed private readonly List _failedFileAppsParts = new List(); //update meter application list which failed private readonly List _updatedMeterApps = new List(); private GenesisMeter _genesisMeter; private String _pcbId; private String _backupPcbId = ""; private MeterFile _meterFile; // Text for caller to inform about actual process being carried out private String _actualOperation; private FwUpdateState _fwUpdateState; // Process counter for caller to monitor actual progress private Int32 _processedBytesCtr; private Int32 _overallBytesCtr; // Needed for repeated update of files to avoid overwriting of update control file private String _upgradeCtrlFile; //the package file describes all files needed for a specific release private String _packageDescriptionFile; /// /// Enable partial file writes of single parts with gaps in between /// public Boolean SingleFilePartsRetryEnable = true; /// /// Enable partial file writes from first failed part to the end /// public Boolean ConsecutiveFilePartsRetryEnable = false; /// /// 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(GetFwUpdateStateOperation(), OverallProcessCtrPercent, _actualOperation, e.OverallProcessPercent)); } #endregion #region Information /// /// Process counter in percent /// /// /// - Initial /// public Double OverallProcessCtrPercent { get { var processFileBytes = _meterFile?.ProcessedBytesCtr ?? 0; return 100.0 * (processFileBytes + _processedBytesCtr) / (_overallBytesCtr > 0 ? _overallBytesCtr : 1); } } /// /// Single file process counter in percent /// /// /// - Initial /// public Double SingleFileProcessCtrPercent => _meterFile?.ProcessCtrPercent ?? 0; #endregion #region CtorAssignment /// /// Ctor /// /// /// /// - Initial /// /// /// - Checked Genesis assignment before access. /// /// /// - Check installed application before require access to registers. /// public MeterFwUpdate(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 and force new update control file /// /// /// /// - Initial /// /// /// - Moved Genesis preparation to /// public void AssignGenesis(GenesisMeter genesisMeter) { _genesisMeter = genesisMeter; _actualOperation = ""; _upgradeCtrlFile = ""; _fwUpdateState = FwUpdateState.Idle; _pcbId = _genesisMeter?.PcbId; _remainingFileAppsParts.Clear(); _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); } /// /// Dispose /// public void Dispose() { _remainingFileAppsParts.Clear(); _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); _updatedMeterApps.Clear(); FileApps.Clear(); if (_meterFile == null) { return; } _meterFile.OnProcessUpdate -= ProcessUpdate_Event; _meterFile = null; } #endregion #region Compare /// /// Compare file and meter applications /// /// /// /// - Initial /// /// /// - Single application comparison exported /// /// /// - Parameter from call removed, genesis is assigned! /// /// /// - Removed genesis meter has to be logged in /// public void CompareAllMeterAndFileApps() { if (_genesisMeter == null || FileApps == null) { return; } foreach (var meterApp in _genesisMeter.MeterAppListVersion) { CompareMeterAppWithFileApp(meterApp); } if (!_genesisMeter.IsLoggedOn) { _genesisMeter.ReLogin(); //set display code to up to date if not needed to be installed or up to date _genesisMeter.SetProcessState(_genesisMeter.MeterAppListVersion.All( f => f.Status == MeterAppState.MeterAppUpToDate || f.Status == MeterAppState.MeterAppNotInstalled || f.Status == MeterAppState.Unknown) ? DisplayCodes.FwUpToDate : DisplayCodes.FwUpdateFailed, false); _genesisMeter.Logout(); } else { //set display code to up to date if not needed to be installed or up to date _genesisMeter.SetProcessState(_genesisMeter.MeterAppListVersion.All( f => f.Status == MeterAppState.MeterAppUpToDate || f.Status == MeterAppState.MeterAppNotInstalled || f.Status == MeterAppState.Unknown) ? DisplayCodes.FwUpToDate : DisplayCodes.FwUpdateFailed, false); } } /// /// Compare single meter application with file /// /// /// /// - Initial /// /// /// - Status text moved to meter and file /// /// /// - Compare changed /// /// /// - Meter state unknown if file apps not loaded /// public void CompareMeterAppWithFileApp(MeterApplications meterApp) { meterApp.Update = false; meterApp.Erase = false; //CASE -1: meter application is installed but files are not loaded if (meterApp.IsInstalled && FileApps.Count == 0) { meterApp.Status = MeterAppState.Unknown; return; } //CASE 0: meter application is not installed and update files are not loaded if (!meterApp.IsInstalled && FileApps.Count == 0) { meterApp.Status = MeterAppState.MeterAppNotInstalled; return; } foreach (var fileApp in FileApps) { if (meterApp.AppId != fileApp.AppId) { continue; } //CASE 1: update file for application is invalid // - in this case the entire package download will be denied, // the information is going to be displayed in the status. if (!fileApp.IsValid) { meterApp.Status = MeterAppState.FileAppInvalid; return; } //CASE 2: meter application is installed if (meterApp.IsInstalled) { //CASE 2.1 update file for application identical if (meterApp.StrVersion == fileApp.StrVersion && meterApp.Crc == fileApp.Crc) { meterApp.Status = MeterAppState.MeterAppUpToDate; return; } //CASE 2.2 update file download succeeded //CASE 2.3 update file download failed //CASE 2.4 update file download suspicious if (CheckDownloadedMeterApps(meterApp)) { return; } //CASE 2.5 update file and meter application are not identical if (meterApp.StrVersion != fileApp.StrVersion) { meterApp.Status = MeterAppState.MeterAppVersionOutdated; meterApp.Update = true; return; } //CASE 2.6 meter file to update file for application CRC mismatch if (meterApp.Crc == fileApp.Crc) { return; } meterApp.Status = MeterAppState.InvalidCrc; meterApp.Update = true; return; } //CASE 3: meter application is not installed else { //CASE 3.1 update file download succeeded //CASE 3.2 update file download failed //CASE 3.3 update file download suspicious if (CheckDownloadedMeterApps(meterApp)) { return; } //CASE 3.4 update file exists and is not identical to downloaded version meterApp.Status = MeterAppState.MeterAppInstallationRequired; meterApp.Update = true; return; } } //CASE 4.1: update file application not found and meter application is installed if (meterApp.IsInstalled) { meterApp.Status = MeterAppState.MeterAppErasureRequired; meterApp.Erase = true; return; } //CASE 4.2: update file application not found and meter application is not installed meterApp.Status = MeterAppState.Unknown; } /// /// Check last downloads /// /// /// /// - Initial /// public Boolean CheckDownloadedMeterApps(MeterApplications meterApp) { if (_pcbId != _backupPcbId) { return false; } foreach (var fileApp in FileApps) { if (meterApp.AppId != fileApp.AppId) { continue; } //Assuming on retries the latest must be the succeeded one and therefore is going to //overwrite the failed status! if (_updatedMeterApps.Any(f => f.AppId == meterApp.AppId)) { foreach (var updateMeterApp in _updatedMeterApps) { if (fileApp.AppId != updateMeterApp.AppId) { continue; } if (fileApp.StrVersion == updateMeterApp.StrVersion && fileApp.Version == updateMeterApp.Version && fileApp.Crc == updateMeterApp.Crc) { meterApp.Status = updateMeterApp.Status; if (meterApp.Status == MeterAppState.MeterAppDownloadFailed) { meterApp.Update = true; } } else if (meterApp.IsInstalled) { meterApp.Status = MeterAppState.MeterAppVersionOutdated; meterApp.Update = true; } else { meterApp.Status = MeterAppState.MeterAppInstallationRequired; meterApp.Update = true; } } return true; } } return false; } /// /// Remind result of download for reload of files, new connect or compare /// /// /// /// - Initial /// /// /// - List removal of elements simplified /// private void RemindDownloadedFileApps() { //Check for downloaded update files for this PCB ID if (_backupPcbId != _pcbId) { return; } foreach (var meterApp in _genesisMeter.MeterAppListVersion) { //build update files reminder var updateMeterApp = new MeterApplications { AppId = meterApp.AppId, AppName = meterApp.AppName }; //check if at least one part of this file download failed if (_failedFileAppsParts.Any(f => f.AppId == meterApp.AppId)) { foreach (var fileApp in FileApps) { if (fileApp.AppId != meterApp.AppId) { continue; } //remove all update files which are overwritten _updatedMeterApps.RemoveAll(f => f.AppId == meterApp.AppId); updateMeterApp.Status = MeterAppState.MeterAppDownloadFailed; updateMeterApp.IsInstalled = false; updateMeterApp.StrVersion = fileApp.StrVersion; updateMeterApp.Version = fileApp.Version; updateMeterApp.Crc = fileApp.Crc; _updatedMeterApps.Add(updateMeterApp); } } //if the download succeeded else if (_succeededFileAppsParts.Any(f => f.AppId == meterApp.AppId)) { foreach (var fileApp in FileApps) { if (fileApp.AppId != meterApp.AppId) { continue; } //remove all update files which are overwritten _updatedMeterApps.RemoveAll(f => f.AppId == meterApp.AppId); updateMeterApp.Status = MeterAppState.MeterAppDownloadSucceeded; updateMeterApp.IsInstalled = true; updateMeterApp.StrVersion = fileApp.StrVersion; updateMeterApp.Version = fileApp.Version; updateMeterApp.Crc = fileApp.Crc; _updatedMeterApps.Add(updateMeterApp); } } } } #endregion #region ProcessInformation /// /// Read the failed update files /// /// /// - Initial /// public List GetFailedFileApps() => _failedFileAppsParts; /// /// Returns the actual file in process for update /// /// /// /// - Initial /// public String GetActualOperation() { return _actualOperation; } private Int32 _actualUpdateFileCtr; /// /// Returns the FW Update state /// /// /// /// - Initial /// /// /// - Changed to localizable language /// public String GetFwUpdateStateOperation() { // search text assigned to current state var text = FwUpdateStateInfo.GetTextFromState(_fwUpdateState); if (_fwUpdateState == FwUpdateState.Idle) { return text; } text += _updateProcedureRetryCtr > 0 ? $"{Resources.StrProcessRetry}{_updateProcedureRetryCtr}" : ""; if (_remainingFileAppsParts.Count > 0) { text += $"{Resources.StrPartOfParts}{_actualUpdateFileCtr}/{_remainingFileAppsParts.Count}"; } 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(); } private Boolean _stopUpdateProcess; /// /// Stop the update process /// public Boolean StopUpdateProcess { get => _stopUpdateProcess; set { _stopUpdateProcess = value; if (_meterFile != null) { _meterFile.StopProcess = value; } _remainingFileAppsParts.Clear(); _fwUpdateState = FwUpdateState.UpdateFailed; } } /// /// Update meter firmware /// /// /// /// - Initial /// /// /// - Build and send of upgrade control file split, /// - OverallByteCounter re-calculated on remaining update file sizes. /// /// /// - Repeated trigger update if this failed. /// /// /// - Status. /// /// /// - State machine implemented /// /// /// - Update preparation exported /// /// /// - Exit preparation added /// public Boolean UpdateMeterFw() { if (!UpdatePreparation()) { return false; } _fwUpdateState = FwUpdateState.StartInitial; while (_fwUpdateState != FwUpdateState.Idle && !StopUpdateProcess) { FwUpdateStateMachine(); } return ExitPreparation(true); } /// /// Upload selected update files and compare them with update files /// /// /// - Initial /// public Boolean ManualVerifyFiles() { if (!UpdatePreparation()) { return false; } return ExitPreparation(true); } /// /// Preselects the downloaded files for trigger upgrade /// /// /// - Initial /// /// /// - Compare changed /// /// /// - Prepare trigger changed to fulfill automatic FW update /// public Boolean PrepareTrigger() { //mark download foreach (var meterApp in _genesisMeter.MeterAppListVersion) { meterApp.Update = meterApp.Status == MeterAppState.MeterAppDownloadSucceeded; } return true; } /// /// Preselects the downloaded files for trigger upgrade /// /// /// - Initial /// public Boolean ManualDownloadRemainingFileAppsParts() { if (!UpdatePreparation()) { return false; } _fwUpdateState = FwUpdateState.DownloadFileApps; var returnValue = DownloadFileApps(); _fwUpdateState = FwUpdateState.Idle; return ExitPreparation(returnValue); } /// /// Download selected update files from beginning /// /// /// - Initial /// /// /// - Exit preparation added /// /// /// - Initial check changed /// public Boolean ManualDownloadAllFileApps() { if (!ManualUpdatePreparation()) { return false; } _updateProcedureRetryCtr = 0; _fwUpdateState = FwUpdateState.BuildFileApps; BuildFileApps(); _fwUpdateState = FwUpdateState.DownloadFileApps; var returnValue = DownloadFileApps(); _fwUpdateState = FwUpdateState.Idle; return ExitPreparation(returnValue); } /// /// Build update control file and trigger update of selected files /// /// /// - Initial /// public Boolean ManualTriggerUpgrade() { if (!ManualUpdatePreparation()) { return false; } _fwUpdateState = FwUpdateState.BuildUpgradeControlFile; var returnValue = BuildUpgradeCtrlFile(); if (!returnValue) { return false; } _fwUpdateState = FwUpdateState.DownloadUpgradeControlFile; returnValue &= DownloadUpgradeCtrlFile(); if (!returnValue) { return false; } _fwUpdateState = FwUpdateState.TriggerUpgrade; returnValue &= TriggerUpgrade(); _fwUpdateState = FwUpdateState.Idle; return ExitPreparation(returnValue); } /// /// Reset FW update process and all lists and counters /// /// /// - Initial /// /// /// - New items added for clearance. /// /// /// - Reestablish file system (logout/login/auto-login). /// /// /// - Logout added. /// public void ResetAll() { //remove last update control file _upgradeCtrlFile = ""; //clear all remaining file application parts for a fresh start _remainingFileAppsParts.Clear(); _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); _updatedMeterApps.Clear(); //remove the update stop action StopUpdateProcess = false; //reset all counters _processedBytesCtr = 0; _overallBytesCtr = 0; _updateProcedureRetryCtr = 0; //set state machine for automatic FW update _fwUpdateState = FwUpdateState.Idle; _actualOperation = ""; //update file information CompareAllMeterAndFileApps(); //logout/login/auto-login ReestablishMeterFileSystem(); _genesisMeter?.Logout(); } /// /// Common update preparation and check for manual updates /// /// /// - Initial /// public Boolean ManualUpdatePreparation() { if (!UpdatePreparation()) { return false; } ReestablishMeterFileSystem(); //remove last update control file _upgradeCtrlFile = ""; //clear all remaining file application parts for a fresh start _remainingFileAppsParts.Clear(); return true; } /// /// Common update preparation and check /// /// /// - Initial /// /// /// - ReLogin added. /// /// /// - Display FW update codes in meter display added. /// /// /// - Genesis preparation to keep Cordonel-CPU load low. /// /// /// - Installed ProcessUpdate_Event. /// /// /// - Set file write timeout to default. /// public Boolean UpdatePreparation() { _actualOperation = ""; _fwUpdateState = FwUpdateState.Idle; _meterFile = new MeterFile(_genesisMeter); if (_meterFile != null) { _meterFile.OnProcessUpdate += ProcessUpdate_Event; } _genesisMeter?.ReLogin(); _genesisMeter?.SetProcessState(DisplayCodes.FwUpdateActive, false); if (_genesisMeter == null || !_genesisMeter.IsLoggedOn || FileApps == null || _meterFile == null) { 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 /// /// /// - keep file information /// /// /// - Reestablish file system (logout/login/auto-login). /// /// /// - Logout added. /// /// /// - Uninstalled ProcessUpdate_Event. /// public Boolean ExitPreparation(Boolean returnValue) { if (_meterFile != null) { _meterFile.OnProcessUpdate -= ProcessUpdate_Event; } _meterFile = null; ReestablishMeterFileSystem(); _genesisMeter?.Logout(); if (!StopUpdateProcess) { return returnValue; } _actualOperation = Resources.StrWaiting; return false; } #endregion #region StateMachine /// /// State machine foe FW update process /// /// /// - Initial /// /// /// - Modified /// /// /// - Prepare trigger added to select all successfully downloaded applications /// even if only a selection of application has been downloaded in this run. /// /// /// - Removed remaining files if partial write denied, /// - Sequences changed to build update control file after prepare trigger. /// /// /// - Register Retry setup removed here. /// /// /// - Retry entire file if some partial file writes failed (may be caused by FW file /// system access error due to fragmentation) to create a new file on a different /// location in the FW file system . /// /// /// - Increased timeout for file write access after a procedure retry threshold /// . This threshold seems to be /// useful as on increased size and number of applications needed to install in one run, the /// file system is slowing down for write accesses. /// private void FwUpdateStateMachine() { switch (_fwUpdateState) { case FwUpdateState.Idle: break; case FwUpdateState.StartInitial: _updateProcedureRetryCtr = 0; _fwUpdateState = FwUpdateState.BuildFileApps; break; case FwUpdateState.BuildFileApps: if (!SingleFilePartsRetryEnable && !ConsecutiveFilePartsRetryEnable) { _remainingFileAppsParts.Clear(); } BuildFileApps(); _fwUpdateState = FwUpdateState.DownloadFileApps; break; case FwUpdateState.DownloadFileApps: _fwUpdateState = DownloadFileApps() ? FwUpdateState.BuildUpgradeControlFile : FwUpdateState.StepFailed; break; case FwUpdateState.BuildUpgradeControlFile: //prepare the trigger to select all successfully downloaded applications PrepareTrigger(); _fwUpdateState = BuildUpgradeCtrlFile() ? FwUpdateState.DownloadUpgradeControlFile : FwUpdateState.StepFailed; break; case FwUpdateState.DownloadUpgradeControlFile: _fwUpdateState = DownloadUpgradeCtrlFile() ? FwUpdateState.TriggerUpgrade : FwUpdateState.StepFailed; break; case FwUpdateState.TriggerUpgrade: _fwUpdateState = TriggerUpgrade() ? FwUpdateState.Idle : FwUpdateState.StepFailed; break; case FwUpdateState.StepFailed: _processedBytesCtr = 0; _overallBytesCtr = 0; //retry entire file if some partial file writes failed if (RetryEntireFileAfterFailedPartialRetries == _updateProcedureRetryCtr) { _remainingFileAppsParts.Clear(); } _fwUpdateState = _updateProcedureRetryCtr++ < UpdateProcedureRetries ? FwUpdateState.RepeatFwUpdate : FwUpdateState.UpdateFailed; break; case FwUpdateState.RepeatFwUpdate: _processedBytesCtr = 0; _overallBytesCtr = 0; _fwUpdateState = FwUpdateState.BuildFileApps; if (_updateProcedureRetryCtr >= IncreaseTimeoutUpdateProcedureRetries) { _meterFile?.SetExtremeFileWriteTimeout(); } break; case FwUpdateState.UpdateFailed: _processedBytesCtr = 0; _overallBytesCtr = 0; StopUpdateProcess = true; _fwUpdateState = FwUpdateState.Idle; break; case FwUpdateState.UploadFileApps: break; case FwUpdateState.CompareFileApps: break; case FwUpdateState.EraseUpdateFile: break; default: _fwUpdateState = FwUpdateState.Idle; break; } } #endregion #region FileApp /// /// Load all binary files from given path, this path has to contain all files for a specific build, /// this has to be a tested combination of concatenated applications /// /// to binary files /// /// /// - Initial /// /// /// - Clear file lists on reload. /// /// /// - Return false if one package file is invalid. /// public Boolean LoadFileApps(String path) { var fileNames = Directory.GetFiles(path, StrFileAppExtensionFilter); if (fileNames.Length == 0) { return false; } FileApps.Clear(); foreach (var fileName in fileNames) { if (!fileName.Contains("_complete.bin")) { var fileApplication = new FileApplications(fileName) { BinData = new List(File.ReadAllBytes(fileName)) }; //add always to package files for later analysis FileApps.Add(fileApplication); } } return SetLoadedFiles(); } /// /// Load all binary files from from obj, /// this has to be a tested combination of concatenated applications /// /// to binary files /// /// /// - Initial /// /// /// - Clear file lists on reload. /// /// /// - Return false if one package file is invalid. /// public Boolean LoadFileApps(List sourceFileApps) { FileApps.Clear(); FileApps.AddRange(sourceFileApps); return SetLoadedFiles(); } /// /// Load all binary files from given Dictionary, this Dictionary has to contain all files for a specific build, /// this has to be a tested combination of concatenated applications /// /// file names with binary content /// /// /// - Initial /// public Boolean LoadFileApps(Dictionary> apps) { if (!apps.Any()) { return false; } FileApps.Clear(); foreach (var fileApplication in apps.Select(fileApp => new FileApplications(fileApp.Key) { BinData = fileApp.Value })) { //add always to package files for later analysis FileApps.Add(fileApplication); } return SetLoadedFiles(); } private Boolean SetLoadedFiles() { var returnValue = true; foreach (var fileApplication in FileApps) { fileApplication.IsValid = CheckFileApp(fileApplication); if (!fileApplication.IsValid) { returnValue = false; } } _remainingFileAppsParts.Clear(); _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); return true; } /// /// Check the content of the binaries: /// - begins with "APP>", /// - Application ID at position 0x28, /// - CRC at position 0x04 LSB, 0x05 MSB /// - Version at position 0x06 and 0x07 decimal /// - Build upgrade meter file name /// /// /// /// - Initial /// /// /// - New file version string builder. /// private static Boolean CheckFileApp(FileApplications fileApp) { fileApp.AppId = fileApp.BinData[FileAppAppIdIndex]; fileApp.Crc = (UInt16)(fileApp.BinData[FileAppCrcIndex] + (fileApp.BinData[FileAppCrcIndex + 1] << 8)); fileApp.Version = (UInt32)((fileApp.BinData[FileAppVersionIndex] & 0x0F) + ((fileApp.BinData[FileAppVersionIndex] & 0xF0) >> 4) * 10 + (fileApp.BinData[FileAppVersionIndex + 1] & 0x0F) * 100 + ((fileApp.BinData[FileAppVersionIndex + 1] & 0xF0) >> 4) * 1000); fileApp.StrVersion = GenesisMeter.BuildFwVersionString(fileApp.BinData[FileAppVersionIndex + 1], fileApp.BinData[FileAppVersionIndex]); var fileAppStartId = Encoding.UTF8.GetString(fileApp.BinData.ToArray(), 0, 4); //check update file start identifier if (fileAppStartId != StrFileAppStartId) { return false; } //TODO THW check CRC //TODO THW check upgrade file name against content to select a valid package //the meter file name is needed for writing the file to the meter or reading it back fileApp.MeterFilename = StrUpgradeAppPartialFileName + $"{fileApp.AppId:X2}"; return true; } /// /// Collect FileApps out of FileApps and split them into /// smaller pieces if the files are larger than a threshold for a single /// write size. Each part of a partitioned file will be handles as separate /// file in the _remainingFileAppsParts distinguished by their part number. /// The update file generation will be skipped if _remainingFileAppsParts /// is not zero or the file is marked as successfully downloaded! /// /// /// /// - Initial /// /// /// - Compare changed /// private void BuildFileApps() { //avoid overwriting of valid update files being able to retry single files if (_remainingFileAppsParts.Count != 0) { return; } //build update control file foreach (var meterApp in _genesisMeter.MeterAppListVersion) { //check if update is required if (!meterApp.Update) { continue; } //search valid update file in UpdatePackage foreach (var updateFile in FileApps) { if (meterApp.AppId != updateFile.AppId || !updateFile.IsValid) { continue; } //copy application name updateFile.AppName = meterApp.AppName; //skip files which are successfully downloaded if (meterApp.Status != MeterAppState.MeterAppDownloadSucceeded) { //add this update file to update file list PartitionFileApp(updateFile); } } } } /// /// Build update files, large files will be split to several small pieces /// handled as an individual update file /// /// /// /// - Initial /// /// /// - Version string added /// private void PartitionFileApp(FileApplications fileApp) { //split file to portions var dataByteList = new List(); dataByteList.AddRange(fileApp.BinData.ToArray()); //calculate records to send based on MaxPartialFileDataSize var chunkCounts = dataByteList.Count / MaxPartialFileDataSize; if (0 != dataByteList.Count % MaxPartialFileDataSize) { chunkCounts += 1; } var dataChunk = new List(); for (var ctr = 0; ctr < chunkCounts; ctr++) { dataChunk.Clear(); var index = ctr * MaxPartialFileDataSize; var size = index + MaxPartialFileDataSize > dataByteList.Count ? dataByteList.Count - index : MaxPartialFileDataSize; dataChunk.AddRange(dataByteList.GetRange(index, size)); var updateFileAppPart = new FileApplications("") { AppName = fileApp.AppName, AppId = fileApp.AppId, IsValid = fileApp.IsValid, StrVersion = fileApp.StrVersion, MeterFilename = fileApp.MeterFilename, MeterFileOffset = index, Part = ctr + 1, Parts = chunkCounts }; //copy binary data updateFileAppPart.BinData.AddRange(dataChunk); _remainingFileAppsParts.Add(updateFileAppPart); } } /// /// Download and result handling for update files /// /// /// /// - Initial /// /// /// -Reestablish meter file system on failed file download, /// information extended and automatism added. /// /// /// -Compare changed. /// /// /// -Exported file download marker, /// -Stop file download at part 1 immediately, because this is needed /// for file generation, a retry of part 1 will overwrite the rest /// of the file! Retries for part 1 are only allowed together with all /// other parts! /// /// /// - Partial file write single parts selectable! /// - Partial file write after first error selectable! /// private Boolean DownloadFileApps() { var returnValue = true; //counters for actual process information _processedBytesCtr = 0; _overallBytesCtr = 0; //counter for overall process information _actualUpdateFileCtr = 0; //clear markers of successfully and failed downloaded parts for this run _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); //calculate the byte in advance! foreach (var fileAppsPart in _remainingFileAppsParts) { //remind bytes to process _overallBytesCtr += fileAppsPart.BinData.Count; } foreach (var meterApp in _genesisMeter.MeterAppListVersion) { //loop all upgrade files foreach (var fileAppsPart in _remainingFileAppsParts) { if (meterApp.AppId != fileAppsPart.AppId) { continue; } _actualUpdateFileCtr++; //mark ongoing DOWNLOAD and mark this for caller meterApp.Status = MeterAppState.MeterAppDownloadActive; meterApp.Update = false; //try to update files if (_meterFile.UnlockEraseWriteMeterFile(fileAppsPart.MeterFilename) && DownloadFileAppPart(fileAppsPart)) { //remind succeeded files for removal from update file list _succeededFileAppsParts.Add(fileAppsPart); } else { //ReEstablishMeterFileSystem(); _failedFileAppsParts.Add(fileAppsPart); //the update procedure has to be repeated, skipping the successful written files returnValue = false; } //check if something was successful if (_succeededFileAppsParts.Any(f => f.AppId == meterApp.AppId)) { meterApp.Status = MeterAppState.MeterAppDownloadSucceeded; } //overwrite status to failed if one part failed if (_failedFileAppsParts.Any(f => f.AppId == meterApp.AppId)) { meterApp.Status = MeterAppState.MeterAppDownloadFailed; //continue update of other parts except part 1 if (SingleFilePartsRetryEnable) { //break on file part 1 failed to avoid update of other parts because //part one will always build a new file if (fileAppsPart.Part == 1) { break; } } //break if one file part failed all following parts to use the FSeek on next //trial to position the remaining parts at the right position else if (ConsecutiveFilePartsRetryEnable) { break; } else { //remove all file parts from succeeded file part list _succeededFileAppsParts.RemoveAll(f => f.AppId == meterApp.AppId); //stop update for entire file, retry of all file parts are required break; } } if (StopUpdateProcess) { return false; } } } //remove all valid written files or parts of it from list to keep the outstanding //update files in the list for a repeated update foreach (var succeededFileAppsPart in _succeededFileAppsParts) { _remainingFileAppsParts.Remove(succeededFileAppsPart); } //remind PCBID for this run _backupPcbId = _pcbId; RemindDownloadedFileApps(); CompareAllMeterAndFileApps(); return returnValue; } /// /// Write one update file to meter, /// due to partitioning of large files this might be just one part of /// the package file here handled as separate update file. /// The file write routine will retry including re-establishing of /// file system by logout and login again (this will close all open files). /// /// /// /// /// - Initial /// /// /// - Partitioning of files exported /// /// /// - Re-establish file system added /// /// /// - Return value retry counter for 0 retries corrected /// private Boolean DownloadFileAppPart(FileApplications fileAppPart) { var retryCtr = 0; Boolean returnValue; //write file portion and retry if not successful do { //if a large file is split into several small files, the part information will //be added var strPart = fileAppPart.Parts > 1 ? $"{Resources.StrPartOfParts}{fileAppPart.Part}" + $"/{fileAppPart.Parts}" : ""; var strRetries = retryCtr > 0 ? $"{Resources.StrProcessRetry}{retryCtr}" : ""; _actualOperation = $"{Resources.StrProcessDownload} {fileAppPart.AppName}" + strPart + strRetries; returnValue = _meterFile.WriteMeterFile(fileAppPart.MeterFilename, fileAppPart.BinData.ToArray(), fileAppPart.MeterFileOffset); if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess); returnValue &= retryCtr <= FileWriteRetries; //update information for process bar _processedBytesCtr += fileAppPart.BinData.Count; return !StopUpdateProcess && returnValue; } #endregion #region UpgradeCtrlFile /// /// Build the upgrade control file. /// The update control file generation will be skipped if the update control file /// is set! /// /// /// /// - Initial /// private Boolean BuildUpgradeCtrlFile() { //avoid overwriting of valid update control file being able to retry single files if (_upgradeCtrlFile != "") { return true; } //build update control file foreach (var meterApp in _genesisMeter.MeterAppListVersion) { //check if update is required if (meterApp.Update) { //add this update file requirement to control file _upgradeCtrlFile += $"{meterApp.AppId:X2}" + StrInstallAppCtrlFilePost; } //check if erase is required else if (meterApp.Erase && meterApp.IsInstalled) { //add this erase requirement to control file _upgradeCtrlFile += $"{meterApp.AppId:X2}" + StrEraseAppCtrlFilePost; } } return _upgradeCtrlFile != ""; } /// /// Send the upgrade control file to the meter /// /// /// /// - Exported writing of upgrade control file /// /// /// - Restore performance registers before trigger upgrade. /// private Boolean DownloadUpgradeCtrlFile() { //initiate upgrade by writing the upgrade control file if (_upgradeCtrlFile == "" || !_meterFile.UnlockEraseWriteMeterFile(StrUpgradeCtrlFileName)) { return false; } _actualOperation = Resources.StrFwUpdateStateDownloadUpgradeControlFile; var retryCtr = 0; Boolean returnValue; //write file and retry if not successful do { returnValue = _meterFile.WriteMeterFile(StrUpgradeCtrlFileName, Encoding.ASCII.GetBytes(_upgradeCtrlFile)); if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess); // restore register values before TriggerUpgrade, after TriggerUpgrade the Gensis // cannot be reached for a certain time. foreach (var item in _registersAfterUpdate) { _genesisMeter.WriteRegister(item.Key, item.Value); } return !StopUpdateProcess && returnValue; } #endregion #region TriggerUpgrade /// /// Send the trigger update /// /// /// /// - Initial /// /// /// - If trigger upgrade succeeded mark all actually selected package files /// as succeeded and remove update or erase marker /// /// /// - Delete downloaded (updated) meter application files, they are validated on /// accepted trigger. /// /// /// - Extended timeout. /// private Boolean TriggerUpgrade() { Boolean returnValue; //trigger upgrade _actualOperation = Resources.StrFwUpdateStateTriggerUpgrade; var triggerCtr = 0; //set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(5000); do { returnValue = _genesisMeter.WriteRegister(Register.System.TriggerFwUpgrade, 1); if (!returnValue) { ReestablishMeterFileSystem(); } } while (!returnValue && triggerCtr++ < TriggerWriteRetries && !StopUpdateProcess); //set timeout back to default value _genesisMeter.TransmitProtocol.SetDefaultResponseTimeout(); _actualOperation = returnValue ? Resources.StrMeterAppTriggerAccepted : Resources.StrMeterAppTriggerFailed; if (!returnValue || StopUpdateProcess) { return false; } //prepare temporary information based on the trigger upgrade feedback, //this information has to be validated with reading all FW versions //mark package files which are in this update as succeeded foreach (var meterApp in _genesisMeter.MeterAppListVersion) { //check if update is required and now acknowledged if (meterApp.Update) { //search for according update file foreach (var fileApp in FileApps) { if (meterApp.AppId != fileApp.AppId) { continue; } meterApp.StrVersion = fileApp.StrVersion; meterApp.Crc = fileApp.Crc; meterApp.Version = fileApp.Version; meterApp.IsInstalled = true; } } //check if erase is require else if (meterApp.Erase) { meterApp.IsInstalled = false; meterApp.StrVersion = ""; meterApp.Version = 0; meterApp.Crc = 0; } } _updatedMeterApps.Clear(); CompareAllMeterAndFileApps(); return true; } #endregion #region Conversion /// /// Build application id string /// /// /// public static String ConvertAppIdToString(UInt32 appId) { return $"0x{appId:X2}"; } /// /// Build a version string like 2.02 /// /// /// public static String ConvertVersionToString(UInt32 version) { return $"{version / 100}.{version % 100:D2}"; } /// /// Build the hexadecimal CRC string /// /// /// public static String ConvertCrcToString(UInt32 crc) { return $"0x{crc:X4}"; } #endregion #region PackageFile /// /// Load package control file /// /// to binary files /// /// /// - Initial /// public Boolean LoadPackageFile(String filePathName) { if (!File.Exists(filePathName)) { return false; } _packageDescriptionFile = File.ReadAllText(filePathName); return _packageDescriptionFile.Length != 0; } /// /// Load package control file /// /// control file content /// /// /// - Initial /// public Boolean LoadPackageFileFromText(String text) { _packageDescriptionFile = text; return _packageDescriptionFile.Length != 0; } /// /// Validate package file with update files /// /// /// /// - Initial /// /// /// - Version string used instead of conversion from UInt32 to string /// /// /// - Return false if one package file is checked being invalid. /// /// /// - Extraction of valid core revisions min/max. /// public Boolean ValidateFileAppsWithPackageFile() { if (string.IsNullOrEmpty(_packageDescriptionFile) || FileApps.Count == 0 || FileApps.Any(f => !f.IsValid)) { return false; } var packageFileLines = _packageDescriptionFile.Split('\n'); var validAppsCtr = 0; foreach (var fileApp in FileApps) { //find a line which contains all required information validAppsCtr += packageFileLines.Count(line => line.Contains($"{StrPackageFileAppId}{ConvertAppIdToString(fileApp.AppId)}") && line.Contains($"{StrPackageFileAppCrc}{ConvertCrcToString(fileApp.Crc)}") && line.Contains($"{StrPackageFileAppVersion}{fileApp.StrVersion}")); } //extract the core revision fields _strCoreRevisionMinimum = packageFileLines.First(line => line.Contains(StrCoreRevisionMinId) && line.Contains(StrCoreRevisionCordonelId)); _strCoreRevisionMaximum = packageFileLines.First(line => line.Contains(StrCoreRevisionMaxId) && line.Contains(StrCoreRevisionCordonelId)); CoreRevisionMinimum = ExtractVersionFromString(_strCoreRevisionMinimum); CoreRevisionMaximum = ExtractVersionFromString(_strCoreRevisionMaximum); return validAppsCtr == FileApps.Count; } /// /// Checks if the core version is in range of required minimum to maximum version. /// The package file has to be read in advance as this contains the min/max. /// /// string containing the core version /// true if version is in range /// /// - Initial. /// /// /// - Removed "Cordonel " string check. /// public Boolean CheckCoreRevision(String coreVersionString) { if (null == CoreRevisionMaximum || null == CoreRevisionMinimum || null == coreVersionString || null == _packageDescriptionFile) { return false; } var meterCoreRevision = ExtractVersionFromString(coreVersionString); return meterCoreRevision >= CoreRevisionMinimum && meterCoreRevision <= CoreRevisionMaximum; } /// /// Extract the version in form "1.23" from a string and returns it /// as Int32 value 123 with major * 100 + minor /// /// string containing version /// version as double /// /// - Initial. /// /// /// - Changed to Int32 with major * 1000 + minor. /// /// /// - Changed to Int32 with major * 100 + minor. /// public static Int32 ExtractVersionFromString(String versionString) { var majorVersion = 0; var minorVersion = 0; var strResults = Regex.Split(versionString, @"\D"); //the first number is the major version, the second the minor var idx = 0; for (; idx < strResults.Length - 1; idx++) { if (string.IsNullOrEmpty(strResults[idx])) { continue; } majorVersion = int.Parse(strResults[idx]); break; } for (; idx < strResults.Length; idx++) { if (string.IsNullOrEmpty(strResults[idx])) { continue; } minorVersion = int.Parse(strResults[idx]); } var version = majorVersion * MajorMultiply + minorVersion; return version; } #endregion } }