laatzen/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterResetPsu.cs

723 lines
26 KiB
C#

using System;
using System.Linq;
using System.Text;
using System.Threading;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
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.Utils.Crc16Ccitt;
using Xylem.Common.Utils.ProcessExec;
using Xylem.Common.Utils.ProcessExec.EventArguments;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile
{
/// <summary>
/// Forcing a meter reset with a PSU (pseudo update):
/// 1. Taking the FLEXNETVERSION from <see cref="GenesisMeter.MeterAppListVersion"/>
/// 2. Build the file upg18 with the FLEXNETVERSION string
/// 3. Download upg18
/// 4. Build upgrade ctrl file forcing update of FLEXNETVERSION
/// 5. TriggerUpgrade
/// </summary>
public class MeterResetPsu : IProcessState
{
#region Variables
// Template of FLEXNETVERSION has to be modified for version, version string and CRC
private static readonly Byte[] TemplateUpgradeFile =
{
0x41, 0x50, 0x50, 0x3E, 0x83, 0xBA, 0x47, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
0x70, 0x47, 0x00, 0x00, 0x02, 0x49, 0x79, 0x44, 0x01, 0x60, 0x00, 0x20, 0x70, 0x47, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x31, 0x2E, 0x32, 0x2E, 0x34, 0x37, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
// upgrade file for FLEXNETVERSION
private Byte[] _upgradeFile;
// upgrade control file content to force an update of exclusively FLEXNETVERSION
private static readonly String UpgradeCtrlFile = $"{MeterFwUpdate.FlexnetVersionAppId:X2}" +
MeterFwUpdate.StrInstallAppCtrlFilePost;
/// <summary>
/// Port scan result event for message dispatcher to caller
/// </summary>
public event EventHandler<ProcessExecEventArgs> OnProcessUpdate;
//disk and name of lookup file
private static readonly String StrFlexnetUpgradeFileName = $"1\\upg{MeterFwUpdate.FlexnetVersionAppId:X2}";
//update reties for files
private const Int32 FileWriteRetries = 1;
//trigger update retries
private const Int32 TriggerWriteRetries = 2;
//time to wait until next trial of communication during reboot
private const Int32 RebootWaitTimeMs = 250;
//reboot wait cycles to be multiplied with reboot wait time for maximum overall time if unsuccessful
private const Int32 RebootWaitCycles = 4 * 90;
//retry of entire update procedure
private const Int32 UpdateProcedureRetries = 1;
//threshold to increase the file write timing
private const Int32 IncreaseTimeoutUpdateProcedureRetries = 4;
// position of version string
private const Int32 FlexVersionStringIndex = 0x66;
private Int32 _processRetryCtr;
private IGenesisMeter _genesisMeter;
private MeterFile _meterFile;
// Text for caller to inform about actual process being carried out
private String _actualOperation;
private MeterResetPsuState _meterResetPsuState;
private MeterResetPsuState _backupMeterResetPsuState;
// number of operations for over all access
private const Int32 NumberOfOperations = 11;
private Int32 _operationCtr;
// error message for display of error reason
public String ErrorMessage;
// manual stop of update process
private Boolean _stopUpdateProcess;
/// <summary>
/// Store configuration in advance to the update
/// </summary>
public Boolean StoreConfigEnable = true;
/// <summary>
/// Threshold to waste the HW if reboots too high
/// </summary>
public const Int32 MAX_REBOOTS_FOR_HEALTHY_HW = 0;
#endregion
#region Events
/// <summary>
/// Process update event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
protected 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("reboot",
OverallProcessCtrPercent, _actualOperation, e.OverallProcessPercent));
}
#endregion
#region Information
/// <summary>
/// Process counter in percent
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double OverallProcessCtrPercent => 100.0 * _operationCtr / NumberOfOperations;
/// <summary>
/// Single file process counter in percent
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public Double SingleFileProcessCtrPercent => _meterFile?.ProcessCtrPercent ?? 0;
#endregion
#region CtorAssignment
/// <summary>
/// Ctor
/// </summary>
/// <param name="genesisMeter"></param>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public MeterResetPsu(IGenesisMeter genesisMeter)
{
if (genesisMeter == null)
return;
AssignGenesis(genesisMeter);
}
/// <summary>
/// Assign new genesis
/// </summary>
/// <param name="genesisMeter"></param>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void AssignGenesis(IGenesisMeter genesisMeter)
{
_genesisMeter = genesisMeter;
_actualOperation = "";
_meterResetPsuState = MeterResetPsuState.Idle;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
if (_meterFile == null)
return;
_meterFile.OnProcessUpdate -= ProcessUpdate_Event;
_meterFile = null;
}
#endregion
#region ProcessInformation
/// <summary>
/// Returns the actual file in process for update
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public String GetActualOperation()
{
return _actualOperation;
}
/// <summary>
/// Returns the LUT Update state
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public String GetResetStateOperation()
{
// search text assigned to current state
var text = MeterResetPsuStateInfo.GetTextFromState(_meterResetPsuState);
if (_meterResetPsuState == MeterResetPsuState.Idle)
{
return text;
}
text += _processRetryCtr > 0 ? $"Retry {_processRetryCtr}" : "";
return text;
}
#endregion
#region Actions
/// <summary>
/// Try to re-establish the file system, therefor logout to close all open files
/// and login again.
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void ReestablishMeterFileSystem()
{
_genesisMeter?.Logout();
_genesisMeter?.ReLogin();
}
/// <summary>
/// Stop the update process
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean StopUpdateProcess
{
get => _stopUpdateProcess;
set
{
_stopUpdateProcess = value;
if (_meterFile != null)
{
_meterFile.StopProcess = value;
}
_meterResetPsuState = MeterResetPsuState.ResetFailed;
}
}
/// <summary>
/// Update meter lookup table
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Dec-10" author="Thomas Wiedebusch">
/// - Initial MockGenesis support.
/// </remarks>
/// <remarks date="2024-Dec-16" author="Thomas Wiedebusch">
/// - Check reboot counter to verify reboot being done.
/// </remarks>
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
/// - CancellationToken.
/// </remarks>
public Boolean MeterResetPsuExecute(CancellationToken cancellationToken = default(CancellationToken))
{
var statusReturn = _genesisMeter.RebootGenesis();
// Status return "Unknown" is from the real Genesis
if (StatusReturn.Unknown != statusReturn)
return StatusReturn.Okay == statusReturn;
if (!ResetPreparation())
{
return false;
}
_operationCtr = 0;
_actualOperation = "";
ErrorMessage = "";
_meterResetPsuState = MeterResetPsuState.StartInitial;
_backupMeterResetPsuState = MeterResetPsuState.Idle;
var registerName = "CUSTOMER_RebootCount";
var rebootCountBeforeReboot = RegisterConverter.ByteArrayToValue<UInt32>(
_genesisMeter.ReadRegister(registerName));
while (_meterResetPsuState != MeterResetPsuState.Idle && !StopUpdateProcess)
{
if (cancellationToken.IsCancellationRequested)
StopUpdateProcess = true;
MeterResetPsuStateMachine();
}
var rebootCountAfterReboot = RegisterConverter.ByteArrayToValue<UInt32>(
_genesisMeter.ReadRegister(registerName));
// The reboot counter will only be increased if reboot executed
if (rebootCountAfterReboot == rebootCountBeforeReboot)
{
return false;
}
return ExitPreparation(true);
}
/// <summary>
/// Reset process and all lists and counters
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
public void ResetAll()
{
//remove the update stop action
StopUpdateProcess = false;
//reset all counters
_processRetryCtr = 0;
//set state machine for automatic FW update
_meterResetPsuState = MeterResetPsuState.Idle;
_backupMeterResetPsuState = MeterResetPsuState.Idle;
_actualOperation = "";
_operationCtr = 0;
//logout/login/auto-login
ReestablishMeterFileSystem();
_genesisMeter?.Logout();
}
/// <summary>
/// Common update preparation and check
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean ResetPreparation()
{
_actualOperation = "Reset preparation";
_meterResetPsuState = MeterResetPsuState.Idle;
_meterFile = new MeterFile(_genesisMeter);
if (_meterFile != null)
_meterFile.OnProcessUpdate += ProcessUpdate_Event;
_genesisMeter?.ReLogin();
if (_genesisMeter == null || !_genesisMeter.IsLoggedOn)
{
return false;
}
//set default timeout for file write operation
_meterFile.SetDefaultFileWriteTimeout();
//remove the update stop action
StopUpdateProcess = false;
return true;
}
/// <summary>
/// Common exit routine
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean ExitPreparation(Boolean returnValue)
{
if (_meterFile != null)
_meterFile.OnProcessUpdate -= ProcessUpdate_Event;
_meterFile = null;
ReestablishMeterFileSystem();
_genesisMeter?.Logout();
if (!StopUpdateProcess)
{
return returnValue;
}
_actualOperation = "";
return false;
}
#endregion
#region StateMachine
/// <summary>
/// State machine for reset PSU (pseudo update)
/// </summary>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2022-Aug-16" author="Thomas Wiedebusch">
/// - Store configurations added
/// </remarks>
/// <remarks date="2022-Dec-12" author="Thomas Wiedebusch">
/// - Wait re-boot introduced.
/// </remarks>
private void MeterResetPsuStateMachine()
{
if (_backupMeterResetPsuState == _meterResetPsuState)
{
Thread.Sleep(1);
}
else
{
// remind backup state to avoid repeated execution and side effects
_backupMeterResetPsuState = _meterResetPsuState;
// set actual state info text, this can be overwritten by each routine
_actualOperation = MeterResetPsuStateInfo.GetTextFromState(_meterResetPsuState);
switch (_meterResetPsuState)
{
case MeterResetPsuState.Idle:
break;
case MeterResetPsuState.StartInitial:
_processRetryCtr = 0;
_meterResetPsuState = MeterResetPsuState.StoreConfigurations;
break;
case MeterResetPsuState.StoreConfigurations:
_meterResetPsuState = StoreConfigurations() ? MeterResetPsuState.BuildUpgradeFile :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.BuildUpgradeFile:
_meterResetPsuState = BuildUpgradeFile() ? MeterResetPsuState.DownloadUpgradeFile :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.DownloadUpgradeFile:
_meterResetPsuState = DownloadUpgradeFile() ? MeterResetPsuState.DownloadUpgradeCtrlFile :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.DownloadUpgradeCtrlFile:
_meterResetPsuState = DownloadUpgradeCtrlFile() ? MeterResetPsuState.TriggerUpgrade :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.TriggerUpgrade:
_meterResetPsuState = TriggerUpgrade() ? MeterResetPsuState.WaitReboot :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.WaitReboot:
_meterResetPsuState = WaitReboot() ? MeterResetPsuState.ResetSucceeded :
MeterResetPsuState.StepFailed;
break;
case MeterResetPsuState.StepFailed:
_meterResetPsuState = _processRetryCtr++ < UpdateProcedureRetries
? MeterResetPsuState.RepeatReset
: MeterResetPsuState.ResetFailed;
break;
case MeterResetPsuState.RepeatReset:
_meterResetPsuState = MeterResetPsuState.DownloadUpgradeFile;
if (_processRetryCtr >= IncreaseTimeoutUpdateProcedureRetries)
{
_meterFile?.SetExtremeFileWriteTimeout();
}
break;
case MeterResetPsuState.ResetFailed:
StopUpdateProcess = true;
_meterResetPsuState = MeterResetPsuState.Idle;
break;
case MeterResetPsuState.ResetSucceeded:
_meterResetPsuState = MeterResetPsuState.Idle;
break;
case MeterResetPsuState.ResetStateListDelimiter:
_meterResetPsuState = MeterResetPsuState.Idle;
break;
default:
_meterResetPsuState = MeterResetPsuState.Idle;
break;
}
}// lock for identical state
}
#endregion
#region Operation
/// <summary>
/// Store configurations before reset, because reboot will use the values
/// of the non-volatile memory.
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-16" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean StoreConfigurations()
{
if (_genesisMeter == null)
{
return false;
}
return !StoreConfigEnable || _genesisMeter.StoreAllConfigurations();
}
/// <summary>
/// Build the upgrade for FLEXNETVERSION
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-11" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-May-07" author="Thomas Wiedebusch">
/// - Changed to take the new hexadecimal version into account as
/// version is already preprocessed at connection time.
/// </remarks>
private Boolean BuildUpgradeFile()
{
if (_genesisMeter == null)
{
return false;
}
// search FLEXNETVERSION
var flexnetversionFound = false;
var strVersion = "";
UInt32 version = 0;
UInt16 fileAppCrc = 0;
foreach (var app in _genesisMeter.MeterAppListVersion.Where(app => app.AppId == MeterFwUpdate.FlexnetVersionAppId))
{
strVersion = app.StrVersion;
fileAppCrc = app.Crc;
version = app.Version;
flexnetversionFound = true;
}
if (!flexnetversionFound)
{
return false;
}
// generate the upgrade file based on the template
_upgradeFile = new Byte[TemplateUpgradeFile.Length];
Buffer.BlockCopy(TemplateUpgradeFile, 0, _upgradeFile, 0, _upgradeFile.Length);
_upgradeFile[MeterFwUpdate.FileAppVersionIndex] = (Byte)(version & 0xFF);
_upgradeFile[MeterFwUpdate.FileAppVersionIndex + 1] = (Byte)((version & 0xFF00) >> 8);
// build the version string and copy it to the upgrade file
var upg18Idx = FlexVersionStringIndex;
foreach (var t in strVersion)
{
_upgradeFile[upg18Idx] = Convert.ToByte(t);
upg18Idx++;
}
// take the payload starting with version index
var payload = new Byte[_upgradeFile.Length - MeterFwUpdate.FileAppVersionIndex];
Buffer.BlockCopy(_upgradeFile, MeterFwUpdate.FileAppVersionIndex, payload, 0, payload.Length);
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(payload);
// avoid replacement wit invalid generated FLEXNETVERSION
if (fileAppCrc != calculatedCrc)
{
return false;
}
// replace the CRC
_upgradeFile[MeterFwUpdate.FileAppCrcIndex] = (Byte)(calculatedCrc & 0xFF);
_upgradeFile[MeterFwUpdate.FileAppCrcIndex + 1] = (Byte)((calculatedCrc & 0xFF00) >> 8);
return true;
}
/// <summary>
/// Send the upgrade file to the meter
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean DownloadUpgradeFile()
{
//initiate upgrade by writing the upgrade control file
if (_upgradeFile == null || _upgradeFile.Length != TemplateUpgradeFile.Length ||
!_meterFile.UnlockEraseWriteMeterFile(StrFlexnetUpgradeFileName))
{
return false;
}
_actualOperation = $"{Resources.StrProcessDownload} FLEXNETVERSION";
var retryCtr = 0;
Boolean returnValue;
//write file and retry if not successful
do
{
returnValue = _meterFile.WriteMeterFile(StrFlexnetUpgradeFileName, _upgradeFile);
if (!returnValue)
{
ReestablishMeterFileSystem();
}
} while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess);
_upgradeFile = null;
return !StopUpdateProcess && returnValue;
}
/// <summary>
/// Send the upgrade control file to the meter
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean DownloadUpgradeCtrlFile()
{
//initiate upgrade by writing the upgrade control file
if (!_meterFile.UnlockEraseWriteMeterFile(MeterFwUpdate.StrUpgradeCtrlFileName))
{
return false;
}
_actualOperation = Resources.StrFwUpdateStateDownloadUpgradeControlFile;
var retryCtr = 0;
Boolean returnValue;
//write file and retry if not successful
do
{
returnValue = _meterFile.WriteMeterFile(MeterFwUpdate.StrUpgradeCtrlFileName,
Encoding.ASCII.GetBytes(UpgradeCtrlFile));
if (!returnValue)
{
ReestablishMeterFileSystem();
}
} while (!returnValue && retryCtr++ < FileWriteRetries && !StopUpdateProcess);
return !StopUpdateProcess && returnValue;
}
/// <summary>
/// Send the trigger update
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean TriggerUpgrade()
{
Boolean returnValue;
//trigger upgrade
_actualOperation = Resources.StrFwUpdateStateTriggerUpgrade;
var triggerCtr = 0;
//set extended timeout for trigger upgrade response delay
_genesisMeter.TransmitProtocol.SetResponseTimeout(5000);
do
{
returnValue = _genesisMeter.WriteRegister<Byte>(Register.System.TriggerFwUpgrade, 1);
if (!returnValue)
{
ReestablishMeterFileSystem();
}
} while (!returnValue && triggerCtr++ < TriggerWriteRetries && !StopUpdateProcess);
//set timeout back to default value
_genesisMeter.TransmitProtocol.SetDefaultResponseTimeout();
_actualOperation = returnValue ? Resources.StrMeterAppTriggerAccepted : Resources.StrMeterAppTriggerFailed;
return returnValue && !StopUpdateProcess;
}
/// <summary>
/// Wait for reboot of meter
/// </summary>
/// <returns></returns>
/// <remarks date="2022-Aug-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Feb-20" author="Thomas Wiedebusch">
/// - Signal event during waiting loop to feed the process bar.
/// </remarks>
private Boolean WaitReboot()
{
_actualOperation = Resources.StrWaitingForReboot;
var rebootCtr = 0;
String pcbId;
do
{
pcbId = RegisterConverter.ByteArrayToValue<String>(
_genesisMeter.ReadRegister(Register.Configexchange.PcbSerialNumber, 12));
if (string.IsNullOrEmpty(pcbId))
{
var overallProcessCtrPercent = 100.0 * rebootCtr / RebootWaitCycles;
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("reboot", overallProcessCtrPercent));
Thread.Sleep(RebootWaitTimeMs);
}
} while (string.IsNullOrEmpty(pcbId) && rebootCtr++ < RebootWaitCycles && !StopUpdateProcess);
return !string.IsNullOrEmpty(pcbId) && !StopUpdateProcess;
}
#endregion
}
}