using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Logic.ProductionToProductMapper.Files.Fw; using Newtonsoft.Json; using Xylem.Common.CommonCore.Configuration; 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.Logic.ProductionOrderCore.FW; using Xylem.Common.Logic.SoftwareAccessHelper; 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 /// /// Minimal files contained in a FW package: /// - System, /// - Configexchange, /// - Genesisflow, /// - Irda, /// - Flexnetversion, /// - rowproduct_xxx.txt /// public const Int32 MinAppsRequiredForOperation = 6; // process update feedback public event EventHandler OnProcessUpdate; // FLEXNETVERSION app id public const Byte FlexnetVersionAppId = 0x18; //disk and name of config file private const String StrConfigMeterFileName = "0\\config"; //name for upgrade control file public const String StrUpgradeCtrlFileName = "1\\upgrade"; //erase application private const String StrEraseAppCtrlFilePost = "*\r"; //upgrade application public 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 public const String StrFileAppStartId = "APP>"; //position of CRC (LSB first) in binary file public const Int32 FileAppCrcIndex = 0x04; //position of version in binary file public const Int32 FileAppVersionIndex = 0x06; //position of application ID in binary file public 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 StrPackageFileAppCrc16 = "CRC16"; private const String StrPackageFileAppCrc32 = "CRC32"; 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 int32? with major * 100 + minor for comparison /// public Int32? CoreRevisionMinimum { get; private set; } /// /// Core revision maximum as int32? with major * 100 + minor for comparison /// public Int32? 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 IGenesisMeter _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; private FwUpdateState _fwBackupUpdateState; // 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; // manual stop of update process private Boolean _stopUpdateProcess; private Int32 _actualUpdateFileCtr; /// /// Enable partial file writes of single parts with gaps in between /// public Boolean SingleFilePartsRetryEnable = true; /// /// Store configuration in advance to the update /// public Boolean StoreConfigEnable = true; /// /// Allow update of genesis flow application /// public Boolean UpdateGenesisFlowEnable; /// /// 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 /// /// /// enable update of genesis flow app /// /// - Initial /// /// /// - Checked Genesis assignment before access. /// /// /// - Check installed application before require access to registers. /// public MeterFwUpdate(IGenesisMeter genesisMeter, Boolean updateGenesisFlowEnable = false) { if (genesisMeter == null) { return; } UpdateGenesisFlowEnable = updateGenesisFlowEnable; 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 and force new update control file /// /// /// /// - Initial /// /// /// - Moved Genesis preparation to /// public void AssignGenesis(IGenesisMeter 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 and update display code if required. /// /// true updates the display code /// /// - Initial /// /// /// - Single application comparison exported /// /// /// - Parameter from call removed, genesis is assigned! /// /// /// - Removed genesis meter has to be logged in /// /// /// - Removed MeterAppState.Unknown. /// /// /// - Optional display code update. /// public void CompareAllMeterAndFileApps(Boolean updateDisplayCode = false) { if (_genesisMeter == null || FileApps == null) { return; } foreach (var meterApp in _genesisMeter.MeterAppListVersion) { CompareMeterAppWithFileApp(meterApp); } if (updateDisplayCode) { //remind the login status to logout if not logged in here var wasLoggedOn = _genesisMeter.IsLoggedOn; _genesisMeter.ReLogin(); var isUpToDate = _genesisMeter.MeterAppListVersion.All( meterApp => meterApp.Status == MeterAppState.MeterAppUpToDate || meterApp.Status == MeterAppState.MeterAppNotRequired); //set display code to up to date if not needed to be installed or up to date _genesisMeter.SetProcessState(isUpToDate ? DisplayCodes.FwUpToDate : DisplayCodes.FwUpdateFailed, false); if (!wasLoggedOn) _genesisMeter.Logout(); } } /// /// Check the meter app status and if this is not indicating the unknown status /// compare the meter app with the file app to check for required action. /// After comparison this marks the meter app status and /// the "Update" or "Erase" request. /// Preconditions: /// - FileApps have to be preset. /// /// single application for check /// /// - Initial /// /// /// - Status text moved to meter and file /// /// /// - Compare changed /// /// /// - Meter state unknown if file apps not loaded /// /// /// - Return if communication error without change of meter app status /// public void CompareMeterAppWithFileApp(MeterApplications meterApp) { meterApp.Update = false; meterApp.Erase = false; //CASE -3: file apps not loaded, no comparison possible, leave everything as is if (FileApps.Count == 0) { return; } //CASE -2: meter application could not be read due to communication error if (meterApp.Status == MeterAppState.Unknown) { //CASE -2.1 Installation is required but installed app is not detected due to communication error if (FileApps.Any(x => x.AppId == meterApp.AppId)) { return; } //CASE -2.2 Installation not required as app is NOT in the fileApp list meterApp.Status = MeterAppState.MeterAppNotRequired; return; } //compare file apps with meter apps and change meterApp.Status 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.MeterAppNotRequired; } /// /// 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() => _actualOperation; /// /// 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 Database access /// /// Search a specific FW version info and clustered fileId which is for production /// /// /// /// /// /// /// /// - Initial. /// /// /// - For special requirements the "not for production" version can be loaded. /// /// /// - All FW versions as stated in the fwVersion string will be accepted as valid as those are defined in the /// 'Standard-' or 'Special-Requirements'. /// public static Boolean SearchProdFwInDb(String fwVersion, out Int32? fwPackageFileId, out String errorMsg, Boolean isForProduction = true) { errorMsg = ""; fwPackageFileId = null; try { // Get the FW update files infos from DB which are for production var url = ServiceUrls.ListAllCordonelFwPackagesUrl(); var requestResponse = LocalWebRequest.GetRequest(url, 8000, out var httpStatus); if (httpStatus == HttpStatusCode.OK && !string.IsNullOrEmpty(requestResponse)) { var fwPackInfo = JsonConvert.DeserializeObject>(requestResponse); if (fwPackInfo != null) { foreach (var fw in fwPackInfo.Where(fw => //(fw.CurrentForProd || !isForProduction) && fw.Name.Contains(fwVersion))) { fwPackageFileId = fw.FileClusterStoreId; return true; } } } } catch (Exception e) { errorMsg = e.Message; } return false; } /// /// Download the FW files from database including all binaries of each app and the application description file /// /// /// /// /// /// /// - Initial. /// public static Boolean GetFwPackageFromDb(Int32 fwPackageFileId, List fwUpdatePackageFiles, out String errorMsg) { errorMsg = ""; if (fwPackageFileId <= 0 || fwUpdatePackageFiles == null) { return false; } try { // Get the files from DB containing the application binaries and the package description file var url = ServiceUrls.DownloadFileParts(); url += $"{fwPackageFileId}&readContent=true"; var requestResponse = LocalWebRequest.GetRequest(url, 8000, out var httpStatus); if (httpStatus == HttpStatusCode.OK && !string.IsNullOrEmpty(requestResponse)) { var files = JsonConvert.DeserializeObject>(requestResponse); fwUpdatePackageFiles.AddRange(files); return true; } } catch (Exception e) { errorMsg = e.Message; } return false; } #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; } _remainingFileAppsParts.Clear(); if (_stopUpdateProcess) _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 /// /// /// - Introduced lock for repeated execution of state machine /// public Boolean UpdateMeterFw() { if (!UpdatePreparation()) { return false; } _fwUpdateState = FwUpdateState.StartInitial; _fwBackupUpdateState = FwUpdateState.Idle; 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; _fwBackupUpdateState = FwUpdateState.Idle; _actualOperation = ""; //update file information CompareAllMeterAndFileApps(true); //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. /// /// /// - Store all configurations before the update. /// /// /// - Message for actual operation, /// - PrepareUpdate state introduced. /// /// /// - Store all configurations BEFORE setting display to avoid "Idle" "8888" being displayed after reboot. /// /// /// - Try catch block to avoid e.g. METROLOGYASST_PulseMode. /// /// /// - Set upgrade permission for metrology to 0xFF if required (will be done by GTB). /// /// /// - Reset retry counter. /// /// /// - Moved StopUpdateProcess on top. /// public Boolean UpdatePreparation() { // remove the update stop action StopUpdateProcess = false; _actualOperation = Resources.StrFwUpdateStatePrepareUpdate; _fwUpdateState = FwUpdateState.PrepareUpdate; _processedBytesCtr = 0; _overallBytesCtr = 6; _updateProcedureRetryCtr = 0; _meterFile = new MeterFile(_genesisMeter); if (_meterFile != null) { _meterFile.OnProcessUpdate += ProcessUpdate_Event; } _genesisMeter?.ReLogin(); if (StoreConfigEnable) { //store all configuration parameters _actualOperation = Resources.StrStoreConfigurations; if (_genesisMeter == null || !_genesisMeter.StoreAllConfigurations()) return false; } _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(); try { if (UpdateGenesisFlowEnable) { _genesisMeter.WriteRegister(Register.System.MetrologyUpgradePermission, new Byte[] { 0xFF }); } foreach (var item in _registersBeforeUpdate) { var writeBackValue = _genesisMeter.ReadRegister(item.Key); _registersAfterUpdate.Add(item.Key, writeBackValue); _genesisMeter.WriteRegister(item.Key, item.Value); _processedBytesCtr++; } } catch (Exception) { //nothing to handle } _genesisMeter.MetrologyUpgradePermission = RegisterConverter.ByteArrayToValue( _genesisMeter.ReadRegister(Register.System.MetrologyUpgradePermission)); 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 for 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. /// /// /// - Introduced lock for repeated execution of state machine /// /// /// - Prepare update added, /// - Upload config file added. /// /// /// - Upload config file removed. /// /// /// - Erase test file introduced. /// private void FwUpdateStateMachine() { if (_fwBackupUpdateState == _fwUpdateState) { Thread.Sleep(1); } else { // remind backup state to avoid repeated execution and side effects _fwBackupUpdateState = _fwUpdateState; switch (_fwUpdateState) { case FwUpdateState.Idle: break; case FwUpdateState.StartInitial: _updateProcedureRetryCtr = 0; _fwUpdateState = FwUpdateState.EraseTstFile; break; case FwUpdateState.EraseTstFile: EraseTstFile(); _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.PrepareUpdate: break; case FwUpdateState.CompareFileApps: break; case FwUpdateState.EraseUpdateFile: break; default: _fwUpdateState = FwUpdateState.Idle; break; } }// lock repeated execution of identical state } #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")) { var fileApplication = new FileApplications(fileName) { BinData = new List(File.ReadAllBytes(fileName)) }; //add always to package files for later analysis FileApps.Add(fileApplication); } } return ValidateLoadedFiles(); } /// /// Load a FW update package containing the binaries and the package description file. /// /// The FW root path is the path where the FW update packages are stored. This simplifies the load of a FW /// completely stored to a subdirectory of this root path: /// /// EXAMPLE: /// [Root path]\EMEA_R130B\rowproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0537_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0070_xyz.bin /// \... /// \binfile18_130B_xyz.bin /// [Root path]\NA_R2006 \naproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0533_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0068_xyz.bin /// \... /// \binfile18_2006_xyz.bin /// /// /// /// search string to specify the release package like "R1.3.0B", will search for a /// subdirectory containing "R130B" in path name /// output of all files /// true if package file exists and could be parsed /// /// - Initial. /// /// /// - Returns bool. /// public static Boolean LoadFwUpdatePackage(String fwRootPath, String releaseName, List fwUpdatePackage) { if (fwUpdatePackage == null || string.IsNullOrEmpty(fwRootPath)) return false; try { if (!ValidateFwSubDirectory(fwRootPath, releaseName, out var fileNames)) return false; foreach (var fileName in fileNames.Where(f => !f.Contains(".json"))) { var file = new FilePart { FileName = Path.GetFileName(fileName) }; var rawFileContent = new List(File.ReadAllBytes(fileName)); file.FileContent = new Byte[rawFileContent.Count]; file.FileContent = rawFileContent.ToArray(); fwUpdatePackage.Add(file); } return true; } catch (Exception e) { Console.WriteLine(e.Message); return false; } } /// /// Common routine to get the filenames of the files contained in a root directory subdirectory. /// /// The FW root path is the path where the FW update packages are stored. This simplifies the load of a FW /// completely stored to a subdirectory of this root path: /// /// EXAMPLE: /// [Root path]\EMEA_R130B\rowproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0537_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0070_xyz.bin /// \... /// \binfile18_130B_xyz.bin /// [Root path]\NA_R2006 \naproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0533_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0068_xyz.bin /// \... /// \binfile18_2006_xyz.bin /// /// /// /// search string to specify the release package like "R1.3.0B", will search for a /// subdirectory containing "R130B" in path name /// output of all file names found in the subdirectory with absolute path /// false if path not found /// /// - Initial. /// private static Boolean ValidateFwSubDirectory(String fwRootPath, String releaseName, out String[] filePathName) { filePathName = null; if (string.IsNullOrEmpty(fwRootPath)) return false; try { // Get list of directories to select the correct based on the release name var fwReleaseDirectories = Directory.GetDirectories(fwRootPath); // Clean FW release name from e.g. R1.3.0B to R130B as this is part of the folder var releaseFolder = releaseName.Replace(".", ""); var fwReleaseDirectory = ""; foreach (var directory in fwReleaseDirectories.Where(d => d.Contains(releaseFolder))) { fwReleaseDirectory = directory; } if (string.IsNullOrEmpty(fwReleaseDirectory)) return false; filePathName = Directory.GetFiles(fwReleaseDirectory); if (filePathName.Length == 0) return false; return true; } catch (Exception e) { Console.WriteLine(e.Message); return false; } } /// /// Load all binary files 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 ValidateLoadedFiles(); } /// /// 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 ValidateLoadedFiles(); } /// /// Common validation of loaded file apps. /// /// true if all applications are valid /// /// - Initial /// /// /// - Corrected return value (was constantly true). /// private Boolean ValidateLoadedFiles() { var returnValue = true; foreach (var fileApplication in FileApps) { fileApplication.IsValid = CheckFileApp(fileApplication); if (!fileApplication.IsValid) { returnValue = false; } } _remainingFileAppsParts.Clear(); _succeededFileAppsParts.Clear(); _failedFileAppsParts.Clear(); return returnValue; } /// /// Extracts AppId from binary application file. /// /// /// /// - Initial extracted from /// public static Byte GetFileAppAppId(Byte[] binData) { return binData[FileAppAppIdIndex]; } /// /// Extracts CRC from binary application file. /// /// /// /// - Initial extracted from /// public static UInt16 GetFileAppCrc(Byte[] binData) { return (UInt16)(binData[FileAppCrcIndex] + (binData[FileAppCrcIndex + 1] << 8)); } /// /// Extracts version from binary application file. /// /// /// /// - Initial extracted from /// public static UInt32 GetFileAppVersion(Byte[] binData, out String strVersion) { var appId = binData[FileAppAppIdIndex]; strVersion = "0.00"; UInt32 version; if (appId == FlexnetVersionAppId) //FLEXNETVERSION { strVersion = GenesisMeter.BuildFlexnetFwVersion(out version, null, binData[FileAppVersionIndex + 1], binData[FileAppVersionIndex]); } else { strVersion = GenesisMeter.BuildFwVersion(out version, binData[FileAppVersionIndex + 1], binData[FileAppVersionIndex]); } return version; } /// /// 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. /// /// /// - File version as hex number to handle . /// /// /// - Uses . /// private static Boolean CheckFileApp(FileApplications fileApp) { var binData = fileApp.BinData.ToArray(); fileApp.AppId = binData[FileAppAppIdIndex]; fileApp.Version = GetFileAppVersion(binData, out fileApp.StrVersion); fileApp.Crc = (UInt16)(binData[FileAppCrcIndex] + (binData[FileAppCrcIndex + 1] << 8)); var fileAppStartId = Encoding.UTF8.GetString(binData, 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); } } /// /// Erase the test file which is an EMEA placeholder for update over the air. /// This has a very large size to keep the space reserved for all applications /// needed to be downloaded. This file can be erased as it will be automatically /// regenerated after reboot by the SENSUSRADIO app. /// /// /// /// - Initial /// private void EraseTstFile() { if (_meterFile == null || _genesisMeter == null) return; // erase always, because it may be left as artefact by changing from NA to EMEA or vice versa! //if (_genesisMeter.Region.Contains("EMEA")) { _meterFile.UnlockEraseWriteMeterFile(MeterFile.StrMeterTstFile); _meterFile.EraseMeterFile(MeterFile.StrMeterTstFile); } } /// /// 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(true); 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 == null || !_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. /// /// /// - Set retry counter to 0, /// - reduce timeout from 5000 to 2000 ms. /// /// /// - Avoid false return on write TriggerUpgrade failure. /// /// /// - Avoid TriggerUpgrade retry as this makes no sense without writing UpgradeCtrlFile /// in advance. /// private Boolean TriggerUpgrade() { Boolean returnValue; //trigger upgrade _actualOperation = Resources.StrFwUpdateStateTriggerUpgrade; //_updateProcedureRetryCtr = 0; //var triggerCtr = 0; //set extended timeout for trigger upgrade response delay _genesisMeter.TransmitProtocol.SetResponseTimeout(2000); //do //{ returnValue = _genesisMeter.WriteRegister(Register.System.TriggerFwUpgrade, true); // 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(true); return true; } #endregion #region Conversion /// /// Build application id string /// /// /// public static String ConvertAppIdToString(UInt32 appId) { return $"0x{appId:X2}"; } /// /// Build the hexadecimal CRC string /// /// /// public static String ConvertCrcToString(UInt32 crc) { return $"0x{crc:X4}"; } #endregion #region PackageFile /// /// Load a FW update package description file. /// /// The filters to find the path in the root path is the region and FW release version /// "EMEA" or "NA" /// "R130B" or "R1.3.0B" /// additional naming is allowed (e.g. "EMEA_R13F3_replaces_R1107_R1108") /// /// The filters to accept the file is: /// "product" and /// ".txt" /// additional naming is allowed (e.g. "rowproduct_R130B.txt") /// /// The FW root path is the path where the FW update packages are stored. /// All files needed to be stored directly in the main folder of the FW version: /// /// EXAMPLE: /// [Root path]\EMEA_R130B\rowproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0537_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0070_xyz.bin /// \... /// \binfile18_130B_xyz.bin /// [Root path]\NA_R2006 \naproduct_xyz.txt (package description file, mandatory is "product" and ".txt") /// \configuration.json /// \binfile00_0533_xyz.bin (package description file, mandatory is "binfile" and ".bin") /// \binfile01_0068_xyz.bin /// \... /// \binfile18_2006_xyz.bin /// /// /// /// search string to specify the release package like "R1.3.0B", will search for a /// subdirectory containing "R130B" in path name /// /// true if package file exists and could be parsed /// /// - Initial. /// /// /// - Return changed to bool. /// public static Boolean LoadPackageFile(String fwRootPath, String releaseName, out String packageFile) { packageFile = ""; if (string.IsNullOrEmpty(fwRootPath) || string.IsNullOrEmpty(releaseName)) return false; try { if (!ValidateFwSubDirectory(fwRootPath, releaseName, out var fileNames)) return false; foreach (var fileName in fileNames.Where(f => f.Contains("product") && f.Contains(".txt"))) { packageFile = File.ReadAllText(fileName); } return true; } catch (Exception e) { Console.WriteLine(e.Message); return false; } } /// /// Load package control file - ADF (application description 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 - ADF (application description file) /// /// control file content /// /// /// - Initial /// public Boolean LoadPackageFileFromText(String text) { _packageDescriptionFile = text; return _packageDescriptionFile.Length != 0; } /// /// Validate package file (ADF - application description file) /// with update files (ABC - application binary container) /// /// /// /// - 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. /// /// /// - CRC16 added: /// old versions will contain CRC without a number, /// new versions starting with 2022-11-04_NA_B122B contain a CRC16 and CRC32 identifier. /// - File line check modified to be immune against multiple blanks behind the field identifier. /// /// /// - Handle FLEXNETVERSION correctly. /// 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) { // convert version from 0x1107 to "11.07" var strVersion = fileApp.AppId == FlexnetVersionAppId ? $"{(fileApp.Version & 0xFF00) >> 8:X2}.{fileApp.Version & 0x00FF:X2}" : fileApp.StrVersion; // find a line which contains all required information validAppsCtr += packageFileLines.Count(line => line.Contains($"{StrPackageFileAppId}") && line.Contains($"{ConvertAppIdToString(fileApp.AppId)}") && line.Contains($"{StrPackageFileAppCrc}") && line.Contains($"{ConvertCrcToString(fileApp.Crc)}") && line.Contains($"{StrPackageFileAppVersion}") && line.Contains($"{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; } /// /// Convert a hexadecimal string to an integer number /// /// /// /// true if successfully converted /// /// - Initial. /// public static Boolean HexStringToInteger(String hexString, ref T intNumber) { var type = typeof(T); // Clean string to allow explicit hex signs as ASCII var strCleanedToHexSigns = Regex.Replace(hexString, "[^0-9,A-F,a-f]", ""); if (type == typeof(UInt64)) { if (ulong.TryParse(strCleanedToHexSigns, NumberStyles.HexNumber, new CultureInfo("en"), out var uint64Number)) { intNumber = (T)Convert.ChangeType(uint64Number, type); } else { return false; } } // Convert for all numbers from Byte to Int64 if (!long.TryParse(strCleanedToHexSigns, NumberStyles.HexNumber, new CultureInfo("en"), out var int64Number)) return false; intNumber = (T)Convert.ChangeType(int64Number, type); return true; } /// /// Extract tall applications found in the package description file. /// /// /// /// /// - Initial. /// public static void GetPackageFileApplications(String packageDescriptionFile, List apps) { try { var packageFileLines = packageDescriptionFile.Split('\n'); foreach (var line in packageFileLines) { if (!line.Contains(StrPackageFileAppId) || !line.Contains(StrPackageFileAppCrc) || !line.Contains(StrPackageFileAppVersion)) continue; var appIsValid = true; var app = new FileApplications("package description file content - unnamed app"); // Example line: "Id 0x0F, CRC16 0xA3A6, size 25164 bytes, version 3.02\r\n" var lineFields = line.Split(','); foreach (var lineField in lineFields) { var fieldsWithNullFields = lineField.Split(' '); var fields = new List(); fields.AddRange(fieldsWithNullFields.Where(str => !string.IsNullOrEmpty(str))); if (fields[0].Equals(StrPackageFileAppId)) { appIsValid = HexStringToInteger(fields[1], ref app.AppId); } // Avoid assignment of new CRC32 (UInt32) as the app.crc variable is too small to hold it (UInt16) if (fields[0].Contains(StrPackageFileAppCrc) && !fields[0].Contains(StrPackageFileAppCrc32)) { appIsValid &= HexStringToInteger(fields[1], ref app.Crc); } // Convert all versions except the FLEXNETVERSION which will keep its hex outline if (fields[0].Equals(StrPackageFileAppVersion) && app.AppId != FlexnetVersionAppId ) { Byte msb = 0; Byte lsb = 0; var subFieldsMsbLsb = fields[1].Split('.'); appIsValid &= HexStringToInteger(subFieldsMsbLsb[0], ref msb); appIsValid &= HexStringToInteger(subFieldsMsbLsb[1], ref lsb); app.StrVersion = GenesisMeter.BuildFwVersion(out app.Version, msb, lsb); appIsValid &= !string.IsNullOrEmpty(app.StrVersion); } // Convert the FLEXNETVERSION which will keep its hex outline if (fields[0].Equals(StrPackageFileAppVersion) && app.AppId == FlexnetVersionAppId ) { app.StrVersion = GenesisMeter.BuildFlexnetFwVersion(out app.Version, fields[1]); appIsValid &= !string.IsNullOrEmpty(app.StrVersion); } } //app.AppId; if (appIsValid) apps.Add(app); } } catch (Exception) { //ignore } } /// /// Extract the maximum core revision from the package description file. /// /// /// nullable core revision as Int32? /// /// - Initial. /// public static Int32? GetCoreRevisionMax(String packageDescriptionFile) { try { var packageFileLines = packageDescriptionFile.Split('\n'); var strCoreMax = packageFileLines.First(line => line.Contains(StrCoreRevisionMaxId) && line.Contains(StrCoreRevisionCordonelId)); if (!string.IsNullOrEmpty(strCoreMax)) { return ExtractVersionFromString(strCoreMax); } } catch (Exception) { // ignored } return null; } /// /// 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. /// /// containing the core version /// true if version is in range /// /// - Initial. /// /// /// - Removed "Cordonel " string check. /// /// /// - Core revision data type changed from string ti int32?. /// public Boolean CheckCoreRevision(Int32? coreRevision) { if (null == CoreRevisionMaximum || null == CoreRevisionMinimum || null == _packageDescriptionFile || coreRevision == null) { return false; } return coreRevision >= CoreRevisionMinimum && coreRevision <= 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. /// private 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 } }