2649 lines
103 KiB
C#
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
|
|
}
|
|
}
|