laatzen/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterFwUpdate.cs
2026-02-24 11:50:00 +01:00

2649 lines
103 KiB
C#

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
{
/// <summary>
/// Meter FW update procedure
/// </summary>
public class MeterFwUpdate : IProcessState
{
#region Variables
/// <summary>
/// Minimal files contained in a FW package:
/// - System,
/// - Configexchange,
/// - Genesisflow,
/// - Irda,
/// - Flexnetversion,
/// - rowproduct_xxx.txt
/// </summary>
public const Int32 MinAppsRequiredForOperation = 6;
// process update feedback
public event EventHandler<ProcessExecEventArgs> 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;
/// <summary>
/// Size of parts for file splitting
/// </summary>
public Int32 MaxPartialFileDataSize = 2 * 1024;
/// <summary>
/// Core revision minimum as int32? with major * 100 + minor for comparison
/// </summary>
public Int32? CoreRevisionMinimum
{
get; private set;
}
/// <summary>
/// Core revision maximum as int32? with major * 100 + minor for comparison
/// </summary>
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"
/// <summary>
/// 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.
/// </summary>
public readonly List<FileApplications> FileApps = new List<FileApplications>();
//update file(parts) list remaining
private readonly List<FileApplications> _remainingFileAppsParts = new List<FileApplications>();
//update file (parts) list which succeeded
private readonly List<FileApplications> _succeededFileAppsParts = new List<FileApplications>();
//update file(parts) list which failed
private readonly List<FileApplications> _failedFileAppsParts = new List<FileApplications>();
//update meter application list which failed
private readonly List<MeterApplications> _updatedMeterApps = new List<MeterApplications>();
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;
/// <summary>
/// Enable partial file writes of single parts with gaps in between
/// </summary>
public Boolean SingleFilePartsRetryEnable = true;
/// <summary>
/// Store configuration in advance to the update
/// </summary>
public Boolean StoreConfigEnable = true;
/// <summary>
/// Allow update of genesis flow application
/// </summary>
public Boolean UpdateGenesisFlowEnable;
/// <summary>
/// Enable partial file writes from first failed part to the end
/// </summary>
public Boolean ConsecutiveFilePartsRetryEnable = false;
/// <summary>
/// Register subset needed to adjust for update performance content before update
/// </summary>
private readonly Dictionary<String, Byte[]> _registersBeforeUpdate = new Dictionary<String, Byte[]>();
/// <summary>
/// Register to restore after update
/// </summary>
private Dictionary<String, Byte[]> _registersAfterUpdate = new Dictionary<String, Byte[]>();
#endregion
#region Events
/// <summary>
/// Process update event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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
/// <summary>
/// Process counter in percent
/// </summary>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double OverallProcessCtrPercent
{
get
{
var processFileBytes = _meterFile?.ProcessedBytesCtr ?? 0;
return 100.0 * (processFileBytes + _processedBytesCtr) /
(_overallBytesCtr > 0 ? _overallBytesCtr : 1);
}
}
/// <summary>
/// Single file process counter in percent
/// </summary>
/// <remarks date="2019-Jun-24" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double SingleFileProcessCtrPercent => _meterFile?.ProcessCtrPercent ?? 0;
#endregion
#region CtorAssignment
/// <summary>
/// Ctor
/// </summary>
/// <param name="genesisMeter"></param>
/// <param name="updateGenesisFlowEnable">enable update of genesis flow app</param>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Oct-28" author="Thomas Wiedebusch">
/// - Checked Genesis assignment before access.
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Check installed application before require access to registers.
/// </remarks>
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 });
}
}
/// <summary>
/// Assign new genesis and force new update control file
/// </summary>
/// <param name="genesisMeter"></param>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Dec-02" author="Thomas Wiedebusch">
/// - Moved Genesis preparation to <see cref="UpdatePreparation"/>
/// </remarks>
public void AssignGenesis(IGenesisMeter genesisMeter)
{
_genesisMeter = genesisMeter;
_actualOperation = "";
_upgradeCtrlFile = "";
_fwUpdateState = FwUpdateState.Idle;
_pcbId = _genesisMeter?.PcbId;
_remainingFileAppsParts.Clear();
_succeededFileAppsParts.Clear();
_failedFileAppsParts.Clear();
}
/// <summary>
/// Dispose
/// </summary>
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
/// <summary>
/// Compare file and meter applications and update display code if required.
/// </summary>
/// <param name="updateDisplayCode">true updates the display code</param>
/// <remarks date="2019-Jun-25" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Single application comparison exported
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Parameter from call removed, genesis is assigned!
/// </remarks>
/// <remarks date="2019-Jul-02" author="Thomas Wiedebusch">
/// - Removed genesis meter has to be logged in
/// </remarks>
/// <remarks date="2024-Apr-23" author="Thomas Wiedebusch">
/// - Removed MeterAppState.Unknown.
/// </remarks>
/// <remarks date="2024-Apr-25" author="Thomas Wiedebusch">
/// - Optional display code update.
/// </remarks>
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();
}
}
/// <summary>
/// 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 <see cref="MeterAppState"/> and
/// the "Update" or "Erase" request.
/// Preconditions:
/// - FileApps have to be preset.
/// </summary>
/// <param name="meterApp">single application for check</param>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - Status text moved to meter and file
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Compare changed
/// </remarks>
/// <remarks date="2020-Dec-01" author="Thomas Wiedebusch">
/// - Meter state unknown if file apps not loaded
/// </remarks>
/// <remarks date="2024-Apr-23" author="Thomas Wiedebusch">
/// - Return if communication error without change of meter app status
/// </remarks>
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;
}
/// <summary>
/// Check last downloads
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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;
}
/// <summary>
/// Remind result of download for reload of files, new connect or compare
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jul-02" author="Thomas Wiedebusch">
/// - List removal of elements simplified
/// </remarks>
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
/// <summary>
/// Read the failed update files
/// </summary>
/// <remarks date="2019-Jul-04" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public List<FileApplications> GetFailedFileApps() => _failedFileAppsParts;
/// <summary>
/// Returns the actual file in process for update
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public String GetActualOperation() => _actualOperation;
/// <summary>
/// Returns the FW Update state
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Changed to localizable language
/// </remarks>
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
/// <summary>
/// Search a specific FW version info and clustered fileId which is for production
/// </summary>
/// <param name="fwVersion"></param>
/// <param name="fwPackageFileId"></param>
/// <param name="errorMsg"></param>
/// <param name="isForProduction"></param>
/// <returns></returns>
/// <remarks date="2024-Nov-09" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2025-Feb-11" author="Thomas Wiedebusch">
/// - For special requirements the "not for production" version can be loaded.
/// </remarks>
/// <remarks date="2026-Feb-23" author="Thomas Wiedebusch">
/// - All FW versions as stated in the fwVersion string will be accepted as valid as those are defined in the
/// 'Standard-' or 'Special-Requirements'.
/// </remarks>
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<List<CordonelFwPackageInfo>>(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;
}
/// <summary>
/// Download the FW files from database including all binaries of each app and the application description file
/// </summary>
/// <param name="fwPackageFileId"></param>
/// <param name="fwUpdatePackageFiles"></param>
/// <param name="errorMsg"></param>
/// <returns></returns>
/// <remarks date="2024-11-09" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean GetFwPackageFromDb(Int32 fwPackageFileId, List<FilePart> 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<List<FilePart>>(requestResponse);
fwUpdatePackageFiles.AddRange(files);
return true;
}
}
catch (Exception e)
{
errorMsg = e.Message;
}
return false;
}
#endregion
#region Actions
/// <summary>
/// Try to re-establish the file system, therefor logout to close all open files
/// and login again.
/// </summary>
/// <remarks date="2019-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Enable auto-logon added
/// </remarks>
/// <remarks date="2019-Jul-03" author="Thomas Wiedebusch">
/// - Removed auto-logon
/// </remarks>
public void ReestablishMeterFileSystem()
{
_genesisMeter?.Logout();
_genesisMeter?.ReLogin();
}
/// <summary>
/// Stop the update process
/// </summary>
public Boolean StopUpdateProcess
{
get => _stopUpdateProcess;
set
{
_stopUpdateProcess = value;
if (_meterFile != null)
{
_meterFile.StopProcess = value;
}
_remainingFileAppsParts.Clear();
if (_stopUpdateProcess)
_fwUpdateState = FwUpdateState.UpdateFailed;
}
}
/// <summary>
/// Update meter firmware
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-22" author="Thomas Wiedebusch">
/// - Build and send of upgrade control file split,
/// - OverallByteCounter re-calculated on remaining update file sizes.
/// </remarks>
/// <remarks date="2019-Jun-23" author="Thomas Wiedebusch">
/// - Repeated trigger update if this failed.
/// </remarks>
/// <remarks date="2019-Jun-25" author="Thomas Wiedebusch">
/// - Status.
/// </remarks>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - State machine implemented
/// </remarks>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Update preparation exported
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Exit preparation added
/// </remarks>
/// <remarks date="2022-Mar-04" author="Thomas Wiedebusch">
/// - Introduced lock for repeated execution of state machine
/// </remarks>
public Boolean UpdateMeterFw()
{
if (!UpdatePreparation())
{
return false;
}
_fwUpdateState = FwUpdateState.StartInitial;
_fwBackupUpdateState = FwUpdateState.Idle;
while (_fwUpdateState != FwUpdateState.Idle && !StopUpdateProcess)
{
FwUpdateStateMachine();
}
return ExitPreparation(true);
}
/// <summary>
/// Upload selected update files and compare them with update files
/// </summary>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean ManualVerifyFiles()
{
if (!UpdatePreparation())
{
return false;
}
return ExitPreparation(true);
}
/// <summary>
/// Preselects the downloaded files for trigger upgrade
/// </summary>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - Compare changed
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Prepare trigger changed to fulfill automatic FW update
/// </remarks>
public Boolean PrepareTrigger()
{
//mark download
foreach (var meterApp in _genesisMeter.MeterAppListVersion)
{
meterApp.Update = meterApp.Status == MeterAppState.MeterAppDownloadSucceeded;
}
return true;
}
/// <summary>
/// Preselects the downloaded files for trigger upgrade
/// </summary>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean ManualDownloadRemainingFileAppsParts()
{
if (!UpdatePreparation())
{
return false;
}
_fwUpdateState = FwUpdateState.DownloadFileApps;
var returnValue = DownloadFileApps();
_fwUpdateState = FwUpdateState.Idle;
return ExitPreparation(returnValue);
}
/// <summary>
/// Download selected update files from beginning
/// </summary>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Exit preparation added
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - Initial check changed
/// </remarks>
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);
}
/// <summary>
/// Build update control file and trigger update of selected files
/// </summary>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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);
}
/// <summary>
/// Reset FW update process and all lists and counters
/// </summary>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - New items added for clearance.
/// </remarks>
/// <remarks date="2019-Jul-02" author="Thomas Wiedebusch">
/// - Reestablish file system (logout/login/auto-login).
/// </remarks>
/// <remarks date="2019-Jul-10" author="Thomas Wiedebusch">
/// - Logout added.
/// </remarks>
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();
}
/// <summary>
/// Common update preparation and check for manual updates
/// </summary>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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;
}
/// <summary>
/// Common update preparation and check
/// </summary>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jul-10" author="Thomas Wiedebusch">
/// - ReLogin added.
/// </remarks>
/// <remarks date="2019-Aug-28" author="Thomas Wiedebusch">
/// - Display FW update codes in meter display added.
/// </remarks>
/// <remarks date="2019-Dec-03" author="Thomas Wiedebusch">
/// - Genesis preparation to keep Cordonel-CPU load low.
/// </remarks>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Installed ProcessUpdate_Event.
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Set file write timeout to default.
/// </remarks>
/// <remarks date="2022-Apr-21" author="Thomas Wiedebusch">
/// - Store all configurations before the update.
/// </remarks>
/// <remarks date="2022-Apr-26" author="Thomas Wiedebusch">
/// - Message for actual operation,
/// - PrepareUpdate state introduced.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Store all configurations BEFORE setting display to avoid "Idle" "8888" being displayed after reboot.
/// </remarks>
/// <remarks date="2022-Jun-23" author="Thomas Wiedebusch">
/// - Try catch block to avoid e.g. METROLOGYASST_PulseMode.
/// </remarks>
/// <remarks date="2023-Jan-20" author="Thomas Wiedebusch">
/// - Set upgrade permission for metrology to 0xFF if required (will be done by GTB).
/// </remarks>
/// <remarks date="2024-Apr-25" author="Thomas Wiedebusch">
/// - Reset retry counter.
/// </remarks>
/// <remarks date="2025-Jun-06" author="Thomas Wiedebusch">
/// - Moved StopUpdateProcess on top.
/// </remarks>
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<String, Byte[]>();
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<Byte>(
_genesisMeter.ReadRegister(Register.System.MetrologyUpgradePermission));
return true;
}
/// <summary>
/// Common exit routine
/// </summary>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - keep file information
/// </remarks>
/// <remarks date="2019-Jul-02" author="Thomas Wiedebusch">
/// - Reestablish file system (logout/login/auto-login).
/// </remarks>
/// <remarks date="2019-Jul-10" author="Thomas Wiedebusch">
/// - Logout added.
/// </remarks>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Uninstalled ProcessUpdate_Event.
/// </remarks>
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
/// <summary>
/// State machine for FW update process
/// </summary>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - Modified
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Prepare trigger added to select all successfully downloaded applications
/// even if only a selection of application has been downloaded in this run.
/// </remarks>
/// <remarks date="2019-Jul-03" author="Thomas Wiedebusch">
/// - Removed remaining files if partial write denied,
/// - Sequences changed to build update control file after prepare trigger.
/// </remarks>
/// <remarks date="2019-Nov-30" author="Thomas Wiedebusch">
/// - Register Retry setup removed here.
/// </remarks>
/// <remarks date="2020-Jan-22" author="Thomas Wiedebusch">
/// - 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 <see cref="RetryEntireFileAfterFailedPartialRetries"/>.
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Increased timeout for file write access after a procedure retry threshold
/// <see cref="IncreaseTimeoutUpdateProcedureRetries"/>. 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.
/// </remarks>
/// <remarks date="2022-Mar-04" author="Thomas Wiedebusch">
/// - Introduced lock for repeated execution of state machine
/// </remarks>
/// <remarks date="2022-Apr-26" author="Thomas Wiedebusch">
/// - Prepare update added,
/// - Upload config file added.
/// </remarks>
/// <remarks date="2023-Jan-02" author="Thomas Wiedebusch">
/// - Upload config file removed.
/// </remarks>
/// <remarks date="2023-Jan-13" author="Thomas Wiedebusch">
/// - Erase test file introduced.
/// </remarks>
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
/// <summary>
/// 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
/// </summary>
/// <param name="path">to binary files</param>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Clear file lists on reload.
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Return false if one package file is invalid.
/// </remarks>
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<Byte>(File.ReadAllBytes(fileName))
};
//add always to package files for later analysis
FileApps.Add(fileApplication);
}
}
return ValidateLoadedFiles();
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="fwRootPath"></param>
/// <param name="releaseName">search string to specify the release package like "R1.3.0B", will search for a
/// subdirectory containing "R130B" in path name</param>
/// <param name="fwUpdatePackage">output of all files</param>
/// <returns>true if package file exists and could be parsed</returns>
/// <remarks date="2024-Dec-04" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-18" author="Thomas Wiedebusch">
/// - Returns bool.
/// </remarks>
public static Boolean LoadFwUpdatePackage(String fwRootPath, String releaseName, List<FilePart> 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<Byte>(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;
}
}
/// <summary>
/// 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
///
/// </summary>
/// <param name="fwRootPath"></param>
/// <param name="releaseName">search string to specify the release package like "R1.3.0B", will search for a
/// subdirectory containing "R130B" in path name</param>
/// <param name="filePathName">output of all file names found in the subdirectory with absolute path</param>
/// <returns>false if path not found</returns>
/// <remarks date="2024-Dec-05" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
}
/// <summary>
/// Load all binary files from obj,
/// this has to be a tested combination of concatenated applications
/// </summary>
/// <param name="sourceFileApps">to binary files</param>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Clear file lists on reload.
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Return false if one package file is invalid.
/// </remarks>
public Boolean LoadFileApps(List<FileApplications> sourceFileApps)
{
FileApps.Clear();
FileApps.AddRange(sourceFileApps);
return ValidateLoadedFiles();
}
/// <summary>
/// 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
/// </summary>
/// <param name="apps"> file names with binary content </param>
/// <returns></returns>
/// <remarks date="2020-05-28" author="R:D">
/// - Initial
/// </remarks>
public Boolean LoadFileApps(Dictionary<String, List<Byte>> 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();
}
/// <summary>
/// Common validation of loaded file apps.
/// </summary>
/// <returns>true if all applications are valid</returns>
/// <remarks date="2022-Apr-07" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2022-Apr-22" author="Thomas Wiedebusch">
/// - Corrected return value (was constantly true).
/// </remarks>
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;
}
/// <summary>
/// Extracts AppId from binary application file.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Apr-04" author="Thomas Wiedebusch">
/// - Initial extracted from <see cref="CheckFileApp"/>
/// </remarks>
public static Byte GetFileAppAppId(Byte[] binData)
{
return binData[FileAppAppIdIndex];
}
/// <summary>
/// Extracts CRC from binary application file.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-Apr-04" author="Thomas Wiedebusch">
/// - Initial extracted from <see cref="CheckFileApp"/>
/// </remarks>
public static UInt16 GetFileAppCrc(Byte[] binData)
{
return (UInt16)(binData[FileAppCrcIndex] + (binData[FileAppCrcIndex + 1] << 8));
}
/// <summary>
/// Extracts version from binary application file.
/// </summary>
/// <returns></returns>
/// <remarks date="2024-May-07" author="Thomas Wiedebusch">
/// - Initial extracted from <see cref="CheckFileApp"/>
/// </remarks>
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;
}
/// <summary>
/// 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
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-26" author="Thomas Wiedebusch">
/// - New file version string builder.
/// </remarks>
/// <remarks date="2024-May-06" author="Thomas Wiedebusch">
/// - File version as hex number to handle .
/// </remarks>
/// <remarks date="2024-May-07" author="Thomas Wiedebusch">
/// - Uses <see cref="GetFileAppVersion"/>.
/// </remarks>
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;
}
/// <summary>
/// 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!
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// - Compare changed
/// </remarks>
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);
}
}
}
}
/// <summary>
/// Build update files, large files will be split to several small pieces
/// handled as an individual update file
/// </summary>
/// <param name="fileApp"></param>
/// <remarks date="2019-Jun-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-26" author="Thomas Wiedebusch">
/// - Version string added
/// </remarks>
private void PartitionFileApp(FileApplications fileApp)
{
//split file to portions
var dataByteList = new List<Byte>();
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<Byte>();
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jan-13" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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);
}
}
/// <summary>
/// Download and result handling for update files
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-26" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// -Reestablish meter file system on failed file download,
/// information extended and automatism added.
/// </remarks>
/// <remarks date="2019-Jun-30" author="Thomas Wiedebusch">
/// -Compare changed.
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// -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!
/// </remarks>
/// <remarks date="2019-Jul-04" author="Thomas Wiedebusch">
/// - Partial file write single parts selectable!
/// - Partial file write after first error selectable!
/// </remarks>
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;
}
/// <summary>
/// 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).
/// </summary>
/// <param name="fileAppPart"></param>
/// <returns></returns>
/// <remarks date="2019-Jun-20" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-21" author="Thomas Wiedebusch">
/// - Partitioning of files exported
/// </remarks>
/// <remarks date="2019-Jun-22" author="Thomas Wiedebusch">
/// - Re-establish file system added
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Return value retry counter for 0 retries corrected
/// </remarks>
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
/// <summary>
/// Build the upgrade control file.
/// The update control file generation will be skipped if the update control file
/// is set!
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
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 != "";
}
/// <summary>
/// Send the upgrade control file to the meter
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-22" author="Thomas Wiedebusch">
/// - Exported writing of upgrade control file
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Restore performance registers before trigger upgrade.
/// </remarks>
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
/// <summary>
/// Send the trigger update
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-24" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-28" author="Thomas Wiedebusch">
/// - If trigger upgrade succeeded mark all actually selected package files
/// as succeeded and remove update or erase marker
/// </remarks>
/// <remarks date="2019-Jul-01" author="Thomas Wiedebusch">
/// - Delete downloaded (updated) meter application files, they are validated on
/// accepted trigger.
/// </remarks>
/// <remarks date="2019-Dec-03/05" author="Thomas Wiedebusch">
/// - Extended timeout.
/// </remarks>
/// <remarks date="2023-Jan-18" author="Thomas Wiedebusch">
/// - Set retry counter to 0,
/// - reduce timeout from 5000 to 2000 ms.
/// </remarks>
/// <remarks date="2025-Nov-14" author="Roland Drabesch">
/// - Avoid false return on write TriggerUpgrade failure.
/// </remarks>
/// <remarks date="2025-Nov-17" author="Roland Drabesch">
/// - Avoid TriggerUpgrade retry as this makes no sense without writing UpgradeCtrlFile
/// in advance.
/// </remarks>
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
/// <summary>
/// Build application id string
/// </summary>
/// <param name="appId"></param>
/// <returns></returns>
public static String ConvertAppIdToString(UInt32 appId)
{
return $"0x{appId:X2}";
}
/// <summary>
/// Build the hexadecimal CRC string
/// </summary>
/// <param name="crc"></param>
/// <returns></returns>
public static String ConvertCrcToString(UInt32 crc)
{
return $"0x{crc:X4}";
}
#endregion
#region PackageFile
/// <summary>
/// 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
///
/// </summary>
/// <param name="fwRootPath"></param>
/// <param name="releaseName">search string to specify the release package like "R1.3.0B", will search for a
/// subdirectory containing "R130B" in path name</param>
/// <param name="packageFile"></param>
/// <returns>true if package file exists and could be parsed</returns>
/// <remarks date="2024-Dec-04" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Dec-18" author="Thomas Wiedebusch">
/// - Return changed to bool.
/// </remarks>
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;
}
}
/// <summary>
/// Load package control file - ADF (application description file)
/// </summary>
/// <param name="filePathName">to binary files</param>
/// <returns></returns>
/// <remarks date="2019-Jun-24" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean LoadPackageFile(String filePathName)
{
if (!File.Exists(filePathName))
{
return false;
}
_packageDescriptionFile = File.ReadAllText(filePathName);
return _packageDescriptionFile.Length != 0;
}
/// <summary>
/// Load package control file - ADF (application description file)
/// </summary>
/// <param name="text">control file content</param>
/// <returns></returns>
/// <remarks date="2010-AUG-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Boolean LoadPackageFileFromText(String text)
{
_packageDescriptionFile = text;
return _packageDescriptionFile.Length != 0;
}
/// <summary>
/// Validate package file (ADF - application description file)
/// with update files (ABC - application binary container)
/// </summary>
/// <returns></returns>
/// <remarks date="2019-Jun-24" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Jun-26" author="Thomas Wiedebusch">
/// - Version string used instead of conversion from UInt32 to string
/// </remarks>
/// <remarks date="2019-Jun-29" author="Thomas Wiedebusch">
/// - Return false if one package file is checked being invalid.
/// </remarks>
/// <remarks date="2020-Dec-02" author="Thomas Wiedebusch">
/// - Extraction of valid core revisions min/max.
/// </remarks>
/// <remarks date="2022-Nov-07" author="Thomas Wiedebusch">
/// - 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.
/// </remarks>
/// <remarks date="2024-May-06" author="Thomas Wiedebusch">
/// - Handle FLEXNETVERSION correctly.
/// </remarks>
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;
}
/// <summary>
/// Convert a hexadecimal string to an integer number
/// </summary>
/// <param name="hexString"></param>
/// <param name="intNumber"></param>
/// <returns>true if successfully converted</returns>
/// <remarks date="2024-Dec-01" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean HexStringToInteger<T>(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;
}
/// <summary>
/// Extract tall applications found in the package description file.
/// </summary>
/// <param name="packageDescriptionFile"></param>
/// <param name="apps"></param>
/// <remarks date="2024-Dec-01" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static void GetPackageFileApplications(String packageDescriptionFile, List<FileApplications> 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<String>();
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
}
}
/// <summary>
/// Extract the maximum core revision from the package description file.
/// </summary>
/// <param name="packageDescriptionFile"></param>
/// <returns>nullable core revision as Int32?</returns>
/// <remarks date="2024-Nov-30" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="coreRevision">containing the core version</param>
/// <returns>true if version is in range</returns>
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
/// - Removed "Cordonel " string check.
/// </remarks>
/// <remarks date="2022-Dec-05" author="Thomas Wiedebusch">
/// - Core revision data type changed from string ti int32?.
/// </remarks>
public Boolean CheckCoreRevision(Int32? coreRevision)
{
if (null == CoreRevisionMaximum || null == CoreRevisionMinimum ||
null == _packageDescriptionFile || coreRevision == null)
{
return false;
}
return coreRevision >= CoreRevisionMinimum && coreRevision <= CoreRevisionMaximum;
}
/// <summary>
/// Extract the version in form "1.23" from a string and returns it
/// as Int32 value 123 with major * 100 + minor
/// </summary>
/// <param name="versionString">string containing version</param>
/// <returns>version as double</returns>
/// <remarks date="2020-Dec-03" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Feb-24" author="Thomas Wiedebusch">
/// - Changed to Int32 with major * 1000 + minor.
/// </remarks>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Changed to Int32 with major * 100 + minor.
/// </remarks>
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
}
}