laatzen/ServiceFwUpdate/Ui/ServiceFwUpdateSw/FrmServiceFwUpdateSw.cs

5111 lines
231 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Logic.ProductionToProductMapper.Cordonel;
using Newtonsoft.Json;
using NLog;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.CommonCore.ThreadWatcher;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
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.GenesisFile;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
using Xylem.Common.Utils.Logging;
using Xylem.Common.Utils.ProcessExec;
using Xylem.Common.Utils.ProcessExec.EventArguments;
using Xylem.Common.Utils.UiInvoker;
using Xylem.Common.Utils.UiLanguageControl;
using Xylem.ServiceFwUpdate.Common.FwUpdateConfig.Consts;
using Xylem.ServiceFwUpdate.Common.FwUpdateSafe;
using Xylem.ServiceFwUpdate.Common.FwUpdateSafe.Consts;
using Xylem.ServiceFwUpdate.Ui.ServiceFwUpdateSw.Const;
using Xylem.ServiceFwUpdate.Ui.ServiceFwUpdateSw.Properties;
namespace Xylem.ServiceFwUpdate.Ui.ServiceFwUpdateSw
{
/// <summary>
/// FW update form
/// </summary>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
[Serializable]
public partial class FrmServiceFwUpdateSw : Form
{
#region ------------------------------------------ Variables --------------------------------------------------
// Genesis (legacy and program internal name for Cordonel)
private GenesisMeter _currentGenesis;
private readonly MeterBatch _meterBatch;
private Byte[] _fwUpdateSafePwdFile;
private MeterLutFile _fwUpdateSafeLutFile;
private PowerCorrection _fwUpdatePowerCorrection;
private MeterFile _meterFile;
private String _passwordLvl8;
private String _skeletonKey;
// port settings
private MeterPortScanner _serialPortScanner;
private PortConfig _portConfig;
private String _portType;
private readonly DataTable _dataTable = new DataTable();
private readonly Thread _fwUpdateSwThread;
private const Int32 Slot = 0;
private DateTimeOffset _startTime;
private Boolean _resetTimeMeasurement;
private readonly ILogger _logger;
private readonly CancellationTokenSource _processToken = new CancellationTokenSource();
// actual process state
private ProcessState _processState;
// locker to avoid repeated state execution
private ProcessState _lastProcessState;
// reminder for state change to execute e.g. error messaging
private ProcessState _invokerProcessState;
// used for display of waiting for meter if the state does not change
private String _lastFwUpdateState;
private Int32 _processProgressObserver;
private MeterFwUpdate _meterFwUpdate;
// file block part size in which the applications will be split for the FW-Update download
private const Int32 MaxPartialFileDataSize = 2 * 1024;
// boot variables, reboot timeout before stopping PCB ID readout trials
private const Int32 RebootTimeout_ms = 180000;
private Int32 _bootDelayCtr_ms;
private Boolean _timerIntervalExpired;
private Boolean _timerDisplayOn;
private Int32 _progressBarValueCounter;
// reminder for corrupted password file
private Boolean _passwordFileIsCorrupted;
// initial connect to force a quick connect being able switch the pulse mode off
private Boolean _initialConnect;
// remind final login, as this allows EXPLICIT login with password level 8 to validate password file
private Boolean _afterUpdateConnect;
// remind recovery request to check finally all register settings
private Boolean _recoveryRegistersRequired;
// reminder for Genesis found in update list (_cordonelDeviceInfos)
private Boolean _genesisInUpdateList;
// status information
private static readonly Color ColorDefault = Color.Black;
private static readonly Color ColorWarning = Color.DarkOrange;
private static readonly Color ColorSuccess = Color.Green;
private static readonly Color ColorProcessFailed = Color.Red;
private static readonly Color ColorOngoingProcess = Color.Blue;
private static readonly Color ColorUnknownStatus = Color.Gray;
private const String SuccessSign = @"✔";
private const String FailedSign = @"✘";
private const String WarningSign = @"!";
private const String StrSeparator = "-----------------------------------------------------" +
"-----------------------------------------------------" +
"-----------------------------";
// data grid styles
private readonly DataGridViewCellStyle _styleInstalled = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleNotInstalled = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleInvalidMeterCrc = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleUpdateRequired = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleDownloadSucceeded = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleDownloadFailed = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleDownloadOngoing = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleValidated = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleVerificationRequired = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleEraseRequired = new DataGridViewCellStyle();
private readonly DataGridViewCellStyle _styleCommunicationError = new DataGridViewCellStyle();
// update information
private Boolean _binaryPackageLoaded;
// license setup with start up check delay to end program if license is unknown
private SoftwareLicense _softwareLicense;
private Int32 _licenseCheckTimer_ms = 2000;
// extended information of safe for report
private FwUpdateSafeInfo _fwUpdateSafeInfo;
// Cordonel device information containing everything needed to update a specific Cordonel
private readonly List<CordonelDeviceInfo> _cordonelDeviceInfos = new List<CordonelDeviceInfo>();
private CordonelFirmware _cordonelFirmwarePackage = new CordonelFirmware();
// external update remarks form
private FrmHistory _frmHistory = new FrmHistory();
// remind manually changed culture setting
private CultureInfo _cultureInfo;
private readonly Version _version;
// base path to update packages received from FW-Update Loader
private FwUpdatePaths _basePath;
// path and name of report files
private String _pathNameReportFile;
// path and name of the installed applications
private String _pathNameCordonelAppVersionFile;
// path and name of the restore settings file on crashed application or interrupted update
private String _pathNameCordonelRegisterRestoreSettingsFile;
// Register subset needed to adjust for update performance content before update
private Dictionary<String, Byte[]> _pulseModeRegistersRestore = new Dictionary<String, Byte[]>();
// loading the lists of meter files which shall be erased before the FW-Update and restored after.
private MeterFilesEraseRestore _meterFilesEraseRestore;
// register restore
private RegisterRestorer _registerRestorer;
//// directory information
//private String _currentDirectory;
// Login retry parameters to delay recurrent login trial after unsuccessfully trial to avoid lock
// of authentication by the meter
private const Int32 DefaultLoginDelay_ms = 2000;
private Int32 _loginDelay_ms = DefaultLoginDelay_ms;
// directory information
private List<String> _readMeterFiles;
// remind customer serial number for logging
private String _customerSerialNumber = "?";
// error message collection to decide for field operator action after one run
private List<String> _errorCollectionMessages = new List<String>();
// State machine sequence for preparation of meter release. This will be used by the error process as well
// as by the state machine to have a common ruler for this sorted sequence!
private List<ProcessState> _stateSequencePrepareMeterRelease = new List<ProcessState>
{
ProcessState.RestoreMeterFiles,
ProcessState.RestoreLutFile,
ProcessState.RecoverRegisters,
ProcessState.FinalReadRegisters,
ProcessState.CompareRegisters,
ProcessState.RestorePasswordFile,
ProcessState.FinalConnect,
ProcessState.ReadEngineeringLogs,
ProcessState.CorrectPowerOverestimation,
ProcessState.VerifyMeterFiles,
ProcessState.CalculateLifeTime,
ProcessState.CheckFwUpdateSuccess
};
// State machine sequence for start of the connection and check for meter. This will be used by the error
// process as well as by the state machine to have a common ruler for this sorted sequence!
private List<ProcessState> _stateSequencePrepareAndCheckMeter = new List<ProcessState>
{
ProcessState.PortScan,
ProcessState.InitialConnect,
ProcessState.InitialReadRegisters,
ProcessState.InitialReadMeterFiles,
ProcessState.ReadInfoEraseRestoreFiles,
ProcessState.LoadUpdateFiles,
ProcessState.CheckUpdateRequest
};
// State machine sequence for start of the connection and check for meter. This will be used by the error
// process as well as by the state machine to have a common ruler for this sorted sequence!
private List<ProcessState> _stateSequenceFwUpdate = new List<ProcessState>
{
ProcessState.CheckUpdateCapability,
ProcessState.EraseMeterFiles,
ProcessState.FirmwareUpdate,
ProcessState.Reboot,
ProcessState.RebootConnect,
ProcessState.RestoreMeterFiles
};
/// <summary>
/// Timer for progress update
/// </summary>
private readonly System.Threading.Timer _tmrProgressUpdate;
private const Int32 TimerInterval_ms = 200;
#endregion --------------------------------------- Variables --------------------------------------------------
#region ------------------------------------------ State Machine ----------------------------------------------
/// <summary>
/// State machine for FW update process:
/// Will be executed in an endless loop from the "Service FW-Update Thread" until exit. State will be executed
/// once and then locked for repeated execution. A state change is the reason for reentry.
/// NOTE:
/// Change the state immediately before calling any routine to avoid repeated execution of routine!
/// </summary>
/// <remarks date="2020-Oct-21" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - State execution locked if not changed!
/// </remarks>
/// <remarks date="2020-Dec-16" author="Thomas Wiedebusch">
/// - Reboot logic implemented.
/// </remarks>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - States reworked.
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Validate software state added.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Password file installation forces re-login trial.
/// </remarks>
/// <remarks date="2021-Feb-17" author="Thomas Wiedebusch">
/// - Added meter file readout, erasure, restore and verification.
/// </remarks>
/// <remarks date="2021-Feb-18" author="Thomas Wiedebusch">
/// - RestAllLabels changed,
/// - After reinstalling the password file go here to idle in FwUpdateSuccess, in the password file restore
/// routine a login will be forced if the password has been installed again.
/// </remarks>
/// <remarks date="2021-Mar-02" author="Thomas Wiedebusch">
/// - Disable all buttons until license validated.
/// </remarks>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Report file generation.
/// - Call of LogInstalledFw
/// </remarks>
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
/// - Report file generation removed,
/// - Meter files pre update read does not stop process if it fails,
/// - Log installed Meter FW removed.
/// </remarks>
/// <remarks date="2021-Mar-17" author="Thomas Wiedebusch">
/// - On Init set buttons to download disable but connect enable.
/// </remarks>
/// <remarks date="2021-Apr-15" author="Thomas Wiedebusch">
/// - On final connection after update and password restore, the connect will be exit with
/// check update request to make the final check.
/// </remarks>
/// <remarks date="2021-Jun-08" author="Roland Drabesch">
/// - Sleep on equal process state to force suspend of actual thread.
/// </remarks>
/// <remarks date="2021-Jun-30" author="Thomas Wiedebusch">
/// - If the register have been backup on a subsequent FW-Update trial, the read, erase of meter files will be
/// skipped to avoid erasure of the upgXX - Files which are successfully downloaded. So only the remaining
/// upgXX - Files will be downloaded as the MeterFwUpdate object reminds all successfully downloaded Apps.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Release display on idle entry.
/// </remarks>
/// <remarks date="2022-Jul-22" author="Thomas Wiedebusch">
/// - Release display on success entry and on error state.
/// </remarks>
/// <remarks date="2023-Oct-18..25" author="Thomas Wiedebusch">
/// - Reorganized to support maintenance before the FW update capability check.
/// </remarks>
/// <remarks date="2023-Oct-27" author="Thomas Wiedebusch">
/// - Release meter to normal operation after error state.
/// </remarks>
/// <remarks date="2023-Nov-09" author="Thomas Wiedebusch">
/// - Major rework of sequence.
/// </remarks>
/// <remarks date="2023-Nov-14" author="Thomas Wiedebusch">
/// - Prepared for engineering logs.
/// </remarks>
/// <remarks date="2023-Nov-15..21" author="Thomas Wiedebusch">
/// - State machine sequences based on ProcessState tables forcing the sequencing.
/// </remarks>
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
/// - Restore meter files and compare registers removed for this release.
/// </remarks>
/// <remarks date="2023-Nov-27" author="Thomas Wiedebusch">
/// - Compare registers reactivated based on new interface (configuration.json) with new
/// "StaticType": “approximate”.
/// </remarks>
/// <remarks date="2024-May-07" author="Thomas Wiedebusch">
/// - Power correction for FW update to new version starting with R1.3.0 for EMEA and
/// R2.0.06 for NA applied.
/// </remarks>
private void FwUpdateSwStateMachine()
{
while (!_processToken.IsCancellationRequested)
{
if (_lastProcessState == _processState)
{
Thread.Sleep(1);
// do nothing until state changed
}
else
{
try
{
_lastProcessState = _processState;
switch (_processState)
{
case ProcessState.Init:
ResetAllStatusLabels();
SetDisableDownloadAndEnableConnect();
_processState = ProcessState.Idle;
break;
case ProcessState.Idle:
break;
case ProcessState.ValidateSoftware:
ValidateSoftware(ProcessState.Idle);
break;
//[Stop] initiated from user
case ProcessState.Stop:
// stop clears all status and ongoing processes
StopProcesses();
// do not change status leave process state stop on stop
break;
case ProcessState.EndOfList:
// The end of a process sequence state list shouldn't be reached, handle it as error
case ProcessState.Error:
// Error process has to take care of the next state
ErrorProcesses();
break;
// [Connect] initiates port scan if NOT already done once
case ProcessState.PortScan:
ResetAllStatusLabels();
PortScanner(GetNextProcessState(_processState, _stateSequencePrepareAndCheckMeter));
break;
// [Connect] initiates initial connect, switches pulse mode off
case ProcessState.InitialConnect:
DisposeGenesis();
_initialConnect = true;
_afterUpdateConnect = false;
_recoveryRegistersRequired = false;
Connect(GetNextProcessState(_processState, _stateSequencePrepareAndCheckMeter));
break;
case ProcessState.InitialReadRegisters:
ReadRegisters(GetNextProcessState(_processState, _stateSequencePrepareAndCheckMeter));
break;
case ProcessState.InitialReadMeterFiles:
ReadMeterFiles(GetNextProcessState(_processState, _stateSequencePrepareAndCheckMeter));
break;
case ProcessState.ReadInfoEraseRestoreFiles:
PrepareInfoMeterFilesEraseRestore(GetNextProcessState(_processState,
_stateSequencePrepareAndCheckMeter));
break;
case ProcessState.LoadUpdateFiles:
LoadUpdateFiles(GetNextProcessState(_processState, _stateSequencePrepareAndCheckMeter));
break;
case ProcessState.CheckUpdateRequest:
// If the FW is up to date (return second state) the meter release can be initiated
CheckUpdateRequest(ProcessState.CheckUpdateCapability, ProcessState.PrepareMeterRelease);
break;
case ProcessState.CheckUpdateCapability:
CheckUpdateCapability(GetNextProcessState(_processState, _stateSequenceFwUpdate));
break;
case ProcessState.EraseMeterFiles:
// even if the erasure failed it should be continued
MeterFilesErase(GetNextProcessState(_processState, _stateSequenceFwUpdate));
break;
case ProcessState.FirmwareUpdate:
FwUpdate(GetNextProcessState(_processState, _stateSequenceFwUpdate));
break;
case ProcessState.Reboot:
_afterUpdateConnect = true;
Reboot(GetNextProcessState(_processState, _stateSequenceFwUpdate));
break;
case ProcessState.RebootConnect:
DisposeGenesis();
Connect(GetNextProcessState(_processState, _stateSequenceFwUpdate));
break;
case ProcessState.RestoreMeterFiles:
// Restore meter files is necessary after FW-Update procedure as this has erased
// those files before the update.
//TODO THW MeterFilesRestore(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
_processState = GetNextProcessState(_processState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.PrepareMeterRelease:
// The prepare meter release is the entry for the finalizing of maintenance sequence
// after an error which makes it necessary to release the meter but restore passwords
// and log all internal meter information for analysis.
// ATTENTION: Needed to reassign to "ProcessState.RestoreLutFile" to set a valid state
// of the _stateSequencePrepareMeterRelease beyond the FW-Update entry. This is needed
// to start the sequence.
_processState = ProcessState.RestoreLutFile;
break;
case ProcessState.RestoreLutFile:
RestoreLutFile(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.RecoverRegisters:
RegisterRecovery(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.FinalReadRegisters:
// Read the registers after update as the definitions may have changed or after recovery
// has been executed
ReadRegisters(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.CompareRegisters:
ReadRegisters(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.RestorePasswordFile:
RestorePasswordFile(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.FinalConnect:
_afterUpdateConnect = false;
Connect(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.VerifyMeterFiles:
ReadMeterFiles(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.ReadEngineeringLogs:
//TODO THW ReadEngineeringLogs(ProcessState.CalculateLifeTime, ProcessState.CalculateLifeTime);
_processState = GetNextProcessState(_processState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.CorrectPowerOverestimation:
CorrectPowerOverestimation(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.CalculateLifeTime:
CalculateLifeTime(GetNextProcessState(_processState, _stateSequencePrepareMeterRelease));
break;
case ProcessState.CheckFwUpdateSuccess:
// If the update request returns false, the FW is up to date,
// if true it needs an update (update did not succeed) release meter to normal operation to
// leave this to the user
CheckUpdateRequest(ProcessState.ReleaseMeterToNormalOperation, ProcessState.FwUpdateSuccess);
break;
case ProcessState.FwUpdateSuccess:
ReleaseMeterToNormalOperation();
FwUpdateSuccessMessage();
_processState = ProcessState.Idle;
break;
case ProcessState.ReleaseMeterToNormalOperation:
ReleaseMeterToNormalOperation();
_processState = ProcessState.Idle;
break;
default:
_processState = ProcessState.Idle;
break;
}
}
catch (ThreadAbortException)
{
throw;
}
catch (Exception e)
{
if (_fwUpdateSwThread.ThreadState == ThreadState.Aborted
|| _fwUpdateSwThread.ThreadState == ThreadState.AbortRequested)
{
MessageBoxShow(e.ToString(), Resources.StrMessageWindowFailed, MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw;
}
}
//finally
//{
//}
}
}// state locked against repeated execution
}
#endregion --------------------------------------- State Machine ----------------------------------------------
#region ------------------------------------------ Timer Controls ---------------------------------------------
/// <summary>
/// The timer is used to check to update a progress bar for routines not having an event callback handler and
/// to monitor for hanging communication. If the communication gets stuck this means, that the execution
/// routine will not fire any event, finally the timer as to interact.
/// This routine will be executed in the "Form Service FW-Update thread".
/// </summary>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Timer interval expired introduced.
/// </remarks>
/// <remarks date="2021-Feb-02" author="Thomas Wiedebusch">
/// - Added label waiting for meter response at reboot.
/// </remarks>
/// <remarks date="2021-Feb-17" author="Thomas Wiedebusch">
/// - Added meter file access states.
/// </remarks>
/// <remarks date="2021-Mar-02" author="Thomas Wiedebusch">
/// - Timeout for license information.
/// </remarks>
/// <remarks date="2021-Mar-29" author="Thomas Wiedebusch">
/// - Update to all cyclic progress bar changes.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Changed timer to System.Threading.Timer as System.Windows.Forms.Timer does not operate if called as DLL
/// from the FW-Update Loader and the FW-Update Loader uses System.Windows.Forms.Timer.
/// - UiInvoker needed as timer now being called from separate thread.
/// </remarks>
/// <remarks date="2021-Nov-16" author="Thomas Wiedebusch">
/// - Added new states.
/// </remarks>
/// <remarks date="2023-Nov-16..22" author="Thomas Wiedebusch">
/// - Added new states.
/// </remarks>
private void TmrProgressUpdate_Tick(Object state)
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
switch (_processState)
{
case ProcessState.Idle:
if (_softwareLicense == null && (_licenseCheckTimer_ms -= TimerInterval_ms) < 0)
_processState = ProcessState.ValidateSoftware;
break;
case ProcessState.EraseMeterFiles:
case ProcessState.RestoreMeterFiles:
case ProcessState.InitialReadMeterFiles:
case ProcessState.VerifyMeterFiles:
case ProcessState.PreUpdateReadMeterFiles:
case ProcessState.RestorePasswordFile:
case ProcessState.RestoreLutFile:
case ProcessState.ReadEngineeringLogs:
case ProcessState.InitialConnect:
case ProcessState.RebootConnect:
case ProcessState.FinalConnect:
// These routines do not have an event callback handler
if (_progressBarValueCounter++ > 100)
_progressBarValueCounter = 0;
UiInvoker.ProgressBarInvoker(barOverallProgressUpdate, _progressBarValueCounter);
break;
case ProcessState.FirmwareUpdate:
var singleProgress = (Int32)(_meterFwUpdate?.SingleFileProcessCtrPercent ?? 0);
if (_meterFwUpdate?.GetFwUpdateStateOperation() != _lastFwUpdateState)
{
_lastFwUpdateState = _meterFwUpdate?.GetFwUpdateStateOperation();
FillDataGridWithAllInfos();
UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed, visible: false);
}
else
{
// If the communication gets frozen
UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed,
Resources.StrWaitForMeterResponse, _processProgressObserver == singleProgress);
}
_processProgressObserver = singleProgress;
break;
case ProcessState.Reboot:
UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed,
Resources.StrWaitForMeterResponse);
_timerIntervalExpired = true;
break;
}
if (_timerDisplayOn)
UpdateTimeDisplay();
}
/// <summary>
/// Timing measurement to keep user informed about expired time.
/// </summary>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - UiInvoker as timer is now in separate task calling this routine.
/// </remarks>
private void UpdateTimeDisplay()
{
var time = DateTimeOffset.UtcNow;
var timeSpan = time - _startTime;
UiInvoker.ControlInvoker(lblUpdateTime, ColorDefault, $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}");
}
#endregion --------------------------------------- Timer Controls ---------------------------------------------
#region ------------------------------------------ Interfaces -------------------------------------------------
/// <summary>
/// Interface function to receive serial byte stream assigned to a special container.
/// As a complex field cannot be passed between a dynamic loaded assembly due to
/// the unknown signature or assembly version, a simply data type will be passed and
/// then converted to the complex type.
/// </summary>
/// <param name="dataContainerId"></param>
/// <param name="serialJsonStream"></param>
/// <returns>true if succeeded</returns>
/// <remarks date="2020-Dec-14" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Set process state to validate the software if this message comes in.
/// </remarks>
/// <remarks date="2021-Jan-14" author="Thomas Wiedebusch">
/// - Set path to FW-Update SW directory to take all DLLs from there.
/// </remarks>
/// <remarks date="2021-Mar-02" author="Thomas Wiedebusch">
/// - Software license object at incoming license information.
/// </remarks>
/// <remarks date="20212-Nov-29" author="Thomas Wiedebusch">
/// - Single FW-package.
/// </remarks>
/// <remarks date="20212-Nov-29" author="Thomas Wiedebusch">
/// - Single FW-package.
/// </remarks>
/// <remarks date="2023-Oct-23" author="Thomas Wiedebusch">
/// - Added FW update safe information for report
/// </remarks>
// ReSharper disable once UnusedMember.Global this cannot be seen here because its
// called from externally
public Boolean SetDataContainerJson(Int32 dataContainerId, String serialJsonStream)
{
var dataContainerName = (DataContainerName)dataContainerId;
switch (dataContainerName)
{
case DataContainerName.FwUpdateSafeInfo:
_fwUpdateSafeInfo = new FwUpdateSafeInfo();
_fwUpdateSafeInfo = JsonConvert.DeserializeObject<FwUpdateSafeInfo>(serialJsonStream);
return true;
case DataContainerName.CordonelDeviceInfo:
_cordonelDeviceInfos.Add(JsonConvert.DeserializeObject<CordonelDeviceInfo>(serialJsonStream));
return true;
case DataContainerName.SoftwareLicense:
_softwareLicense = new SoftwareLicense();
_softwareLicense = JsonConvert.DeserializeObject<SoftwareLicense>(serialJsonStream);
_processState = ProcessState.ValidateSoftware;
return true;
case DataContainerName.UpdatePackageBasePath:
_basePath = JsonConvert.DeserializeObject<FwUpdatePaths>(serialJsonStream);
return true;
case DataContainerName.UpdatePackage:
_cordonelFirmwarePackage = JsonConvert.DeserializeObject<CordonelFirmware>(serialJsonStream);
return true;
default:
InfoProcessFailed(null, Resources.StrDataContainerUnknown);
break;
}
return false;
}
#endregion --------------------------------------- Interfaces -------------------------------------------------
#region ------------------------------------------ Form Load Unload -------------------------------------------
/// <summary>
/// Ctor FW update
/// </summary>
/// <remarks date="2021-Mar-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Changed timer to System.Threading.Timer as System.Windows.Forms.Timer does not operate if called as DLL
/// from the FW-Update Loader and the FW-Update Loader uses System.Windows.Forms.Timer.
/// </remarks>
public FrmServiceFwUpdateSw()
{
_logger = NLogHelper.CreateOrGetLogger("ServiceFwUpdateSw");
_version = Assembly.GetExecutingAssembly().GetName().Version;
// Log version to files for debug purposes
_logger.Info(StrSeparator);
_logger.Info($"Service FwUpdateSw Version: {_version.Major}.{_version.Minor}.{_version.Build}");
_logger.Info(StrSeparator);
_meterBatch = new MeterBatch();
InitializeComponent();
_cultureInfo = Thread.CurrentThread.CurrentCulture;
radioBtnEnglishLanguage.Checked = true;
if (_cultureInfo.IetfLanguageTag == "de-DE")
{
radioBtnGermanLanguage.Checked = true;
}
SetDesktopLocation(0, 0);
lblFwUpdateInfo.Text = $@"Version: {_version.Major}.{_version.Minor}.{_version.Build}";
// set locked repeat initially different from process state to unlock first entry to state machine
_lastProcessState = ProcessState.Unspecified;
_invokerProcessState = ProcessState.Unspecified;
_processState = ProcessState.Init;
//assign thread to loop and start thread
_fwUpdateSwThread = new Thread(FwUpdateSwStateMachine);
ThreadWatcher.Instance.Start(_fwUpdateSwThread);
_timerDisplayOn = false;
_tmrProgressUpdate = new System.Threading.Timer(TmrProgressUpdate_Tick, null, 200, 200);
}
/// <summary>
/// Logout from Genesis and dispose it to force a new read of registers.
/// </summary>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2022-Apr-28" author="Thomas Wiedebusch">
/// - Release display to normal operation,
/// - switch measurement LED off.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Release display removed.
/// </remarks>
private void DisposeGenesis()
{
_currentGenesis?.Logout();
// Batch removal disposes the meter!
_meterBatch?.RemoveAllMeters();
_currentGenesis = null;
}
/// <summary>
/// Reset all status labels, clear data table and set genesis to not connected.
/// </summary>
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Cleaned for thread safe access to labels and controls.
/// </remarks>
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
/// - Removed clear history.
/// </remarks>
/// <remarks date="2021-Feb-18" author="Thomas Wiedebusch">
/// - Split label reset to pre-update and post-update to keep the pre-update information.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
/// <remarks date="2021-Mar-02" author="Thomas Wiedebusch">
/// - Removed button settings.
/// </remarks>
private void ResetAllStatusLabels()
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
InfoStatusUnknown(lblCordonelDetection, Resources.StrCordonelDetectUnspecified);
InfoStatusUnknown(lblCordonelAuthentication, Resources.StrCordonelAuthenticationUnspecified);
UiInvoker.ControlInvoker(lblConnectPcb, ColorProcessFailed, Resources.StrNotConnected);
UiInvoker.ControlViewInvoker(lblProductToInstall, false);
InfoStatusUnknown(lblUpdateInformationStatus, Resources.StrUpdateInformationStatusUnspecified);
InfoStatusUnknown(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityUnspecified);
InfoStatusUnknown(lblRegisterReadout, Resources.StrRegisterReadoutUnspecified);
InfoStatusUnknown(lblFileErase, Resources.StrFileReadoutUnspecified);
InfoStatusUnknown(lblFirmwareUpdate, Resources.StrFirmwareUpdateUnspecified);
InfoStatusUnknown(lblRegisterRestore, Resources.StrRegisterRestoreUnspecified);
InfoStatusUnknown(lblFileRestore, Resources.StrFileRestoreUnspecified);
InfoStatusUnknown(lblPasswordFileCheck, Resources.StrPasswordFileRestoreUnspecified);
InfoStatusUnknown(lblLutFileCheck, Resources.StrLutFileRestoreUnspecified);
SetOverallProgressDisplayOff();
SetActualProgressDisplayOff();
}
/// <summary>
/// Clear history window.
/// </summary>
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void ClearHistoryWindow()
{
if (_frmHistory != null && _frmHistory.rtbHistory.InvokeRequired)
{
_frmHistory.rtbHistory.Invoke(new Action(() => { _frmHistory.rtbHistory.Clear(); }));
}
else
_frmHistory?.rtbHistory.Clear();
}
private void FrmFwUpdate_Load(Object sender, EventArgs e)
{
_styleInstalled.BackColor = Color.White;
_styleInstalled.ForeColor = Color.Black;
_styleNotInstalled.BackColor = Color.LightGray;
_styleNotInstalled.ForeColor = Color.Black;
_styleInvalidMeterCrc.BackColor = Color.White;
_styleInvalidMeterCrc.ForeColor = Color.Purple;
_styleUpdateRequired.BackColor = Color.White;
_styleUpdateRequired.ForeColor = Color.Red;
_styleEraseRequired.BackColor = Color.LightGray;
_styleEraseRequired.ForeColor = Color.Black;
_styleDownloadOngoing.BackColor = Color.GreenYellow;
_styleDownloadOngoing.ForeColor = Color.Black;
_styleValidated.BackColor = Color.White;
_styleValidated.ForeColor = Color.Green;
_styleDownloadSucceeded.BackColor = Color.LightGreen;
_styleDownloadSucceeded.ForeColor = Color.Green;
_styleDownloadFailed.BackColor = Color.LightPink;
_styleDownloadFailed.ForeColor = Color.Red;
_styleCommunicationError.BackColor = Color.LightPink;
_styleCommunicationError.ForeColor = Color.Red;
_styleVerificationRequired.BackColor = Color.LightGoldenrodYellow;
_styleVerificationRequired.ForeColor = Color.Green;
_resetTimeMeasurement = true;
}
/// <summary>
/// Finalize update and clean up. Bring the display back to normal operation and switch the LED off.
/// </summary>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Added LED off.
/// </remarks>
/// <remarks date="2022-Apr-28" author="Thomas Wiedebusch">
/// - Login before display back to normal operation.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Release display removed.
/// </remarks>
private void FrmFwUpdate_FormClosing(Object sender, FormClosingEventArgs e)
{
_frmHistory?.Close();
_processToken?.Cancel();
// this additionally disposes all meters
_meterBatch?.RemoveAllMeters();
_meterBatch?.Dispose();
_meterFwUpdate?.Dispose();
_meterFwUpdate = null;
_registerRestorer = null;
_tmrProgressUpdate?.Dispose();
}
#endregion --------------------------------------- Form Load Unload -------------------------------------------
#region ------------------------------------------ Data Grid Controls -----------------------------------------
/// <summary>
/// Build data grid and fill it with meter and file information,
/// compare version and CRC of meter and file
/// </summary>
private void FillDataGridWithAllInfos()
{
if (_currentGenesis == null)
{
return;
}
Invoke(new Action(() =>
{
_dataTable.Rows.Clear();
_dataTable.Columns.Clear();
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
_dataTable.Columns.Add(Resources.StrTableAppName, typeof(String));
_dataTable.Columns.Add(Resources.StrTableAppId, typeof(String));
_dataTable.Columns.Add(Resources.StrTableMeterVersion, typeof(String));
_dataTable.Columns.Add(Resources.StrTableMeterCrc, typeof(String));
_dataTable.Columns.Add(Resources.StrTableFileVersion, typeof(String));
_dataTable.Columns.Add(Resources.StrTableFileCrc, typeof(String));
_dataTable.Columns.Add(Resources.StrTableFileSize, typeof(String));
_dataTable.Columns.Add(Resources.StrTableStatus, typeof(String));
foreach (var meterApp in _currentGenesis.MeterAppListVersion)
{
var row = _dataTable.NewRow();
//application information read from configuration
row[Resources.StrTableAppName] = meterApp.AppName;
row[Resources.StrTableAppId] = MeterFwUpdate.ConvertAppIdToString(meterApp.AppId);
_dataTable.Rows.Add(row);
}
//output data to data grid view
gridViewApplic.DataSource = _dataTable.DefaultView;
foreach (DataRow dataRow in _dataTable.Rows)
{
DisplayFileAppInfo(dataRow);
DisplayMeterAppInfo(dataRow);
}
foreach (DataGridViewColumn column in gridViewApplic.Columns)
{
column.SortMode = DataGridViewColumnSortMode.NotSortable;
}
//color the results of the compare
UpdateInformationStyleSet();
}));
}
/// <summary>
/// Set meter application information to data grid view
/// </summary>
/// <param name="dataRow"></param>
private void DisplayMeterAppInfo(DataRow dataRow)
{
if (_currentGenesis == null)
{
return;
}
var meterStateAppInfo = new MeterAppStateInfo();
foreach (var meterApp in _currentGenesis.MeterAppListVersion)
{
var appId = dataRow[Resources.StrTableAppId].ToString();
if (appId != MeterFwUpdate.ConvertAppIdToString(meterApp.AppId))
{
continue;
}
dataRow[Resources.StrTableStatus] = meterStateAppInfo.GetTextFromState(meterApp.Status);
dataRow[Resources.StrTableMeterVersion] = meterApp.StrVersion;
dataRow[Resources.StrTableMeterCrc] = meterApp.IsInstalled ?
MeterFwUpdate.ConvertCrcToString(meterApp.Crc) : "";
}
}
/// <summary>
/// Set file application information to data grid view.
/// </summary>
/// <param name="dataRow"></param>
private void DisplayFileAppInfo(DataRow dataRow)
{
if (_currentGenesis == null || _meterFwUpdate?.FileApps == null)
{
return;
}
foreach (var meterApp in _currentGenesis.MeterAppListVersion)
{
var appId = dataRow[Resources.StrTableAppId].ToString();
if (appId != MeterFwUpdate.ConvertAppIdToString(meterApp.AppId))
{
continue;
}
foreach (var fileApp in _meterFwUpdate.FileApps.Where(fileApp => meterApp.AppId == fileApp.AppId))
{
dataRow[Resources.StrTableFileVersion] = fileApp.StrVersion;
dataRow[Resources.StrTableFileCrc] = MeterFwUpdate.ConvertCrcToString(fileApp.Crc);
dataRow[Resources.StrTableFileSize] = $"{(Double)fileApp.BinData.Count / 1024:0.000}";
}
}
}
/// <summary>
/// Display the update information
/// </summary>
/// <remarks date="2021-Jun-30" author="Thomas Wiedebusch">
/// - MeterAppState.FileAppInvalid changed from _styleDownloadOngoing to _styleNotInstalled
/// </remarks>
/// <remarks date="2024-Apr-23" author="Thomas Wiedebusch">
/// - New style for communication error
/// </remarks>
private void UpdateInformationStyleSet()
{
foreach (DataGridViewRow dataGridRow in gridViewApplic.Rows)
{
var testString = dataGridRow.Cells[Resources.StrTableStatus].Value.ToString();
var meterAppStateInfo = new MeterAppStateInfo();
var meterAppState = meterAppStateInfo.GetStateFromText(testString);
switch (meterAppState)
{
case MeterAppState.MeterAppInstalledUnchecked:
dataGridRow.DefaultCellStyle = _styleInstalled;
break;
case MeterAppState.MeterAppUpToDate:
dataGridRow.DefaultCellStyle = _styleValidated;
break;
case MeterAppState.MeterAppInstallationRequired:
dataGridRow.DefaultCellStyle = _styleUpdateRequired;
break;
case MeterAppState.FileAppInvalid:
dataGridRow.DefaultCellStyle = _styleNotInstalled;
break;
case MeterAppState.MeterAppVersionOutdated:
dataGridRow.DefaultCellStyle = _styleUpdateRequired;
break;
case MeterAppState.Unknown:
dataGridRow.DefaultCellStyle = _styleCommunicationError;
break;
case MeterAppState.MeterAppNotInstalled:
dataGridRow.DefaultCellStyle = _styleNotInstalled;
break;
case MeterAppState.MeterAppNotRequired:
dataGridRow.DefaultCellStyle = _styleNotInstalled;
break;
case MeterAppState.InvalidCrc:
dataGridRow.DefaultCellStyle = _styleInvalidMeterCrc;
break;
case MeterAppState.MeterAppErasureRequired:
dataGridRow.DefaultCellStyle = _styleEraseRequired;
break;
case MeterAppState.MeterAppDownloadSucceeded:
dataGridRow.DefaultCellStyle = _styleDownloadSucceeded;
break;
case MeterAppState.MeterAppDownloadSuspicious:
dataGridRow.DefaultCellStyle = _styleVerificationRequired;
break;
case MeterAppState.MeterAppSuccessfulErased:
dataGridRow.DefaultCellStyle = _styleDownloadSucceeded;
break;
case MeterAppState.MeterAppSuccessfulUpdated:
dataGridRow.DefaultCellStyle = _styleDownloadSucceeded;
break;
case MeterAppState.MeterAppDownloadFailed:
dataGridRow.DefaultCellStyle = _styleDownloadFailed;
break;
case MeterAppState.MeterAppDownloadActive:
dataGridRow.DefaultCellStyle = _styleDownloadOngoing;
break;
default:
dataGridRow.DefaultCellStyle = _styleNotInstalled;
break;
}
}
}
#endregion --------------------------------------- Data Grid Controls -----------------------------------------
#region ------------------------------------------ Checks -----------------------------------------------------
/// <summary>
/// Loading update package ABC and description file ADF.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2020-Dec-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - Error messages exported.
/// </remarks>
/// <remarks date="2023-Feb-18" author="Thomas Wiedebusch">
/// - Removed information like region and size of fw update package from name of package,
/// - Added check for null of FwPackage info, binary content and description file.
/// </remarks>
/// <remarks date="2024-Apr-24" author="Thomas Wiedebusch">
/// - Update app installation status.
/// </remarks>
private void LoadUpdateFiles(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
if (_meterFwUpdate == null)
_meterFwUpdate = new MeterFwUpdate(_currentGenesis);
_binaryPackageLoaded = false;
try
{
if (_cordonelDeviceInfos == null
|| _cordonelFirmwarePackage?.FwPackageInfo == null
|| _cordonelFirmwarePackage.BinaryApplicationFiles == null
|| _cordonelFirmwarePackage.BinaryApplicationFiles.Count == 0
|| _cordonelFirmwarePackage.PackageDescriptionFile == null)
{
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrPackageFileInvalid);
_processState = errorExitState;
return;
}
foreach (var device in _cordonelDeviceInfos.Where(device =>
_currentGenesis.PcbId == device.PcbId))
{
if (_cordonelFirmwarePackage.FwPackageInfo.Name == device.RequiredFwRelease)
{
// here the firmware package is valid, encode content
var encoding = new ASCIIEncoding();
var packageFile =
encoding.GetString(_cordonelFirmwarePackage.PackageDescriptionFile.FileContent.ToArray());
// setup package description file in FwUpdate class
if (!_meterFwUpdate.LoadPackageFileFromText(packageFile))
{
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrPackageFileInvalid);
_processState = errorExitState;
return;
}
var fileApps = new List<FileApplications>();
foreach (var app in _cordonelFirmwarePackage.BinaryApplicationFiles.Where(f => f.FileName.EndsWith(".bin")))
{
var fileApplication = new FileApplications(app.FileName)
{
BinData = new List<Byte>(app.FileContent.ToList())
};
fileApps.Add(fileApplication);
}
if (_meterFwUpdate.LoadFileApps(fileApps))
{
// Check status of installed applications
_meterFwUpdate.AssignGenesis(_currentGenesis);
_meterFwUpdate.CompareAllMeterAndFileApps();
FillDataGridWithAllInfos();
_binaryPackageLoaded = true;
_processState = successExitState;
break;
}
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrUpdateInformationStatusFailed);
_processState = errorExitState;
}
break;
}
}
catch (Exception)
{
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrUpdateInformationStatusFailed);
_processState = errorExitState;
}
}
/// <summary>
/// Validate the version and the expiration date of the software.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Initial based on previous implementation of CheckUpdateEnabled().
/// </remarks>
/// <remarks date="2021-Feb-09" author="Thomas Wiedebusch">
/// - Duty Date implemented.
/// </remarks>
/// <remarks date="2021-Mar-02" author="Thomas Wiedebusch">
/// - Enable [Connect] and [Port Scan] if license is validated.
/// </remarks>
/// <remarks date="2021-Mar-22" author="Thomas Wiedebusch">
/// - Software license element naming adapted to DB content.
/// </remarks>
private void ValidateSoftware(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
if (_version == null || _softwareLicense?.Program == null)
{
_processState = errorExitState;
return;
}
var programName = Assembly.GetExecutingAssembly().GetName().Name;
if (_version.Major == _softwareLicense.Major &&
_version.Minor == _softwareLicense.Minor &&
_version.Build == _softwareLicense.Build &&
_softwareLicense.Program == programName &&
DateTime.Compare(_softwareLicense.ValidTo, DateTime.Now) >= 1)
{
_processState = successExitState;
SetDisableDownloadAndEnableConnect();
return;
}
_processState = errorExitState;
}
/// <summary>
/// State call to check the update capability.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - Initial based on previous implementation of CheckUpdateEnabled().
/// </remarks>
/// <remarks date="2021-Jan-14" author="Thomas Wiedebusch">
/// - Check metrology update capability.
/// </remarks>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Check region radio.
/// </remarks>
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
/// - Exported CheckPackageFileAndFileApps to CheckUpdateRequest to separate capability check from
/// applications are up-to-date.
/// </remarks>
private void CheckUpdateCapability(ProcessState successExitState,
ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
Invoke(new Action(() =>
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
if (!CoreRevisionValid())
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckCoreRevision;
return;
}
if (!MetrologyUpdateCapability())
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckMetrology;
return;
}
if (!RegionRadioUpdateCapability())
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckRegionRadio;
return;
}
if (!MeterSizeUpdateCapability())
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckMeterSize;
return;
}
// update capability is verified as good!
InfoProcessSuccess(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilitySucceeded);
_processState = successExitState;
}));
SetControlDownloadEnable();
}
/// <summary>
/// State call to check for FW-Update request.
/// </summary>
/// <param name="successExitState">update is needed</param>
/// <param name="breakExitState">skip update</param>
/// <param name="errorExitState">incompatible update in safe</param>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - Initial based on previous implementation of CheckUpdateEnabled().
/// </remarks>
/// <remarks date="2024-Apr-23..24" author="Thomas Wiedebusch">
/// - Check for communication issues to avoid wrong information on installed apps.
/// </remarks>
private void CheckUpdateRequest(ProcessState successExitState, ProcessState breakExitState,
ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
Invoke(new Action(() =>
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
// check if apps are marked as status unknown due to communication error
var allAppsDetected = AllAppsDetected();
if (_invokerProcessState == ProcessState.CheckUpdateRequest)
{
if (!CheckPackageFileAndFileApps())
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckUpdateFiles;
return;
}
if (!allAppsDetected)
{
_processState = errorExitState;
// overwrite the invoker state to create a message
_invokerProcessState = ProcessState.CheckCommunication;
return;
}
}
if (allAppsDetected)
{
if (!_currentGenesis.MeterAppListVersion.Any(f => f.Update) &&
!_currentGenesis.MeterAppListVersion.Any(f => f.Erase))
{
// Nothing has to be updated, the latest FW is installed from program handler
// it stops operation with break for FW-Update
_processState = breakExitState;
}
else
{
// an update needs to be executed
_processState = successExitState;
}
}
else
{
// Communication error and therefore unknown installation state
_processState = errorExitState;
}
}));
}
/// <summary>
/// Correct power overestimation at FW change from
/// <see cref="PowerCorrection.EmeaFwThresholdForPowCorr"/>,
/// <see cref="PowerCorrection.NaFNaFwThresholdForPowCorr"/>.
/// Precondition:
/// Meter registers have to be read in advance,
/// Meter files have to be read in advance.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2024-May-08" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void CorrectPowerOverestimation(ProcessState successExitState,
ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
if (_currentGenesis?.InstalledFwVersion == null ||
_cordonelFirmwarePackage?.FwPackageInfo?.Version == null)
{
InfoProcessFailed(null, Resources.StrPowerCorrectionFailed);
_processState = errorExitState;
return;
}
var powCorrFile = new MeterPowerCorrectionFile(_currentGenesis);
// CASE 1: Power correction has been executed in previous run:
// Check if file "1\\powcorr" is in meter
if (_readMeterFiles.Any(f => f.Contains(MeterPowerCorrectionFile.StrMeterPowCorrFileName)))
{
// read the powCorrFile
powCorrFile.ReadPowCorrMeterFile();
LogPowCorrContent(powCorrFile);
_processState = successExitState;
return;
}
// CASE 2: Power correction needed due to update of FW to corrected version regarding the
// power calculation:
// Check required versus installed FW versions and threshold version for power correction
if ((_currentGenesis.Region == "EMEA" &&
_cordonelFirmwarePackage.FwPackageInfo.Version >= PowerCorrection.EmeaFwThresholdForPowCorr &&
_currentGenesis.InstalledFwVersion < PowerCorrection.EmeaFwThresholdForPowCorr) ||
(_currentGenesis.Region == "NA" &&
_cordonelFirmwarePackage.FwPackageInfo.Version >= PowerCorrection.NaFNaFwThresholdForPowCorr &&
_currentGenesis.InstalledFwVersion < PowerCorrection.NaFNaFwThresholdForPowCorr))
{
var applyAlgorithm1 = true;
// CASE 2.1: Missing power correction settings from FW update package
if (_fwUpdatePowerCorrection == null)
{
applyAlgorithm1 = false;
//do something
_fwUpdatePowerCorrection = new PowerCorrection();
}
// calculate the power correction
if (applyAlgorithm1)
{
}
else
{
}
// correct the total used seconds
// fill all infos to meter power correction file
_fwUpdatePowerCorrection.ActualFwVersion = _currentGenesis.InstalledFwVersion;
_fwUpdatePowerCorrection.RequiredFwVersion = (UInt32?)_cordonelFirmwarePackage.FwPackageInfo.Version;
// store results to "1\\powcorr" in the meter
}
else
{
InfoProcessSuccess(null, Resources.StrPowerCorrectionNotNeeded);
_processState = successExitState;
return;
}
LogPowCorrContent(powCorrFile);
_processState = successExitState;
}
/// <summary>
/// Log the power correction file content.
/// </summary>
/// <param name="powCorrFile"></param>
/// <remarks date="2023-Nov-21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void LogPowCorrContent(MeterPowerCorrectionFile powCorrFile)
{
//TODO THW
var powerCorrectionInfo = new List<String>
{
$"{Resources.StrLifeTimeDrainedBattery} {0} ",
$"{Resources.StrLifeTimeRemainingYears} {0} " +
$"{Resources.StrLifeTimeYears}"
};
LogStringList(Resources.StrPowerCorrection, powerCorrectionInfo, Resources.StrPowerCorrectionSucceeded);
}
/// <summary>
/// Calculate the consumed power and remaining life time.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2023-Nov-21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void CalculateLifeTime(ProcessState successExitState,
ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var status = new GenesisStatus();
if (GenesisStatusHandler.BuildLifeTimeInformation(_currentGenesis, status))
{
var lifeTimeInfo = new List<String>
{
$"{Resources.StrLifeTimeDrainedBattery} {status.DrainedBatteryLoadPercent} %",
$"{Resources.StrLifeTimeRemainingYears} {status.RemainingLifeTimeYears} " +
$"{Resources.StrLifeTimeYears}"
};
LogStringList(Resources.StrLifeTimeCalculation, lifeTimeInfo, Resources.StrLifeTimeCalculationSucceeded);
_processState = successExitState;
}
else
{
InfoProcessFailed(null, Resources.StrLifeTimeCalculationFailed);
_processState = errorExitState;
}
}
/// <summary>
/// Core revision check
/// </summary>
/// <returns>true if core revision is valid</returns>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Jan-14" author="Thomas Wiedebusch">
/// - Moved success message to caller.
/// </remarks>
/// <remarks date="2021-Apr-15" author="Thomas Wiedebusch">
/// - Success message implemented.
/// </remarks>
/// <remarks date="2022-Dec-05" author="Thomas Wiedebusch">
/// - Core revision data type changed from string ti int32?.
/// </remarks>
private Boolean CoreRevisionValid()
{
// check the update capability
if (MeterFwUpdateCapability.CheckCoreRevision(_meterFwUpdate, _currentGenesis.CoreRevision))
{
InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityCoreSucceeded);
return true;
}
// set information of required and detected versions, check failed!
var coreLineList = new List<String>
{
@"Minimum: " + _meterFwUpdate.CoreRevisionMinimum / 100 + "." +
_meterFwUpdate.CoreRevisionMinimum % 100,
@"Maximum: " + _meterFwUpdate.CoreRevisionMaximum / 100 + "." + _meterFwUpdate.CoreRevisionMaximum % 100,
@"Cordonel: " + _currentGenesis.StrCoreRevision
};
LogStringList(Resources.StrCoreRevision, coreLineList,
errorMessage: Resources.StrCordonelUpdateCapabilityCoreFailed);
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityCoreFailed, false);
_processState = ProcessState.Error;
return false;
}
/// <summary>
/// Metrology update capability check
/// </summary>
/// <returns>true if core revision is valid</returns>
/// <remarks date="2021-Jan-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Apr-15" author="Thomas Wiedebusch">
/// - Success message implemented.
/// </remarks>
private Boolean MetrologyUpdateCapability()
{
// check the update capability
if (MeterFwUpdateCapability.CheckMetrology(_meterFwUpdate, _currentGenesis))
{
InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityMetrologySucceeded);
return true;
}
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityMetrologyFailed);
_processState = ProcessState.Error;
return false;
}
/// <summary>
/// Check that during detection every request returns a valid communication to avoid unpredictable information
/// of installed applications, on missing communication the installation status is undefined!
/// </summary>
/// <returns>true if during app detection every message returns a response</returns>
/// <remarks date="2024-Apr-23" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean AllAppsDetected()
{
// check the update capability
if (_currentGenesis.MeterAppListVersion.All(f => f.Status != MeterAppState.Unknown))
return true;
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelCommunicationFailed);
return false;
}
/// <summary>
/// Region and radio update capability check:
/// - Region (NA, EMEA, China),
/// - RadioFrequencyMhz (null, 433 MHz, 868 MHz),
/// If any information cannot be read from the meter, it will be assumed, that the FwUpdateBuilder operator
/// assembled a correct FwUpdateSafe. Otherwise old devices may be unable to update.
/// The FwPackage has to be checked in advance before calling this routine, as all these information is needed
/// for this update capability check.
/// </summary>
/// <returns>true all checks are approved and valid</returns>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2022-Apr-27" author="Thomas Wiedebusch">
/// - Starting with FW 1.2.* all frequencies will be supported, removed frequency check,
/// - Supported China as new region.
/// </remarks>
/// <remarks date="2023-Feb-18" author="Thomas Wiedebusch">
/// - Region, RadioFrequencyMhz, MeterSize taken from fw package info instead of package name,
/// - Meter sizes list taken into account.
/// </remarks>
/// <remarks date="2023-Feb-18" author="Thomas Wiedebusch">
/// - Meter sizes check exported to separate routine.
/// </remarks>
private Boolean RegionRadioUpdateCapability()
{
var retVal = true;
// extract the package information
var fwPackRegion = _cordonelFirmwarePackage.FwPackageInfo.Region;
var fwPackRadioFrequencyMhz = _cordonelFirmwarePackage.FwPackageInfo.RadioFrequencyMhz;
// region
if (!string.IsNullOrEmpty(fwPackRegion))
{
// accept region if identical, on undefined installed region or support for all regions
if (_currentGenesis.Region == fwPackRegion
|| Constants.StrWildcard == fwPackRegion
|| Constants.StrUnknown == _currentGenesis.Region)
{
InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityRegionSucceeded);
}
else
{
retVal = false;
// set information of required and detected regions radio, check failed!
var lineList = new List<String>
{
$"{Resources.StrFwPackageInfo} {fwPackRegion}",
$@"Cordonel: {_currentGenesis.Region}"
};
LogStringList(Resources.StrMessageSystemRegion, lineList,
errorMessage: Resources.StrCordonelUpdateCapabilityRegionFailed);
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityRegionFailed, false);
}
}
// if radio frequency is null the region is "NA" or the fw package serves all frequencies,
// if the radio frequency is explicit set, it has to be verified against the preset value in the Cordonel
if (fwPackRadioFrequencyMhz == null || fwPackRadioFrequencyMhz == _currentGenesis.RadioFrequencyMhz
|| _currentGenesis.RadioFrequencyMhz == null)
{
InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityRadioSucceeded);
}
else
{
retVal = false;
// set information of required and detected radio frequency check failed!
var lineList = new List<String>
{
Resources.StrFwPackageInfo + " " + fwPackRadioFrequencyMhz + " MHz",
@"Cordonel: " + _currentGenesis.RadioFrequencyMhz + " MHz"
};
LogStringList(Resources.StrMessageSystemRadio, lineList,
errorMessage: Resources.StrCordonelUpdateCapabilityRadioFailed);
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityRadioFailed, false);
}
if (!retVal)
_processState = ProcessState.Error;
return retVal;
}
/// <summary>
/// Meter size update capability check:
/// - MeterSize (DN40, DN50,..., DN300, US1_5, US2, US3,..., US12.
/// If any information cannot be read from the meter, it will be assumed, that the FwUpdateBuilder operator
/// assembled a correct FwUpdateSafe. Otherwise old devices may be unable to update.
/// The FwPackage has to be checked in advance before calling this routine, as all these information is needed
/// for this update capability check.
/// </summary>
/// <returns>true if meter size is approved</returns>
/// <remarks date="2023-Feb-23" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean MeterSizeUpdateCapability()
{
var retVal = true;
// extract the package information
var fwPackMeterSizes = _cordonelFirmwarePackage.FwPackageInfo.MeterSizes;
// meter size
if (fwPackMeterSizes != null && fwPackMeterSizes.Count > 0)
{
// check the update capability for meter size,
// if fw package supports all meter sizes or the meter size is unknown
if (fwPackMeterSizes.Any(size => size == _currentGenesis.MeterSize)
|| fwPackMeterSizes.Any(size => size == Constants.StrWildcard)
|| Constants.StrUnknown == _currentGenesis.MeterSize)
{
InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityMeterSizeSucceeded);
}
else
{
retVal = false;
// set information of required and detected regions check failed!
var msg = " ";
var cnt = fwPackMeterSizes.Count;
foreach (var size in fwPackMeterSizes)
{
msg += size;
if (--cnt > 0)
msg += ", ";
}
var lineList = new List<String>
{
Resources.StrFwPackageInfo + msg,
@"Cordonel: " + _currentGenesis.MeterSize
};
LogStringList(Resources.StrMessageMeterSize, lineList,
errorMessage: Resources.StrCordonelUpdateCapabilityRegionFailed);
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityMeterSizeFailed,
false);
}
}
if (!retVal)
_processState = ProcessState.Error;
return retVal;
}
/// <summary>
/// Compare the package information file and package content with installed applications
/// </summary>
/// <remarks date="2020-Nov-30" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Control view invoker added.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
/// <remarks date="2023-Feb-17" author="Thomas Wiedebusch">
/// - Name of fw package taken from fw package info.
/// </remarks>
private Boolean CheckPackageFileAndFileApps()
{
var fwPackageName = _cordonelFirmwarePackage.FwPackageInfo.Name;
if (string.IsNullOrEmpty(fwPackageName) || !_binaryPackageLoaded)
{
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrUpdatePackageInvalid);
return false;
}
InfoProcessSuccess(lblProductToInstall, Resources.StrProductToInstall + @" " + fwPackageName);
UiInvoker.ControlViewInvoker(lblProductToInstall, true);
if (_meterFwUpdate.ValidateFileAppsWithPackageFile())
{
InfoProcessSuccess(lblUpdateInformationStatus, Resources.StrUpdateInformationStatusSucceeded);
return true;
}
InfoProcessFailed(lblUpdateInformationStatus, Resources.StrUpdateInformationStatusFailed);
return false;
}
#endregion --------------------------------------- Checks -----------------------------------------------------
#region ------------------------------------------ User Interaction -------------------------------------------
/// <summary>
/// Establish connection to water-meter
/// Avoid port scan if meter has been detected.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Aug-22" author="Thomas Wiedebusch">
/// - _finalConnectAfterUpdate set to false as user forces a new connection to create a new report and switch
/// eventually to new device without exit the program.
/// </remarks>
private void BtnConnect_Click(Object sender, EventArgs e)
{
// restart for new device
_afterUpdateConnect = false;
if (_resetTimeMeasurement)
{
_startTime = DateTimeOffset.UtcNow;
_resetTimeMeasurement = false;
}
// if the Genesis/Cordonel has been already detected, the port scan can be bypassed
if (_currentGenesis != null && !string.IsNullOrEmpty(_currentGenesis.PcbId))
_processState = ProcessState.InitialConnect;
else
_processState = ProcessState.PortScan;
}
/// <summary>
/// Repeat port scan on changed interface.
/// This removed the current meter from lists
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void btnPortScan_Click(Object sender, EventArgs e)
{
// IMPORTANT: DisposeGenesis() is needed here even if it will be executed again in Connect():
// The init disposes the Genesis. If a new port is selected by removal and reinsert of the IrDA
// readout head, the port may have changed. This forces a new port scan as the Genesis is null.
DisposeGenesis();
BtnConnect_Click(this, null);
}
/// <summary>
/// User request to stop all operations
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void BtnStop_Click(Object sender, EventArgs e)
{
_processState = ProcessState.Stop;
}
/// <summary>
/// Automatic update of entire FW
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-24" author="Thomas Wiedebusch">
/// - Start of update procedure changed to read files to erase and restore.
/// </remarks>
private void BtnUpdateFw_Click(Object sender, EventArgs e)
{
if (_currentGenesis == null || _meterFwUpdate?.FileApps == null ||
_registerRestorer == null)
return;
_startTime = DateTimeOffset.UtcNow;
_resetTimeMeasurement = false;
_processState = ProcessState.ReadInfoEraseRestoreFiles;
}
/// <summary>
/// Open/Toggle history window
/// </summary>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void BtnHistory_Click(Object sender, EventArgs e)
{
var xPosition = Location.X + Size.Width;
var yPosition = Location.Y;
if (_frmHistory == null)
return;
if (_frmHistory.Visible)
{
_frmHistory.Hide();
btnHistory.Text = Resources.StrHistoryWindowShow;
}
else
{
_frmHistory.SetDesktopLocation(xPosition, yPosition);
_frmHistory.Show();
btnHistory.Text = Resources.StrHistoryWindowToggle;
}
}
/// <summary>
/// Release Cordonel display from"Idle" to normal operation
/// </summary>
/// <remarks date="2022-Nov-25" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void BtnReleaseDisplay_Click(Object sender, EventArgs e)
{
ReleaseDisplayAndSwitchLedOff();
}
#endregion --------------------------------------- User Interaction -------------------------------------------
#region ------------------------------------------ Tools ------------------------------------------------------
/// <summary>
/// Logging of password file read from meter.
/// </summary>
/// <remarks date="2023-Oct-11" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Oct-19" author="Thomas Wiedebusch">
/// - Output message changed to get colored error or success messages.
/// </remarks>
/// <remarks date="2023-Oct-27" author="Thomas Wiedebusch">
/// - Message for uninstalled password file moved from <see cref="ExecConnect"/>ExecConnect to here.
/// </remarks>
/// <remarks date="2023-Nov-09" author="Thomas Wiedebusch">
/// - Avoid comparison if password file is not in FW-Update safe.
/// </remarks>
private Boolean ReadLogAndCompareMeterPwdFile()
{
if (_currentGenesis == null)
return false;
_currentGenesis.ReLogin();
Boolean retVal;
try
{
// Read the meter password file
var meterFile = new MeterFile(_currentGenesis);
retVal = meterFile.ReadMeterFile(MeterPwdFile.StrPasswordFileName, out var hashedMeterPwdFile);
// Separate all hashed passwords for logging each in an individual line
var hashedPwdList = new List<String>();
if (retVal)
{
_passwordFileIsCorrupted = false;
for (var idx = 0; idx < hashedMeterPwdFile.Count; idx += MeterPwdDb.HashedPwdLength)
{
var hashedPwd = BitConverter.ToString(hashedMeterPwdFile.GetRange(idx,
MeterPwdDb.HashedPwdLength).ToArray());
hashedPwdList.Add(hashedPwd);
}
}
// Analyze the read out of meter password file
if (hashedMeterPwdFile == null || hashedMeterPwdFile.Count == 0)
{
// Log information about password file is not installed
InfoProcessFailed(null, Resources.StrPasswordFileNotInstalled);
_passwordFileIsCorrupted = true;
retVal = false;
}
// If the FW-Update safe does not contain a password file this cannot be compared
if (_fwUpdateSafePwdFile == null)
{
// If the FW-Update safe does not contain a password file just log the read out
if (!_passwordFileIsCorrupted)
{
LogStringList(Resources.StrMessageHashedPwdFile, hashedPwdList,
errorMessage: Resources.StrPasswordFileNotDelivered);
}
else
{
LogErrorText(Resources.StrPasswordFileNotDelivered);
}
}
else if (!_passwordFileIsCorrupted)
{
// Compare the installed password file with the password file from FW update safe
var pwdFile = new MeterPwdFile(_currentGenesis);
_passwordFileIsCorrupted = !pwdFile.VerifyMeterPwdFile(_fwUpdateSafePwdFile);
if (!_passwordFileIsCorrupted)
{
LogStringList(Resources.StrMessageHashedPwdFile, hashedPwdList,
Resources.StrPasswordFileCompareSucceeded);
}
else
{
// password file is suspicious
LogStringList(Resources.StrMessageHashedPwdFile, hashedPwdList,
errorMessage: Resources.StrPasswordFileCompareFailed);
}
}
}
catch (Exception)
{
LogErrorText(Resources.StrPasswordFileReadoutFailed);
retVal = false;
}
return retVal;
}
/// <summary>
/// Logging of LUT file read from meter and validate the CRC generated from raw data read from meter
/// and compared with meter lookup CRC set in meter.
/// </summary>
/// <remarks date="2023-Nov-13" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
/// - LUT file invalid as it cannot be read.
/// </remarks>
private Boolean ReadLogAndCompareMeterLutFile()
{
if (_currentGenesis == null)
return false;
_currentGenesis.ReLogin();
Boolean retVal;
try
{
// Read the meter LUT file
var meterFile = new MeterFile(_currentGenesis);
var meterLutFile = new MeterLutFile();
retVal = meterFile.ReadMeterFile(MeterLutFile.StrLutMeterFileName, out meterLutFile.BinData);
// Extract information from read meter LUT file
var lutLineList = new List<String>();
if (retVal)
{
// Validate the meter LUT file format
var lutFileFormatValid = meterLutFile.ValidateLutFormat();
var lutLine = $@"LUT Version: {meterLutFile.Version}";
lutLineList.Add(lutLine);
lutLine = $@"LUT {Resources.StrMessageMeterSize} " +
$@"{MeterSizeConverter.ConvertMeterSizeEnumToSizeName(meterLutFile.MeterSize)}";
lutLineList.Add(lutLine);
lutLine = $@"{Resources.StrMessageLutCrc} 0x{meterLutFile.FileCrc:X4}";
lutLineList.Add(lutLine);
if (lutFileFormatValid)
{
if (_fwUpdateSafeLutFile == null || _fwUpdateSafeLutFile.BinData.Count != meterLutFile.SizeLutFile)
{
LogStringList(Resources.StrMessageLutFile, lutLineList,
Resources.StrLutFileFromMeterValid);
}
else
{
for (var c = 0; c < meterLutFile.DataLength; c++)
{
// If one byte is different the comparison failed
if (_fwUpdateSafeLutFile.BinData[c] != meterLutFile.BinData[c])
{
LogStringList(Resources.StrMessageLutFile, lutLineList,
errorMessage: Resources.StrLutFileCompareFailed);
retVal = false;
}
}
// Here all bytes are identical
if (!retVal)
{
LogStringList(Resources.StrMessageLutFile, lutLineList,
Resources.StrLutFileCompareSucceeded);
}
}
}
// Here the LUT file format is invalid
else
{
LogStringList(Resources.StrMessageLutFile, lutLineList,
errorMessage: Resources.StrLutFileFromMeterInvalid);
retVal = false;
}
}
// Here the LUT file format is invalid
else
{
LogStringList(Resources.StrMessageLutFile, lutLineList,
errorMessage: Resources.StrLutFileFromMeterInvalid);
}
}
catch (Exception)
{
LogErrorText(Resources.StrLutFileReadoutFailed);
retVal = false;
}
return retVal;
}
/// <summary>
/// Set data and time.
/// </summary>
/// <remarks date="2023-Oct-09" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2024-Feb-08" author="Thomas Wiedebusch">
/// - Avoid to setup IRDA_MfgDate as this is the manufacturing date and not the FW-Update date!
/// </remarks>
private Boolean SetDateTime()
{
if (_currentGenesis == null)
return false;
_currentGenesis.ReLogin();
Boolean retVal;
try
{
// get the time in UTC to program the time for the Cordonel in seconds since 01. Jan 2000 00:00:00 UTC
var teaTime = new TimeT();
// build byte array
var rawSeconds = RegisterConverter.ValueToByteArray(teaTime.UtcNowToSecondsSince2000);
retVal = _currentGenesis.WriteRegister("SYSTEM_CalendarSeconds", rawSeconds);
LogSuccessText(Resources.StrDateTimeSetupSucceeded + " " + teaTime);
}
catch (Exception)
{
LogErrorText(Resources.StrDateTimeSetupFailed);
retVal = false;
}
return retVal;
}
/// <summary>
/// Activate the radio in customer mode with logging of success or failed.
/// </summary>
/// <remarks date="2023-Feb-18" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Feb-20" author="Thomas Wiedebusch">
/// - Reworked with new information from Joerg Lachenmayer:
/// 1. Write 0xff,
/// 2. logout,
/// 3. Wait 1000 ms,
/// 4. login
/// 5. read if not 0x01.
/// </remarks>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - Handle SystemState 0 = OFF
/// </remarks>
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
/// - Store configuration of radio settings
/// </remarks>
private Boolean SetRadioToCustomerMode()
{
if (_currentGenesis == null)
return false;
_currentGenesis.ReLogin();
Boolean retVal;
try
{
// read initially if radio is NOT in production mode (0x01), where it sends unencrypted radio telegrams
var radioSystemState =
RegisterConverter.ByteArrayToValue<Int32>(_currentGenesis.ReadRegister(Register.Sensusradio.SystemState));
if (radioSystemState != 0x01 && radioSystemState != 0)
{
LogSuccessText(Resources.StrRadioAlreadyActive);
retVal = true;
}
else // switch radio active to customer mode with encrypted communication
{
// write register to 0xFF means radio activation with programmed radio key to customer mode,
// SENSUSRADIO needs to store all information to CONFIGEXCHANGE, therefore a logout has to be forced
_currentGenesis.WriteRegister(Register.Sensusradio.SystemState, 0xFF);
_currentGenesis.Logout();
// give the radio app the time to store and restart in customer mode
Thread.Sleep(1000);
// login for read back of new mode which shouldn't be 0x01
_currentGenesis.ReLogin();
// read back if radio is no longer in production mode
if (0x01 != RegisterConverter.ByteArrayToValue<Int32>(_currentGenesis.ReadRegister(Register.Sensusradio.SystemState)))
{
_currentGenesis.WriteRegister(Register.Sensusradio.StoreConfiguration, 1);
LogSuccessText(Resources.StrRadioActivationSuccess);
retVal = true;
}
else
{
LogErrorText(Resources.StrRadioActivationFailed);
retVal = false;
}
} // initially radio active check
}
catch (Exception)
{
LogErrorText(Resources.StrRadioActivationFailed);
retVal = false;
}
return retVal;
}
/// <summary>
/// The pulse module interrupts and disturbs the IrDA communication. As quick fix the pulse module can be switched off,
/// as soon as a communication could be established. As the pulse output will only be switched on again, if a pulse
/// module is attached to the device, this pulse mode can be switched active instantaneously after deactivation.
/// </summary>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - Initial, sequence of deactivation based on code from Roland Drabesch.
/// </remarks>
private Boolean DeactivatePulseMode()
{
var retVal = false;
try
{
if (!_currentGenesis.IsLoggedOn)
_currentGenesis.ReLogin();
// create path and name for register setup reminder for this PcbId
_pathNameCordonelRegisterRestoreSettingsFile = Path.Combine(FwUpdateConfig.ReportedPath,
$"{_currentGenesis.PcbId}_" + FwUpdateConfig.RestoreSettingsFileName);
var pulseMode =
RegisterConverter.ByteArrayToValue<Byte>(_currentGenesis.ReadRegister(Register.Metrologyasst.PulseMode));
// if pulse mode is already set to 0 (OFF) this could be initial setup or a indication of a crashed update
if (pulseMode == 0)
{
LogText(Resources.StrPulseModeInactive);
// check if a file exists with settings from a previous update
if (File.Exists(_pathNameCordonelRegisterRestoreSettingsFile))
{
var fileData = File.ReadAllText(_pathNameCordonelRegisterRestoreSettingsFile);
_pulseModeRegistersRestore = JsonConvert.DeserializeObject<Dictionary<String, Byte[]>>(fileData);
}
retVal = true;
}
//if retVal is true, the pulse mode is switched OFF, no further action is required
if (!retVal)
{
// safe the pulse setting
_pulseModeRegistersRestore.Add(Register.Metrologyasst.PulseMode, new[] { pulseMode });
// Create new file as for report only one version is allowed
var serializedData = JsonConvert.SerializeObject(_pulseModeRegistersRestore);
var asciiStream = Encoding.UTF8.GetBytes(serializedData);
File.WriteAllBytes(_pathNameCordonelRegisterRestoreSettingsFile, asciiStream);
// write pulse mode temporary to 0 (OFF) and wait for result
retVal = _currentGenesis.WriteRegister(Register.Metrologyasst.PulseMode, 0);
LogText(Resources.StrPulseModeTemporaryDeactivated);
}
}
catch (Exception)
{
retVal = false;
}
return retVal;
}
/// <summary>
/// Restore all listed pulse mode registers.
/// </summary>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private Boolean RestorePulseMode()
{
var retVal = true;
try
{
_currentGenesis.ReLogin();
foreach (var item in _pulseModeRegistersRestore)
{
retVal &= _currentGenesis.WriteRegister(item.Key, item.Value);
}
retVal &= _currentGenesis.WriteRegister(Register.Metrologyasst.StoreConfiguration, 1);
if (retVal && _pulseModeRegistersRestore.Any(x => x.Key == Register.Metrologyasst.PulseMode))
LogText(Resources.StrPulseModeRestored);
}
catch (Exception)
{
retVal = false;
}
return retVal;
}
/// <summary>
/// Store configurations of all applications with logging of success or failed.
/// </summary>
/// <remarks date="2023-Feb-17" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean StoreConfigurations()
{
if (_currentGenesis == null)
return false;
_currentGenesis.ReLogin();
Boolean retVal;
Thread.Sleep(1000);
if (_currentGenesis.StoreAllConfigurations())
{
LogSuccessText(Resources.StrStoreConfigSuccess);
retVal = true;
}
else
{
LogErrorText(Resources.StrStoreConfigFailed);
retVal = false;
}
return retVal;
}
/// <summary>
/// Release display and switch measurement LED off.
/// </summary>
/// <remarks date="2022-Apr-27" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2022-Jul-22" author="Thomas Wiedebusch">
/// - Store configuration genesisflow after display release. This is essential if a reboot will occur
/// to have the correct display of the accumulated volume and flow rate!
/// </remarks>
/// <remarks date="2023-Oct-15" author="Thomas Wiedebusch">
/// - Additional message of LED off
/// </remarks>
private Boolean ReleaseDisplayAndSwitchLedOff()
{
if (_currentGenesis == null)
return false;
var retVal = _currentGenesis.ReLogin();
// Set LCD DISPLAY from IdLE and 9999 back to operation, this may be necessary if the update has been
// interrupted by a program stop due to a power loss or any unpredictable event.
LogText(_currentGenesis.WriteRegister(Register.Genesisflow.TriggerIdle, 0)
? Resources.StrLedOffSucceeded
: Resources.StrLedOffFailed);
// deactivate LED, not needed to get measurements during the update or on repeated start after system
// failure. This overwrites the settings in _meterFwUpdate.
LogText(_currentGenesis.WriteRegister(Register.Genesisflow.LedMode, 0)
? Resources.StrReleaseDisplaySucceeded
: Resources.StrReleaseDisplayFailed);
// safe all settings for GENESISFLOW application
retVal &= _currentGenesis.WriteRegister(Register.Genesisflow.StoreConfiguration, 1);
Thread.Sleep(2000);
return retVal;
}
/// <summary>
/// Store all made settings and release meter to normal operation.
/// </summary>
/// <remarks date="2023-Oct-7" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean ReleaseMeterToNormalOperation()
{
var retVal = ReleaseDisplayAndSwitchLedOff();
retVal &= RestorePulseMode();
SetOverallProgressDisplayOff();
SetDisableDownloadAndEnableConnect();
return retVal;
}
/// <summary>
/// Logging of string lists.
/// </summary>
/// <param name="strHeader">Head line for comment</param>
/// <param name="strings">string list</param>
/// <param name="successMessage">optional message if process succeeded</param>
/// <param name="errorMessage">optional message if process failed</param>
/// <remarks date="2023-Oct-19" author="Thomas Wiedebusch">
/// - Output message changed to get colored error or success messages.
/// </remarks>
/// <remarks date="2023-Oct-24" author="Thomas Wiedebusch">
/// - LogErrorText.
/// </remarks>
private void LogStringList(String strHeader, IEnumerable<String> strings, String successMessage = null,
String errorMessage = null)
{
LogText(strHeader);
foreach (var text in strings)
{
LogText(text);
}
if (!string.IsNullOrEmpty(successMessage))
LogSuccessText(successMessage);
if (!string.IsNullOrEmpty(errorMessage))
LogErrorText(errorMessage);
}
/// <summary>
/// Logging of installed meter firmware and meter information.
/// </summary>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Added application list file handling as json file: One file with all new installed versions.
/// </remarks>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Added region and radio.
/// </remarks>
/// <remarks date="2022-Dec-05" author="Thomas Wiedebusch">
/// - Core revision data type changed from string ti int32?.
/// </remarks>
/// <remarks date="2023-Aug-15" author="Thomas Wiedebusch">
/// - LUT CRC and MeterSize added.
/// </remarks>
/// <remarks date="2023-Aug-31" author="Thomas Wiedebusch">
/// - FW version added.
/// </remarks>
private void LogInstalledMeterFw()
{
if (_currentGenesis == null)
return;
LogText($"PCB ID: {_currentGenesis.PcbId}");
LogText($"{Resources.StrMessageCustomerSerialNumber} {_customerSerialNumber}");
LogText($"FW Version: {_currentGenesis.FwVersion}");
LogText($"{Resources.StrMessageSystemCoreRevision} {_currentGenesis.StrCoreRevision}");
LogText($"{Resources.StrMessageSystemRegion} {_currentGenesis.Region}");
LogText($"{Resources.StrMessageSystemRadio} {_currentGenesis.RadioFrequencyMhz}");
LogText($"{Resources.StrMessageMeterSize} {_currentGenesis.MeterSize}");
if (!string.IsNullOrEmpty(_currentGenesis.LutCrc))
LogText($"{Resources.StrMessageLutCrc} {_currentGenesis.LutCrc}");
LogText(Resources.StrMessageInstalledApp);
var appList = new List<CordonelAppVersion>();
// the core has the -1 as identifier and is not updateable
var coreVersion = new CordonelAppVersion(-1, _currentGenesis.StrCoreRevision, false);
appList.Add(coreVersion);
foreach (var app in _currentGenesis.MeterAppListVersion)
{
var versionString = app.IsInstalled ? $"V: {app.StrVersion} - CRC: 0x{app.Crc:X4}" :
Resources.StrMessageAppNotInstalled;
if (app.Status == MeterAppState.Unknown)
versionString = Resources.StrCordonelCommunicationFailed;
LogText($"{Resources.StrTableAppId}: 0x{app.AppId:X2} - {versionString} - " +
$"{Resources.StrTableAppName}: {app.AppName}");
if (app.IsInstalled)
{
var appVersion = new CordonelAppVersion(app.AppId, app.StrVersion);
// Copy metrology update permission
if (app.AppName == MeterFwUpdateCapability.MetrologyName &&
_currentGenesis.MetrologyUpgradePermission != MeterFwUpdateCapability.MetrologyUpgradePermitted)
{
appVersion.IsUpdateable = false;
}
appList.Add(appVersion);
}
}
LogText(StrSeparator);
if (string.IsNullOrEmpty(_pathNameCordonelAppVersionFile))
return;
try
{
// Create new file as for report only one version is allowed
var serializedData = JsonConvert.SerializeObject(appList);
var asciiStream = Encoding.UTF8.GetBytes(serializedData);
File.WriteAllBytes(_pathNameCordonelAppVersionFile, asciiStream);
}
catch (Exception e)
{
MessageBoxShow(e.Message, Resources.StrTestReportFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Take the process state from a sorted list of sequenced processes.
/// </summary>
/// <remarks date="2023-Nov-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private ProcessState GetNextProcessState(ProcessState invokerProcessState,
IReadOnlyList<ProcessState> processSateSequence)
{
// The last entry is an end of list marker
for (var c = 0; c < processSateSequence.Count - 1; c++)
{
if (invokerProcessState == processSateSequence[c])
return processSateSequence[c + 1];
}
// If the process state is not in the list
_invokerProcessState = ProcessState.EndOfList;
return ProcessState.Error;
}
/// <summary>
/// Common message window.
/// </summary>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Control view invoker added.
/// </remarks>
/// <remarks date="2021-Jan-29" author="Thomas Wiedebusch">
/// - Force main form being TopMost to see the message box on top.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
/// <remarks date="2021-Mar-29" author="Thomas Wiedebusch">
/// - Forcing message box being modal and on top.
/// </remarks>
/// <remarks date="2023-Nov-14" author="Thomas Wiedebusch">
/// - Return user selection.
/// </remarks>
private DialogResult MessageBoxShow(String text, String caption, MessageBoxButtons buttons,
MessageBoxIcon icon)
{
SetOverallProgressDisplayOff();
SetActualProgressDisplayOff();
UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed, visible: false);
return MessageBox.Show(text, caption, buttons, icon, MessageBoxDefaultButton.Button1,
MessageBoxOptions.ServiceNotification);
}
/// <summary>
/// Output exclusively to user update remarks text window.
/// </summary>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Color added.
/// </remarks>
private void LogText(String txtHistory)
{
InfoWindowColoredText(txtHistory, ColorDefault);
_logger.Info(txtHistory);
ReportText(txtHistory);
}
/// <summary>
/// Output exclusively to user update remarks text window.
/// </summary>
private void LogErrorText(String txtHistory)
{
InfoWindowColoredText(txtHistory, ColorProcessFailed);
_logger.Info(txtHistory);
ReportText(txtHistory);
LogText(StrSeparator);
}
/// <summary>
/// Output exclusively to user update remarks text window.
/// </summary>
private void LogWarningText(String txtHistory)
{
InfoWindowColoredText(txtHistory, ColorWarning);
_logger.Info(txtHistory);
ReportText(txtHistory);
LogText(StrSeparator);
}
/// <summary>
/// Output exclusively to user update remarks text window.
/// </summary>
private void LogSuccessText(String txtHistory)
{
InfoWindowColoredText(txtHistory, ColorSuccess);
_logger.Info(txtHistory);
ReportText(txtHistory);
LogText(StrSeparator);
}
/// <summary>
/// Output exclusively to user update remarks text window.
/// </summary>
private void InfoWindowColoredText(String txtHistory, Color color)
{
if (_frmHistory?.rtbHistory == null)
return;
Invoke(new Action(() =>
{
_frmHistory.rtbHistory.SuspendLayout();
_frmHistory.rtbHistory.SelectionStart = _frmHistory.rtbHistory.Text.Length;
_frmHistory.rtbHistory.SelectionLength = 0;
_frmHistory.rtbHistory.SelectionColor = color;
_frmHistory.rtbHistory.AppendText($"{txtHistory}{Environment.NewLine}");
_frmHistory.rtbHistory.SelectionColor = _frmHistory.rtbHistory.ForeColor;
_frmHistory.rtbHistory.ScrollToCaret();
_frmHistory.rtbHistory.ResumeLayout();
}));
}
/// <summary>
/// Output to user update remarks text window and report file.
/// </summary>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Additional use for report file.
/// </remarks>
private void ReportText(String txtHistory)
{
if (string.IsNullOrEmpty(_pathNameReportFile))
return;
try
{
// Create new file or add to existing file
if (File.Exists(_pathNameReportFile))
{
File.AppendAllText(_pathNameReportFile, txtHistory + Environment.NewLine);
}
else
{
File.WriteAllText(_pathNameReportFile, txtHistory + Environment.NewLine);
}
}
catch (Exception e)
{
MessageBoxShow(e.Message, Resources.StrTestReportFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Neutral information to label, user text box and logger.
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void InfoStatusUnknown(Control label, String msg, Boolean userTextOutput = false)
{
if (userTextOutput)
LogText(msg);
UiInvoker.ControlInvoker(label, ColorUnknownStatus, msg);
}
/// <summary>
/// Success information to label, user text box and logger.
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void InfoProcessActive(Control label, String msg, Boolean log = true)
{
if (log)
{
LogText(msg);
}
UiInvoker.ControlInvoker(label, ColorOngoingProcess, msg);
}
/// <summary>
/// Success information to label, user text box and logger.
/// </summary>
private void InfoProcessSuccess(Control label, String msg, Boolean log = true)
{
if (log)
{
LogSuccessText(msg);
}
UiInvoker.ControlInvoker(label, ColorSuccess, $@"{SuccessSign} {msg}");
}
/// <summary>
/// Success information to label, user text box and logger.
/// </summary>
private void InfoProcessWarning(Control label, String msg, Boolean log = true)
{
if (log)
{
LogWarningText(msg);
}
UiInvoker.ControlInvoker(label, ColorWarning, $@"{WarningSign} {msg}");
}
/// <summary>
/// Failed information to label, user text box and logger.
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void InfoProcessFailed(Control label, String msg, Boolean log = true)
{
if (log)
{
LogErrorText(msg);
}
UiInvoker.ControlInvoker(label, ColorProcessFailed, $@"{FailedSign} {msg}");
}
/// <summary>
/// Action on FW-Update success
/// </summary>
/// <remarks date="2020-Dec-18" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Control invoker implemented.
/// </remarks>
private void FwUpdateSuccessMessage()
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
InfoProcessSuccess(lblFirmwareUpdate, Resources.StrFirmwareUpdateSucceeded);
SetDisableDownloadAndEnableConnect();
InfoProcessSuccess(lblCordonelUpdateCapability, Resources.StrFwUpToDateMessage);
MessageBoxShow(Resources.StrFwUpToDateMessage, "Info", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
/// <summary>
/// Common routine to enable download last control gets focus
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetControlDownloadEnable()
{
UiInvoker.ControlEnableInvoker(grpLanguageSelection, true);
UiInvoker.ControlEnableInvoker(btnStop, false);
UiInvoker.ControlEnableInvoker(btnReleaseDisplay, true);
UiInvoker.ControlEnableInvoker(btnPortScan, true);
UiInvoker.ControlEnableInvoker(btnConnect, true);
// set focus
UiInvoker.ControlEnableInvoker(btnUpdateFw, true);
}
/// <summary>
/// Common routine to lock download last control gets focus
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
/// <remarks date="2023-Feb-22" author="Thomas Wiedebusch">
/// - Enable "release Display" if Cordonel is connected
/// </remarks>
private void SetDisableDownloadAndEnableConnect()
{
UiInvoker.ControlEnableInvoker(grpLanguageSelection, true);
UiInvoker.ControlEnableInvoker(btnStop, false);
// if the FwVersion is set, the password is valid and a login was performed in advance to read it
if (_currentGenesis != null
&& !string.IsNullOrEmpty(_currentGenesis.PcbId)
&& !string.IsNullOrEmpty(_currentGenesis.FwVersion))
{
UiInvoker.ControlEnableInvoker(btnReleaseDisplay, true);
}
else
{
UiInvoker.ControlEnableInvoker(btnReleaseDisplay, false);
}
UiInvoker.ControlEnableInvoker(btnPortScan, true);
UiInvoker.ControlEnableInvoker(btnUpdateFw, false);
// set focus
UiInvoker.ControlEnableInvoker(btnConnect, true);
}
/// <summary>
/// Common routine to set controls during register access with water meter
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetControlsAllButtonsDisabled()
{
UiInvoker.ControlEnableInvoker(grpLanguageSelection, false);
UiInvoker.ControlEnableInvoker(btnConnect, false);
UiInvoker.ControlEnableInvoker(btnUpdateFw, false);
UiInvoker.ControlEnableInvoker(btnPortScan, false);
UiInvoker.ControlEnableInvoker(btnStop, false);
UiInvoker.ControlEnableInvoker(btnReleaseDisplay, false);
}
/// <summary>
/// Common routine to set controls during communication with water meter
/// last control gets focus
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetControlsCommunicationActive()
{
UiInvoker.ControlEnableInvoker(grpLanguageSelection, false);
UiInvoker.ControlEnableInvoker(btnConnect, false);
UiInvoker.ControlEnableInvoker(btnUpdateFw, false);
UiInvoker.ControlEnableInvoker(btnPortScan, false);
UiInvoker.ControlEnableInvoker(btnReleaseDisplay, false);
// set focus
UiInvoker.ControlEnableInvoker(btnStop, true);
}
/// <summary>
/// Common routine to activate overall process bar and text
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetOverallProgressDisplayOn(String startText = "")
{
_timerDisplayOn = true;
_progressBarValueCounter = 0;
UiInvoker.ProgressBarInvoker(barOverallProgressUpdate);
UiInvoker.ControlInvoker(lblOverallProcess, ColorDefault, startText);
}
/// <summary>
/// Common routine to hide overall process bar and text.
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetOverallProgressDisplayOff()
{
_timerDisplayOn = false;
_progressBarValueCounter = 0;
UiInvoker.ControlViewInvoker(barOverallProgressUpdate, false);
UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed, visible: false);
UiInvoker.ControlViewInvoker(lblOverallProcess, false);
}
/// <summary>
/// Common routine to activate actual process bar and text
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetActualProgressDisplayOn(String startText = "")
{
UiInvoker.ProgressBarInvoker(barActualProgressUpdate);
UiInvoker.ControlInvoker(lblActualProcess, ColorDefault, startText);
}
/// <summary>
/// Common routine to hide actual process bar and text
/// </summary>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void SetActualProgressDisplayOff()
{
UiInvoker.ControlViewInvoker(barActualProgressUpdate, false);
UiInvoker.ControlViewInvoker(lblActualProcess, false);
}
#endregion --------------------------------------- Tools ------------------------------------------------------
#region ------------------------------------------ Common Process Routines ------------------------------------
/// <summary>
/// Common routine to convert objects to process states.
/// Objects are sorted:
/// 1. successState
/// 2. errorState
/// 3. breakState
/// </summary>
/// <param name="objects">input of object array</param>
/// <param name="successState">success state, default set to idle</param>
/// <param name="errorState">error state, default set to error</param>
/// <param name="breakState">break state, default set to stop</param>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private static void ObjectsToProcessStates(IReadOnlyList<Object> objects, out ProcessState successState,
out ProcessState errorState, out ProcessState breakState)
{
// cast objects to process states sorted as success, error, break
successState = ProcessState.Idle;
errorState = ProcessState.Error;
breakState = ProcessState.Stop;
if (objects == null)
return;
if (objects.Count > 0 && objects[0] != null)
successState = (ProcessState)objects[0];
if (objects.Count > 1 && objects[1] != null)
errorState = (ProcessState)objects[1];
if (objects.Count > 2 && objects[2] != null)
breakState = (ProcessState)objects[2];
}
/// <summary>
/// Error processes.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - Error message dispatcher.
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Additional messages.
/// </remarks>
/// <remarks date="2021-Jan-14" author="Thomas Wiedebusch">
/// - Additional messages.
/// </remarks>
/// <remarks date="2021-Jan-29" author="Thomas Wiedebusch">
/// - Password file restore added.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Dispose meter at stop during port scan to avoid lock of port.
/// </remarks>
/// <remarks date="2021-Mar-03" author="Thomas Wiedebusch">
/// - Disable download on selected errors instead of all.
/// </remarks>
/// <remarks date="2021-Mar-20" author="Thomas Wiedebusch">
/// - Enable download on previously failed update procedure as this has been check in advance.
/// </remarks>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Region radio message.
/// </remarks>
/// <remarks date="2023-Feb-22" author="Thomas Wiedebusch">
/// - Meter size message.
/// </remarks>
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
/// - Meter size message.
/// </remarks>
/// <remarks date="2023-Nov-10" author="Thomas Wiedebusch">
/// - Meter LUT message.
/// </remarks>
/// <remarks date="2023-Nov-15..16" author="Thomas Wiedebusch">
/// - Error state decides what to do next.
/// </remarks>
/// <remarks date="2023-Nov-29" author="Thomas Wiedebusch">
/// - If LUT restore generates an error all processes have to be stopped as the meter is NOT functional.
/// </remarks>
/// <remarks date="2024-Apr-24" author="Thomas Wiedebusch">
/// - COmmunication error on app detection.
/// </remarks>
private void ErrorProcesses()
{
SetOverallProgressDisplayOff();
SetActualProgressDisplayOff();
_resetTimeMeasurement = true;
// take the invoker of the error state to generate the message and / or popup window
switch (_invokerProcessState)
{
// Port scan and Cordonel detection, this is the start of the connection and check sequence
case ProcessState.PortScan:
var msg = Resources.StrRequestPortFailed;
ErrorProcessCommon(msg);
// If the port scan failed there is nothing to do than manually pressing connect again
_processState = ProcessState.Idle;
break;
case ProcessState.InitialConnect:
msg = Resources.StrCordonelAuthenticationFailed;
ErrorProcessCommon(msg);
// If initial connect failed it makes no sense to go ahead
_processState = ProcessState.Idle;
break;
case ProcessState.InitialReadRegisters:
msg = Resources.StrRegisterReadoutFailed;
ErrorProcessCommon(msg);
// Even if this state failed try to maintain the next steps of the sequence
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareAndCheckMeter);
break;
case ProcessState.InitialReadMeterFiles:
msg = Resources.StrFileReadoutFailed;
ErrorProcessCommon(msg);
// Even if this state failed try to maintain the next steps of the sequence
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareAndCheckMeter);
break;
case ProcessState.ReadInfoEraseRestoreFiles:
msg = Resources.StrFileEraseNotDefined;
ErrorProcessCommon(msg);
// Even if this state failed try to maintain the next steps of the sequence
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareAndCheckMeter);
break;
case ProcessState.LoadUpdateFiles:
msg = Resources.StrUpdateInformationStatusFailed;
ErrorProcessCommon(msg);
// Even if this state failed try to maintain the next steps of the sequence
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareAndCheckMeter);
break;
// This is the prepare meter release sequence which should be continued even if one step failed
case ProcessState.RestoreMeterFiles:
msg = Resources.StrFileRestoreFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.RestoreLutFile:
//msg = Resources.StrLutFileRestoreFailed;
msg = Resources.StrLutFileCompareFailed;
ErrorProcessCommon(msg);
// If LUT file is invalid all processes have to be stopped as the meter is NOT functional.
_processState = ProcessState.Idle;
break;
case ProcessState.RecoverRegisters:
msg = Resources.StrRegisterRecoveryFailed;
ErrorProcessCommon(msg);
// Even if this state failed try to maintain the next steps of the sequence
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.FinalReadRegisters:
msg = Resources.StrRegisterReadoutFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.CompareRegisters:
msg = Resources.StrRegisterCompareFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.RestorePasswordFile:
msg = Resources.StrPasswordFileRestoreFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.VerifyMeterFiles:
msg = Resources.StrFileRestoreFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.ReadEngineeringLogs:
msg = Resources.StrEngineeringLogsReadFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
case ProcessState.CalculateLifeTime:
msg = Resources.StrLifeTimeCalculationFailed;
ErrorProcessCommon(msg);
_processState = GetNextProcessState(_invokerProcessState, _stateSequencePrepareMeterRelease);
break;
// This is the check sequence for update capability
case ProcessState.CheckFwUpdateSuccess:
msg = Resources.StrFirmwareUpdateFailed;
ErrorProcessCommon(msg);
InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrFwUpdateFailed);
_processState = ProcessState.ReleaseMeterToNormalOperation;
break;
case ProcessState.CheckCoreRevision:
msg = Resources.StrCordonelUpdateCapabilityCoreFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
case ProcessState.CheckMetrology:
msg = Resources.StrCordonelUpdateCapabilityMetrologyFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
case ProcessState.CheckRegionRadio:
msg = Resources.StrCordonelUpdateCapabilityRegionFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
case ProcessState.CheckMeterSize:
msg = Resources.StrCordonelUpdateCapabilityMeterSizeFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
case ProcessState.CheckUpdateFiles:
msg = Resources.StrUpdateInformationStatusFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
case ProcessState.CheckCommunication:
msg = Resources.StrCordonelCommunicationFailed;
ErrorProcessCommon(msg);
_processState = ProcessState.PrepareMeterRelease;
break;
// This is the FW-Update process
case ProcessState.FirmwareUpdate:
var requiredUpdates = _meterFwUpdate?.GetFailedFileApps();
msg = Resources.StrFirmwareUpdateFailed;
if (requiredUpdates != null && requiredUpdates.Count > 0)
{
msg += $"\n{Resources.StrFailedToUpdateApp}\n";
FileApplications lastFile = null;
foreach (var file in requiredUpdates)
{
if (lastFile == null || lastFile.AppId != file.AppId)
{
msg += $"{file.AppName}\n";
}
lastFile = file;
}
}
//TODO THW initialize window with retry button or cancel, on cancel prepare meter release
ErrorProcessCommon(msg);
// A retry of the download can be done because all checks passed in advance!
SetControlDownloadEnable();
_processState = ProcessState.Idle;
break;
// Reboot to enable new FW automatically executed by the FW-Update process on trigger upgrade command
case ProcessState.Reboot:
msg = Resources.StrRebootFailed;
//TODO THW initialize window with retry button or cancel, on cancel prepare meter release
ErrorProcessCommon(msg);
_processState = ProcessState.Idle;
break;
// Validation of this SW version and due date
case ProcessState.ValidateSoftware:
MessageBoxShow(Resources.StrSoftwareVersionExpired, Resources.StrMessageWindowFailed,
MessageBoxButtons.OK, MessageBoxIcon.Error);
// Close the form
Invoke(new Action(Close));
break;
}
}
/// <summary>
/// Common routine for error process with:
/// - Disable download control and enable connect,
/// - Logging to log-file,
/// - Add error message to error collection list,
/// - Message output window with user selection feedback.
/// </summary>
/// <param name="msg">message to display and log</param>
/// <param name="buttons"></param>
/// <param name="icon"></param>
/// <returns>user selection of message window</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private DialogResult ErrorProcessCommon(String msg, MessageBoxButtons buttons = MessageBoxButtons.OK,
MessageBoxIcon icon = MessageBoxIcon.Asterisk)
{
SetDisableDownloadAndEnableConnect();
_logger.Error($"Slot:{Slot} - {msg}");
_errorCollectionMessages?.Add(msg);
return MessageBoxShow(msg, Resources.StrMessageWindowFailed, buttons, icon);
}
/// <summary>
/// Stop all ongoing processes.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-21" author="Thomas Wiedebusch">
/// - Break message dispatcher.
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Check update capability state required on exit.
/// </remarks>
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
/// - Switch depending on process to create correct message.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Dispose meter at stop during port scan to avoid lock of port.
/// </remarks>
/// <remarks date="2021-Mar-18" author="Thomas Wiedebusch">
/// - Enable [Connect] and [Port Scan],
/// - _processState to idle.
/// </remarks>
private void StopProcesses()
{
InfoProcessFailed(null, Resources.StrUserStop);
SetOverallProgressDisplayOff();
SetActualProgressDisplayOff();
SetDisableDownloadAndEnableConnect();
_resetTimeMeasurement = true;
if (_serialPortScanner != null)
_serialPortScanner.StopPortScan = true;
if (_meterFwUpdate != null)
_meterFwUpdate.StopUpdateProcess = true;
if (_registerRestorer != null)
_registerRestorer.StopRegisterAccess = true;
if (_meterFile != null)
_meterFile.StopProcess = true;
MessageBoxShow(Resources.StrUserStop, "Info", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
// take the invoker of the error state to generate the message and / or popup window
switch (_invokerProcessState)
{
case ProcessState.PortScan:
_meterBatch?.RemoveAllMeters();
_processState = ProcessState.Idle;
break;
default:
_processState = ProcessState.Idle;
break;
}
}
/// <summary>
/// Common routine to process update event to display and log results
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Take all senders for processing bar and text output.
/// </remarks>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Change to log text for RegisterRestorer and FW-Update to fill report file.
/// </remarks>
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
/// - Removed log for FW-Update process as this gives no additional useful information.
/// </remarks>
public virtual void ProcessUpdate_Event(Object sender, ProcessExecEventArgs e)
{
switch (sender)
{
case RegisterRestorer _:
if (!string.IsNullOrEmpty(e.ActualProcessMessage))
{
LogText(e.ActualProcessMessage);
}
break;
case MeterPortScanner _:
if (!string.IsNullOrEmpty(e.ActualProcessMessage))
{
LogText(e.ActualProcessMessage);
}
break;
case MeterFwUpdate _:
// Nothing to do here
break;
}
OverallProcessUpdate(e);
if (e.ActualProcessPercent != null)
ActualProcessUpdate(e);
}
/// <summary>
/// Update of overall process bar and text output
/// </summary>
/// <param name="e"></param>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Invoker implemented.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void OverallProcessUpdate(ProcessExecEventArgs e)
{
UiInvoker.ControlInvoker(lblOverallProcess, ColorDefault, e.OverallProcessMessage);
if (e.OverallProcessPercent == null)
return;
var progress = (Int32)e.OverallProcessPercent;
UiInvoker.ProgressBarInvoker(barOverallProgressUpdate, progress > 100 ? 100 : progress);
}
/// <summary>
/// Update of single process bar and text output
/// </summary>
/// <param name="e"></param>
/// <remarks date="2020-Dec-10" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Invoker implemented.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
private void ActualProcessUpdate(ProcessExecEventArgs e)
{
UiInvoker.ControlInvoker(lblActualProcess, ColorDefault, e.ActualProcessMessage);
if (e.ActualProcessPercent == null)
return;
var progress = (Int32)e.ActualProcessPercent;
UiInvoker.ProgressBarInvoker(barActualProgressUpdate, progress > 100 ? 100 : progress);
}
#endregion --------------------------------------- Common Process Routines ------------------------------------
#region ------------------------------------------ Reboot -----------------------------------------------------
/// <summary>
/// Update the firmware and restart the water-meter.
/// INFO: Takes a lot of time until the water-meter answers again.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Changed port scanner call using state responses after execution.
/// </remarks>
private void Reboot(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
if (_currentGenesis == null)
{
_processState = errorExitState;
return;
}
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitReboot, ExecReboot, FinalizeReboot, _cultureInfo,
exitProcessStateObjects);
}
/// <summary>
/// Initialization of reboot procedure.
/// </summary>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitReboot()
{
SetOverallProgressDisplayOn(Resources.StrUpdateAppAndRestartMeter);
_timerIntervalExpired = false;
}
/// <summary>
/// Execution of reboot.
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Timer interval expired introduced.
/// </remarks>
private StatusReturn ExecReboot()
{
String text = null;
// var timerInterval = _tmrProgressUpdate.Interval;
var timerInterval = TimerInterval_ms;
_bootDelayCtr_ms = 0;
var internalCommunicationDelayMs = _bootDelayCtr_ms + 10 * timerInterval;
while (_bootDelayCtr_ms < RebootTimeout_ms && string.IsNullOrEmpty(text))
{
if (!_timerIntervalExpired)
continue;
_timerIntervalExpired = false;
// internal communication delay to avoid overload of Cordonel
if (_bootDelayCtr_ms % internalCommunicationDelayMs == 0)
{
text = RegisterConverter.ByteArrayToValue<String>(
_currentGenesis.ReadRegister(Register.Configexchange.PcbSerialNumber, 12));
}
ProcessUpdate_Event(this, new ProcessExecEventArgs(Resources.StrUpdateAppAndRestartMeter,
_bootDelayCtr_ms * 100.0 / RebootTimeout_ms));
_bootDelayCtr_ms += timerInterval;
}
return string.IsNullOrEmpty(text) ? StatusReturn.Failed : StatusReturn.Okay;
}
/// <summary>
/// Finalization of reboot.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Using success from execute routine
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
/// - Set date and time.
/// </remarks>
/// <remarks date="2023-Nov-23" author="Thomas Wiedebusch">
/// - Check all installed meter apps.
/// </remarks>
/// <remarks date="2024-Feb-12" author="Thomas Wiedebusch">
/// - Request new pcbId as the auto-detected may be corrupt.
/// </remarks>
private void FinalizeReboot(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out _);
// try to login to check if meter is ready after reboot procedure
_currentGenesis.GetPcbId();
_currentGenesis.ReLogin();
// Check status of installed applications
_meterFwUpdate?.CompareAllMeterAndFileApps();
_processState = _currentGenesis.IsLoggedOn ? successExitState : errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
// set the time
SetDateTime();
}
#endregion --------------------------------------- Reboot -----------------------------------------------------
#region ------------------------------------------ Port Scan --------------------------------------------------
/// <summary>
/// Execution of port scan.
/// INFO: Very time consuming because of trial to open the port listed in Win-Device-Manager,
/// the more ports listed, the more time will be needed. This may last a few minutes!
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Changed port scanner call using state responses after execution.
/// </remarks>
private void PortScanner(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitPortScanner, ExecPortScanner, FinalizePortScanner, _cultureInfo,
exitProcessStateObjects);
}
/// <summary>
/// Initialization of port scanner.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitPortScanner()
{
//initialize the port type if not read from file
_portType = "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort";
if (_serialPortScanner == null)
_serialPortScanner = new MeterPortScanner(_portType);
//set the process bars
SetOverallProgressDisplayOn();
SetControlsCommunicationActive();
_serialPortScanner.OnProcessUpdate += ProcessUpdate_Event;
}
/// <summary>
/// Execution of port scanner.
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private StatusReturn ExecPortScanner()
{
_serialPortScanner.ScanAllSerialPorts(Slot, _basePath?.PortConfigFilePathName);
return _serialPortScanner.AutoDetectedPortName == null ? StatusReturn.Failed : StatusReturn.Okay;
}
/// <summary>
/// Finalization of port scanner.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Using success from execute routine
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Reworked exit state.
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Result display moved to here.
/// </remarks>
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
/// - Removed additional stop user stop message,
/// - Dispose port scanner.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2021-Mai-11" author="Thomas Wiedebusch">
/// - Delay after port scan to free serial port for new assignment.
/// </remarks>
private void FinalizePortScanner(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
if (_serialPortScanner != null)
_serialPortScanner.OnProcessUpdate -= ProcessUpdate_Event;
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out var breakExitState);
if (_processState == breakExitState)
{
_serialPortScanner?.Dispose();
SetOverallProgressDisplayOff();
return;
}
if (success != StatusReturn.Okay)
{
// If the IrDA port scan failed, it makes no sense to signal Cordonel Detection failed!
InfoProcessFailed(null, Resources.StrRequestPortFailed);
_processState = errorExitState;
_serialPortScanner?.Dispose();
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
_portConfig = new PortConfig
{
PortName = _serialPortScanner?.AutoDetectedPortName,
Type = _portType
};
_serialPortScanner?.Dispose();
InfoProcessSuccess(lblCordonelDetection, Resources.StrCordonelDetectSucceeded);
_processState = successExitState;
// wait to free the serial port
GC.Collect();
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
#endregion --------------------------------------- Port Scan --------------------------------------------------
#region ------------------------------------------ Connect ----------------------------------------------------
/// <summary>
/// Establish connection to genesis water meter
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Changed port scanner call using state responses after execution.
/// </remarks>
private void Connect(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitConnect, ExecConnect, FinalizeConnect, _cultureInfo, exitProcessStateObjects);
}
/// <summary>
/// Initialization of connect to water-meter.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Mai-11" author="Thomas Wiedebusch">
/// - Delay at start up to allow dispose for eventually assigned port from scanner.
/// </remarks>
private void InitConnect()
{
SetControlsCommunicationActive();
Thread.Sleep(1000);
if (_currentGenesis == null)
{
//assign new meter and assign meter to FW update file if this exists
_currentGenesis = new GenesisMeter(Slot, _portConfig, null)
{
Configuration =
{
//configuration has to be set BEFORE adding meter to batch to avoid e.g. auto update files
//from network and therefore have a long network request timeout before the task starts
UseRegisterWatchService = false,
UseMinMaxCheck = false,
AutoUpdateFiles = false
}
};
_meterBatch?.AddMeter(_currentGenesis);
}
if (_currentGenesis == null)
{
_processState = ProcessState.Error;
return;
}
//assign register restorer or set new created Genesis to access the post update register restorer
if (_registerRestorer == null)
_registerRestorer = new RegisterRestorer(_currentGenesis);
else
_registerRestorer.AssignGenesis(_currentGenesis);
SetOverallProgressDisplayOn(Resources.StrConnecting);
}
/// <summary>
/// Execution of connect to water-meter.
/// Sequence:
/// - Check if pcbId of meter is set, if not this is an initial login. On initial login it has to require
/// the pcbId (read it from the meter) and mark this initial access as this will execute some special
/// steps <see cref="LoginAndSpecialSetupProcedure"/>,
/// - Create the report files for the detected pcbId,
/// - Searches for update information extracted from the FW update safe and extract password level 8 and
/// the skeleton key,
/// - Delays 3s as a previous login may have failed to avoid blocking of login procedure,
/// - Tries to login with password ( this is the level 8 pwd), exit here if successfully logged in,
///
/// - If login with password level 8 failed:
/// - The password file may be corrupted or not installed,
/// - Delays 5s,
/// - Try to login with skeletonKey and exit if successfully logged in, else mark procedure as failed.
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-18" author="Thomas Wiedebusch">
/// - Returns true if _currentGenesis is logged on.
/// </remarks>
/// <remarks date="2021-Jan-29" author="Thomas Wiedebusch">
/// - Check if password file is installed by logging in with password Level 8, if this is not possible
/// the password file may be corrupted or not installed, then try the skeletonKey and remind the
/// password file error being able to restore it.
/// </remarks>
/// <remarks date="2021-Feb-02" author="Thomas Wiedebusch">
/// - If login with password Level 8 failed, keep delay and try login with skeletonKey.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Reset marker for password corruption.
/// </remarks>
/// <remarks date="2021-Feb-15" author="Thomas Wiedebusch">
/// - Display information if Genesis is not in update list (password = null and skeletonKey = null).
/// </remarks>
/// <remarks date="2021-Apr-12" author="Thomas Wiedebusch">
/// - Additional delay before first login to avoid lock if login has been tried just before,
/// - Check for final login as this allows EXPLICIT login with password level 8 to validate password file.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Execute even if still logged on to meter,
/// - Store all configurations .
/// </remarks>
/// <remarks date="2023-Feb-17" author="Thomas Wiedebusch">
/// - Switch radio on if not NA version.
/// </remarks>
/// <remarks date="2023-Mar-01" author="Thomas Wiedebusch">
/// - "Release Display" before "Store Configuration".
/// </remarks>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - "Deactivate Pulse Mode" at initial login to avoid interference of pulses to IrDA communication.
/// </remarks>
/// <remarks date="2023-Aug-22" author="Thomas Wiedebusch">
/// - Create report file early to catch radio and pulse mode activity.
/// </remarks>
/// <remarks date="2023-Oct-11" author="Thomas Wiedebusch">
/// - AVOID marking of _passwordFile is corrupted on initial login as the pulse output may be active
/// and needs to be switched OFF first in the <see cref="LoginAndSpecialSetupProcedure"/>,
/// - Verifies password file of meter by reading it out and comparing it with the password file
/// from the FW update safe, remind the hashedPwdFile being able to restore it if not installed,
/// - Additional logging of password login or skeleton login,
/// - Report customer serial number.
/// </remarks>
/// <remarks date="2023-Oct-18" author="Thomas Wiedebusch">
/// - Restructured.
/// </remarks>
/// <remarks date="2023-Oct-18" author="Thomas Wiedebusch">
/// - Avoid report file generation on finalLoginAfterUpdate.
/// </remarks>
/// <remarks date="2023-Oct-27" author="Thomas Wiedebusch">
/// - Message moved to <see cref="ReadLogAndCompareMeterPwdFile"/>.
/// </remarks>
/// <remarks date="2023-Nov-09" author="Thomas Wiedebusch">
/// - Enabled final connect with passwordLvl8 and skeletonKey to enable installation of password file.
/// </remarks>
/// <remarks date="2023-Nov-17" author="Thomas Wiedebusch">
/// - LoginDelay dynamically based on trials, reset after successfully login.
/// </remarks>
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
/// - Added password valid information main screen and log file.
/// </remarks>
/// <remarks date="2024-May-07" author="Thomas Wiedebusch">
/// - Added power correction on FW update to specific version.
/// </remarks>
private StatusReturn ExecConnect()
{
// If the PCB ID is not set (read from meter) this is the first initial login.
if (string.IsNullOrEmpty(_currentGenesis.PcbId))
{
// Read out the PCB ID from water-meter needed to open the password container of this device
_currentGenesis.GetPcbId();
// Create report file
if (_initialConnect)
{
BuildReportFiles();
}
}
// Search the PCB ID and extract password for login
_skeletonKey = null;
_passwordLvl8 = null;
_genesisInUpdateList = false;
if (_cordonelDeviceInfos != null)
{
foreach (var device in _cordonelDeviceInfos.Where(device => _currentGenesis.PcbId == device.PcbId))
{
_skeletonKey = device.PwdContainer.SkeletonKey;
_passwordLvl8 = device.PwdContainer.PasswordLvl8;
_customerSerialNumber = device.CustomerSerialNumber;
// extract the hashed safe password file for validation
_fwUpdateSafePwdFile = new Byte[MeterPwdDb.PwdFileLength];
_fwUpdateSafePwdFile = device.PwdContainer.EncryptedPasswordFile;
// get LUT file content from FW update safe
_fwUpdateSafeLutFile = device.LutFile;
// extract the power corrections and required FW update version
_fwUpdatePowerCorrection = device.PowerCorrectionValues;
// mark that meter is in update list
_genesisInUpdateList = true;
break;
}
}
if (_passwordLvl8 == null || _skeletonKey == null)
{
_processState = ProcessState.Error;
return StatusReturn.Failed;
}
// This login may be time consuming on readout of application information
// first try to login with password Level 8, this is needed if the password file is installed
if (LoginAndSpecialSetupProcedure(_passwordLvl8))
{
// Reset login delay as on successfully login the meter resets the retry-lock-in-delay
_loginDelay_ms = DefaultLoginDelay_ms;
LogText(Resources.StrLoginPwdLevel8);
InfoProcessSuccess(lblPasswordFileCheck, Resources.StrPasswordFileValid);
_passwordFileIsCorrupted = false;
return StatusReturn.Okay;
}
// Delay needed if repeated login trial given by Cordonel, the next communication has to
// wait that long, otherwise the Cordonel locks for 2s, 4s, 8s, 16s and so on on every retry!
_loginDelay_ms *= 2;
Thread.Sleep(_loginDelay_ms);
// Use the skeleton key for login as the password file may not be installed or invalid
if (LoginAndSpecialSetupProcedure(_skeletonKey))
{
// Reset login delay as on successfully login the meter resets the retry-lock-in-delay
_loginDelay_ms = DefaultLoginDelay_ms;
LogText(Resources.StrLoginSkeletonKey);
return StatusReturn.Okay;
}
_processState = ProcessState.Error;
return StatusReturn.Failed;
}
/// <summary>
/// Common login and special setup procedure, as this is needed for login with password level 8
/// and for login with skeleton key:
/// - Initial login without register readout being able to quick switch off the pulse mode,
/// - Switch off the pulse mode on initial connection,
/// - Activate the radio in customer mode,
/// - Release the display to normal operation,
/// - Setup date and time,
/// - Store all configurations on initial connection,
/// - Logs the password file content if not initial connected.
/// </summary>
/// <param name="password">lvl8 or skeleton</param>
/// <returns>true if successfully executed</returns>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-18" author="Thomas Wiedebusch">
/// - Date time setup,
/// - Read and log meter password file.
/// </remarks>
/// <remarks date="2023-Nov-15" author="Thomas Wiedebusch">
/// - Clean error collection messages on initial connect.
/// </remarks>
/// <remarks date="2023-Nov-17" author="Thomas Wiedebusch">
/// - Retries on pulse mode switching off as this is very critical for a healthy communication and
/// may cause unpredictable behaviour if it fails.
/// </remarks>
/// <remarks date="2023-Nov-24" author="Thomas Wiedebusch">
/// - Fill data grid with all FW infos.
/// </remarks>
/// <remarks date="2024-Feb-20" author="Thomas Wiedebusch">
/// - Changed program flow.
/// </remarks>
/// <remarks date="2024-Apr-24" author="Thomas Wiedebusch">
/// - Update app information on new connect.
/// </remarks>
private Boolean LoginAndSpecialSetupProcedure(String password)
{
Boolean retVal;
var pulseModeOffRetryCtr = 1;
if (_initialConnect)
{
// Start with a fresh list of errors to decide after the entire procedure how to proceed
_errorCollectionMessages?.Clear();
do
{
// Login without reading all registers and FW keeping the communication to a minimum to switch
// the pulse mode OFF before reading all installed apps, which will be unpredictable on activated
// pulse module.
retVal = _currentGenesis.Login(password, skipReadMeterFwAndAssignRegisters: true);
if (retVal)
{
// Switch the pulse module inactive on initial login at first connect and log out
DeactivatePulseMode();
// Reset login delay as on successfully login the meter resets the retry lock time
_loginDelay_ms = DefaultLoginDelay_ms;
}
else
{
// Delay needed if repeated login trial given by Cordonel, the next communication has to
// wait that long, otherwise the Cordonel locks for 2s, 4s, 8s, 16s and so on on every retry!
_loginDelay_ms *= 2;
Thread.Sleep(_loginDelay_ms);
}
// Increase th login delay to avoid
} while (!retVal && pulseModeOffRetryCtr-- > 0);
// If the login couldn't be performed exit with error
if (!retVal)
{
return false;
}
}
// Second login if initial was executed including reading out all installed applications
retVal = _currentGenesis.Login(password);
// Set radio to customer mode (encryption active)
if (_initialConnect && retVal)
{
if (_currentGenesis.RadioFrequencyMhz != null)
{
SetRadioToCustomerMode();
}
// safe all settings from RAM to FLASH to keep those for reboot
StoreConfigurations();
// Read out the installed password file for logging and compare it with the required one
ReadLogAndCompareMeterPwdFile();
// remind initial connect has been executed once
_initialConnect = false;
}
// display to normal operation and stores the setup permanently
retVal = ReleaseDisplayAndSwitchLedOff();
// Check status of installed applications
_meterFwUpdate?.AssignGenesis(_currentGenesis);
_meterFwUpdate?.CompareAllMeterAndFileApps();
FillDataGridWithAllInfos();
return retVal;
}
/// <summary>
/// Finalize of connection process to water-meter.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Using success from execute routine
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Reworked exit state.
/// </remarks>
/// <remarks date="2020-Dec-22" author="Thomas Wiedebusch">
/// - Label corrected for user stop.
/// </remarks>
/// <remarks date="2020-Dec-23" author="Thomas Wiedebusch">
/// - Added Cordonel detected.
/// </remarks>
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
/// - Removed additional stop user stop message.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Log information if password file is not installed.
/// </remarks>
/// <remarks date="2021-Feb-15" author="Thomas Wiedebusch">
/// - Display information if Genesis is not in update list.
/// </remarks>
/// <remarks date="2021-Feb-25" author="Thomas Wiedebusch">
/// - Used UiInvoker
/// </remarks>
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
/// - Build report files added.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2021-Apr-15" author="Thomas Wiedebusch">
/// - Avoid report of versions on final login after update, this is already reported.
/// </remarks>
/// <remarks date="2021-Apr-20" author="Thomas Wiedebusch">
/// - Final login message produced to log password file validation.
/// </remarks>
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
/// - Enable connect if error.
/// </remarks>
/// <remarks date="2023-Aug-22" author="Thomas Wiedebusch">
/// - Report file generation moved to <see cref="ExecConnect"/>.
/// </remarks>
/// <remarks date="2023-Oct-15" author="Thomas Wiedebusch">
/// - Log always installed meter FW.
/// </remarks>
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
/// - Final login is not a grant for valid password file anymore.
/// </remarks>
private void FinalizeConnect(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out var breakExitState);
if (_processState == breakExitState)
{
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
if (success != StatusReturn.Okay)
{
InfoProcessFailed(lblCordonelAuthentication, !_genesisInUpdateList
? $"{_currentGenesis.PcbId} {Resources.StrCordonelNotInUpdateList}"
: Resources.StrCordonelAuthenticationFailed);
_processState = errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
SetDisableDownloadAndEnableConnect();
return;
}
_meterFwUpdate?.AssignGenesis(_currentGenesis);
// if authentication succeeded, the cordonel port detection must have succeeded in advance,
// this information might get lost on Connect() without previous port scan.
InfoProcessSuccess(lblCordonelDetection, Resources.StrCordonelDetectSucceeded);
InfoProcessSuccess(lblCordonelAuthentication, Resources.StrCordonelAuthenticationSucceeded);
// use here direct text assignment to avoid x or v before label
UiInvoker.ControlInvoker(lblConnectPcb, ColorSuccess,
Resources.StrPartPcbConnected + @" " + _currentGenesis.PcbId);
UiInvoker.ControlInvoker(lblCoreRevision, ColorDefault,
Resources.StrCoreRevision + @" " + $"{_currentGenesis.StrCoreRevision}");
LogInstalledMeterFw();
_processState = successExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
#endregion --------------------------------------- Connect ----------------------------------------------------
#region ------------------------------------------ Register Read ----------------------------------------------
/// <summary>
/// Execution of register read.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Changed port scanner call using state responses after execution.
/// </remarks>
private void ReadRegisters(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
if (_currentGenesis == null || _registerRestorer == null)
{
_processState = errorExitState;
return;
}
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitReadRegister, ExecReadRegister, FinalReadRegisters,
_cultureInfo, exitProcessStateObjects);
}
/// <summary>
/// Initialization of register backup.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitReadRegister()
{
SetControlsAllButtonsDisabled();
if (_invokerProcessState == ProcessState.CompareRegisters)
{
InfoProcessActive(lblRegisterReadout, Resources.StrRegisterCompareActive);
}
else
{
InfoProcessActive(lblRegisterReadout, Resources.StrRegisterReadoutActive);
}
//set the process bars
SetOverallProgressDisplayOn(Resources.StrRegisterReadoutActive);
_registerRestorer.OnProcessUpdate += ProcessUpdate_Event;
}
/// <summary>
/// Execution of register read.
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-23" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Nov-21" author="Thomas Wiedebusch">
/// - Skip final read and compare if not updated or no recovery request.
/// </remarks>
private StatusReturn ExecReadRegister()
{
Boolean retVal;
switch (_invokerProcessState)
{
case ProcessState.InitialReadRegisters:
retVal = _registerRestorer.InitialReadRegisters();
break;
case ProcessState.FinalReadRegisters:
if (_afterUpdateConnect || _recoveryRegistersRequired)
{
retVal = _registerRestorer.FinalReadRegisters();
}
else
{
retVal = true;
}
break;
case ProcessState.CompareRegisters:
if (_afterUpdateConnect || _recoveryRegistersRequired)
{
retVal = _registerRestorer.CompareRegisters();
}
else
{
retVal = true;
}
break;
default:
retVal = _registerRestorer.ReadRegisters();
break;
}
return retVal ? StatusReturn.Okay : StatusReturn.Failed;
}
/// <summary>
/// Finalization of register reading.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.
/// </remarks>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Using success from execute routine
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Reworked exit state.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2023-Nov-21" author="Thomas Wiedebusch">
/// - Context depending message.
/// </remarks>
private void FinalReadRegisters(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
if (_registerRestorer != null)
_registerRestorer.OnProcessUpdate -= ProcessUpdate_Event;
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out var breakExitState);
if (_processState == breakExitState)
{
InfoProcessFailed(lblRegisterReadout, Resources.StrRegisterUserBreak);
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
String msg;
if (success == StatusReturn.Okay)
{
switch (_invokerProcessState)
{
case ProcessState.InitialReadRegisters:
msg = Resources.StrRegisterReadoutSucceeded;
break;
case ProcessState.FinalReadRegisters:
msg = Resources.StrRegisterReadoutSucceeded;
break;
case ProcessState.CompareRegisters:
msg = Resources.StrRegisterCompareSucceeded;
break;
default:
msg = Resources.StrRegisterReadoutSucceeded;
break;
}
InfoProcessSuccess(lblRegisterReadout, msg);
_processState = successExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
switch (_invokerProcessState)
{
case ProcessState.InitialReadRegisters:
msg = Resources.StrRegisterReadoutFailed;
break;
case ProcessState.FinalReadRegisters:
msg = Resources.StrRegisterReadoutFailed;
break;
case ProcessState.CompareRegisters:
msg = Resources.StrRegisterCompareFailed;
break;
default:
msg = Resources.StrRegisterReadoutFailed;
break;
}
InfoProcessFailed(lblRegisterReadout, msg);
_processState = errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
#endregion --------------------------------------- Register Read ----------------------------------------------
#region ------------------------------------------ File Access ------------------------------------------------
/// <summary>
/// Read all meter files for erase and restore from file defined in FW-Update safe.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2021-Feb-16" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Apr-12" author="Thomas Wiedebusch">
/// - Added password file to erase if password file is marked as corrupted. It has to be replaced!!!!
/// </remarks>
private void PrepareInfoMeterFilesEraseRestore(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
try
{
// loading the lists of meter files which shall be erased before the FW-Update and restored after.
_meterFilesEraseRestore = new MeterFilesEraseRestore();
if (_basePath.MeterFilesConfigFilePathName != null &&
File.Exists(_basePath.MeterFilesConfigFilePathName))
{
using (var tr = new StreamReader(_basePath.MeterFilesConfigFilePathName))
{
var fileStream = tr.ReadToEnd();
_meterFilesEraseRestore = JsonConvert.DeserializeObject<MeterFilesEraseRestore>(fileStream);
}
if (_meterFilesEraseRestore != null)
{
// If the password file is corrupted, it has to be erased. The restore loop will re-install a valid
// password-file
if (_passwordFileIsCorrupted)
_meterFilesEraseRestore.Erase.Add(MeterPwdFile.StrPasswordFileName);
_processState = successExitState;
return;
}
}
InfoProcessFailed(lblFileErase, Resources.StrFileEraseNotDefined);
InfoProcessFailed(lblFileRestore, Resources.StrFileRestoreNotDefined);
_processState = errorExitState;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
InfoProcessFailed(lblFileErase, Resources.StrFileEraseNotDefined);
InfoProcessFailed(lblFileRestore, Resources.StrFileRestoreNotDefined);
_processState = errorExitState;
}
}
/// <summary>
/// Reading all meter files and log the content of it.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2021-Feb-15" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2023-Oct-19" author="Thomas Wiedebusch">
/// - Output message changed.
/// </remarks>
/// <remarks date="2024-Mar-12" author="Thomas Wiedebusch">
/// - If verify meter files succeeded the lblFileErase can be set to success as every file could be read.
/// </remarks>
/// <remarks date="2024-Apr-09" author="Thomas Wiedebusch">
/// - ReLogin replaced with Logout, Delay 250 ms and Login.
/// </remarks>
private void ReadMeterFiles(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
// return on unassigned meter
if (_currentGenesis != null)
{
SetControlsCommunicationActive();
SetOverallProgressDisplayOn(Resources.StrFileAccessActive);
switch (_invokerProcessState)
{
case ProcessState.InitialReadMeterFiles:
case ProcessState.PreUpdateReadMeterFiles:
InfoProcessActive(lblFileErase, Resources.StrFileAccessActive, false);
break;
case ProcessState.VerifyMeterFiles:
InfoProcessActive(lblFileErase, Resources.StrFileAccessActive, false);
InfoProcessActive(lblFileRestore, Resources.StrFileAccessActive, false);
break;
}
_currentGenesis.Logout();
Thread.Sleep(250);
_currentGenesis.Login();
// read all meter files
_meterFile = new MeterFile(_currentGenesis);
if (_readMeterFiles == null)
_readMeterFiles = new List<String>();
_readMeterFiles.Clear();
if (_meterFile.ReadMeterFileCatalog(out var meterFilesDrive0, MeterFile.StrMeterDrive0))
{
_readMeterFiles.AddRange(meterFilesDrive0);
}
if (_meterFile.ReadMeterFileCatalog(out var meterFilesDrive1, MeterFile.StrMeterDrive1))
{
_readMeterFiles.AddRange(meterFilesDrive1);
}
if (_readMeterFiles.Count > 0)
{
_processState = successExitState;
SetOverallProgressDisplayOff();
switch (_invokerProcessState)
{
case ProcessState.InitialReadMeterFiles:
case ProcessState.PreUpdateReadMeterFiles:
InfoProcessSuccess(lblFileErase, Resources.StrFileReadoutSucceeded, false);
break;
case ProcessState.VerifyMeterFiles:
InfoProcessSuccess(lblFileErase, Resources.StrFileReadoutSucceeded, false);
InfoProcessSuccess(lblFileRestore, Resources.StrFileReadoutSucceeded, false);
break;
}
//log files
LogStringList(Resources.StrFileRead, _readMeterFiles, Resources.StrFileReadoutSucceeded);
return;
}
}
switch (_invokerProcessState)
{
case ProcessState.InitialReadMeterFiles:
case ProcessState.PreUpdateReadMeterFiles:
InfoProcessFailed(lblFileErase, Resources.StrFileReadoutFailed, false);
break;
case ProcessState.VerifyMeterFiles:
InfoProcessFailed(lblFileRestore, Resources.StrFileReadoutFailed, false);
break;
}
//log files
LogStringList(Resources.StrFileRead, _readMeterFiles, errorMessage: Resources.StrFileReadoutFailed);
_processState = errorExitState;
SetOverallProgressDisplayOff();
}
/// <summary>
/// Tries to erase unused files to free space for the FW-Update files.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2021-Feb-16" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2023-Oct-19" author="Thomas Wiedebusch">
/// - Output message changed to get colored error or success messages.
/// </remarks>
private void MeterFilesErase(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
// return on unassigned meter
if (_currentGenesis != null)
{
SetControlsCommunicationActive();
InfoProcessActive(lblFileErase, Resources.StrFileAccessActive);
_currentGenesis.ReLogin();
// read all meter files
var meterFile = new MeterFile(_currentGenesis);
var erasedMeterFiles = new List<String>();
var filesToErase = new List<String>();
var wildcardList = new List<String>();
// check if any file has to be erased
if (_meterFilesEraseRestore?.Erase != null && _meterFilesEraseRestore.Erase.Count > 0 &&
_readMeterFiles != null && _readMeterFiles.Count > 0)
{
// build a wildcard list and remove wildcard '*'
foreach (var meterFileToEraseMask in _meterFilesEraseRestore.Erase.Where(
x => x.Contains(Constants.StrWildcard)))
{
var wildcardMask = meterFileToEraseMask.Split('*');
wildcardList.Add(wildcardMask[0]);
}
// build a new list of all files referenced by wildcard search
filesToErase.AddRange(_readMeterFiles.Where(readMeterFile =>
wildcardList.Any(readMeterFile.Contains)));
// add all exactly named files
filesToErase.AddRange(_readMeterFiles.Where(readMeterFile =>
_meterFilesEraseRestore.Erase.Any(readMeterFile.Contains)));
// erase all files from list
foreach (var eraseFile in filesToErase)
{
SetOverallProgressDisplayOn(Resources.StrFileErasing + eraseFile);
// here the real erase will be executed
if (!meterFile.UnlockEraseWriteMeterFile(eraseFile))
continue;
if (meterFile.EraseMeterFile(eraseFile))
{
erasedMeterFiles.Add(eraseFile);
}
}
if (erasedMeterFiles.Count == filesToErase.Count)
{
_processState = successExitState;
InfoProcessSuccess(lblFileErase, Resources.StrFileEraseSucceeded, false);
//log files
LogStringList(Resources.StrFileErased, erasedMeterFiles, Resources.StrFileEraseSucceeded);
SetOverallProgressDisplayOff();
return;
}
}
LogStringList(Resources.StrFileErased, erasedMeterFiles, errorMessage: Resources.StrFileReadoutFailed);
}
InfoProcessFailed(lblFileErase, Resources.StrFileReadoutFailed, false);
_processState = errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
/// <summary>
/// Tries to install the erased files.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2021-Feb-16" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void MeterFilesRestore(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
// return on unassigned meter
if (_currentGenesis != null)
{
SetControlsCommunicationActive();
InfoProcessActive(lblFileRestore, Resources.StrFileAccessActive);
_currentGenesis.ReLogin();
var meterFile = new MeterFile(_currentGenesis);
var restoredMeterFiles = new List<String>();
// check if any file has to be restored
if (_meterFilesEraseRestore?.Restore != null)
{
// check if file exists which should be restored
foreach (var restoreFile in _meterFilesEraseRestore.Restore)
{
if (_readMeterFiles != null)
{
// skip file restore if this file has already been created
// e.g. an application may create a file on re-boot
if (_readMeterFiles.Contains(restoreFile.Name))
{
// this file will be put to list even if the application has created it by itself
restoredMeterFiles.Add(restoreFile.Name);
continue;
}
}
SetOverallProgressDisplayOn(Resources.StrFileRestoring + restoreFile.Name);
// this is the restore execution
if (!meterFile.UnlockEraseWriteMeterFile(restoreFile.Name))
continue;
if (meterFile.CreateEmptyMeterFile(restoreFile.Name, restoreFile.ByteSize))
{
restoredMeterFiles.Add(restoreFile.Name);
}
}
if (restoredMeterFiles.Count > 0)
{
//log files
LogStringList(Resources.StrFileRestored, restoredMeterFiles);
}
if (restoredMeterFiles.Count == _meterFilesEraseRestore.Restore.Count)
{
_processState = successExitState;
InfoProcessSuccess(lblFileRestore, Resources.StrFileRestoreSucceeded);
SetOverallProgressDisplayOff();
return;
}
}
}
InfoProcessFailed(lblFileRestore, Resources.StrFileRestoreFailed);
_processState = errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
/// <summary>
/// Tries to install the password file if this is corrupted, Genesis has to be logged in.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2021-Jan-29" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2021-Feb-02" author="Thomas Wiedebusch">
/// - Message if succeeded.
/// </remarks>
/// <remarks date="2021-Feb-03" author="Thomas Wiedebusch">
/// - Reset marker for password corruption.
/// </remarks>
/// <remarks date="2021-Feb-18" author="Thomas Wiedebusch">
/// - Password restored message included if password file was present at all times.
/// - After reinstalling the password file a login will be forced with the connect status request
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
/// <remarks date="2021-Apr-12" author="Thomas Wiedebusch">
/// - Mark for final login as this allows EXPLICIT login with password level 8 to validate password file.
/// </remarks>
/// <remarks date="2021-Apr-20" author="Thomas Wiedebusch">
/// - Changed password file valid message.
/// </remarks>
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
/// - Checked for password file not in update container.
/// </remarks>
/// <remarks date="2022-Apr-28" author="Thomas Wiedebusch">
/// - Release display to normal operation,
/// - switch measurement LED off.
/// </remarks>
/// <remarks date="2022-Mai-10" author="Thomas Wiedebusch">
/// - Release display removed.
/// </remarks>
/// <remarks date="2023-Oct-11" author="Thomas Wiedebusch">
/// - Hashed password file read on connect from FW update safe.
/// </remarks>
/// <remarks date="2023-Oct-24" author="Thomas Wiedebusch">
/// - Read and log password file after restoring.
/// </remarks>
/// <remarks date="2023-Oct-27" author="Thomas Wiedebusch">
/// - Removed check for password file length, leave it to the <see cref="MeterPwdFile.CheckHashedPwdFile"/>
/// </remarks>
/// <remarks date="2023-Nov-21" author="Thomas Wiedebusch">
/// - Removed state change to final connect.
/// </remarks>
private void RestorePasswordFile(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
// if the password file is valid exit
if (!_passwordFileIsCorrupted)
{
InfoProcessSuccess(lblPasswordFileCheck, Resources.StrPasswordFileValid);
_processState = successExitState;
return;
}
InfoProcessActive(lblPasswordFileCheck, Resources.StrPasswordFileRestoreActive);
// try to install password, first get the password file from device info
var pwdFileObject = new MeterPwdFile(_currentGenesis);
if (_fwUpdateSafePwdFile != null &&
pwdFileObject.CheckHashedPwdFile(_fwUpdateSafePwdFile, _skeletonKey, _passwordLvl8))
{
SetControlsCommunicationActive();
_currentGenesis?.ReLogin();
if (pwdFileObject.UnlockEraseWriteMeterPwdFile())
{
if (pwdFileObject.WriteAndVerifyMeterPwdFile(_fwUpdateSafePwdFile) &&
ReadLogAndCompareMeterPwdFile())
{
_passwordFileIsCorrupted = false;
_afterUpdateConnect = false;
InfoProcessSuccess(lblPasswordFileCheck, Resources.StrPasswordFileRestoreSucceeded);
_processState = successExitState;
}
else
{
InfoProcessFailed(lblPasswordFileCheck, Resources.StrPasswordFileRestoreFailed);
_processState = errorExitState;
}
}
}
// the password file is NOT in the password container
else
{
InfoProcessFailed(lblPasswordFileCheck, Resources.StrPasswordFileFromSafeInvalid);
_processState = errorExitState;
}
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
/// <summary>
/// Reads the LUT and restores it if different from given LUT in FwUpdateSafe.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <remarks date="2023-Nov-13" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Nov-17" author="Thomas Wiedebusch">
/// - For FW version without LUT file needed return with success state,
/// - Reading initially the header to get the length of the LUT.
/// </remarks>
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
/// - Taking meter response of invalid LUT file indicated by 0x8000xxxx.
/// </remarks>
/// <remarks date="2023-Nov-27" author="Thomas Wiedebusch">
/// - Added check for string LutCrc is not null or empty.
/// </remarks>
/// <remarks date="2023-Nov-28" author="Thomas Wiedebusch">
/// - Use installed FW version to decide if LUT is required.
/// </remarks>
private void RestoreLutFile(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error)
{
// Remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
InfoProcessActive(lblLutFileCheck, Resources.StrLutFileRestoreActive, false);
//1. If InstalledFwVersion is below 1.2.xxx a LUT file is NOT required
if ((_currentGenesis.Region == "EMEA"
&& _currentGenesis.InstalledFwVersion < MeterLutUpdate.EmeaFwThresholdForLutFile) ||
(_currentGenesis.Region == "NA"
&& _currentGenesis.InstalledFwVersion < MeterLutUpdate.NaFwThresholdForLutFile))
{
InfoProcessSuccess(lblLutFileCheck, Resources.StrLutFileNotNeeded);
_processState = successExitState;
return;
}
// 2. LUT file is needed but invalid indicated by CRC or cannot be observed in file list
if ( (!string.IsNullOrEmpty(_currentGenesis.LutCrc)
&& _currentGenesis.LutCrc.Contains(MeterLutFile.StrLutFileInvalidCrc))
|| (_readMeterFiles != null
&& !_readMeterFiles.Any(f => f.Contains(MeterLutFile.StrLutMeterFileName))))
{
InfoProcessFailed(lblLutFileCheck, Resources.StrLutFileNotInstalled);
_processState = errorExitState;
return;
}
// 3. LUT file NOT installed:
// If meter LUT CRC is not set and the meter files do not report the LUT, the meter does not have an
// installed LUT
if ((string.IsNullOrEmpty(_currentGenesis.LutCrc) ||
_currentGenesis.LutCrc.Equals(Constants.StrUnknown)) &&
_readMeterFiles != null && !_readMeterFiles.Any(f => f.Contains(MeterLutFile.StrLutMeterFileName)))
{
// 3. a) LUT file not installed but required by the FW update safe:
// If the safe contains a LUT file, the installation of it has failed or it got lost during
// update
if (_fwUpdateSafeLutFile != null)
{
InfoProcessFailed(lblLutFileCheck, Resources.StrLutFileNotInstalled);
_processState = errorExitState;
return;
}
// 3. b) LUT file not installed and not delivered by the FW update safe
// This FW version does not require a LUT file
InfoProcessSuccess(lblLutFileCheck, Resources.StrLutFileNotNeeded);
_processState = successExitState;
LogText(Resources.StrLutFileNotNeeded);
return;
}
// 4. LUT file is installed:
// 4.a) Lut file is installed but NOT in the safe:
// If the LUT is not in the safe it cannot be restored or compared to the required content but can
// be analyzed and logged
// 4.b) LUT file is installed and delivered by the FW update safe:
// On validated installed LUT file in meter this can be compared with the required LUT file
if (!ReadLogAndCompareMeterLutFile())
{
// Set label but do not log as this will be done by ReadLogAndCompareMeterLutFile
InfoProcessFailed(lblLutFileCheck, Resources.StrLutFileCompareFailed, false);
_processState = errorExitState;
}
else
{
// Set label but do not log as this will be done by ReadLogAndCompareMeterLutFile
InfoProcessSuccess(lblLutFileCheck, Resources.StrLutFileCompareSucceeded, false);
_processState = successExitState;
}
// Change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
#endregion --------------------------------------- File Access ------------------------------------------------
#region ------------------------------------------ Register Recovery -----------------------------------------
/// <summary>
/// Execution of register recovery.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void RegisterRecovery(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
if (_currentGenesis == null || _registerRestorer == null)
{
_processState = errorExitState;
return;
}
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitRegisterRecovery, ExecRegisterRecovery, FinalizeRegisterRecovery,
_cultureInfo, exitProcessStateObjects);
}
/// <summary>
/// Initialization of register backup.
/// </summary>
/// <remarks date="2020-Dec-18" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitRegisterRecovery()
{
SetControlsAllButtonsDisabled();
InfoProcessActive(lblRegisterRestore, Resources.StrRegisterAccessActive);
//set the process bars
SetOverallProgressDisplayOn(Resources.StrRegisterAccessActive);
_registerRestorer.OnProcessUpdate += ProcessUpdate_Event;
}
/// <summary>
/// Execution of register recovery.
///
/// ATTENTION:
/// Do NOT sort the recovery registers as those needed to be executed in the correct sequence, as those
/// are preparing the meter to a special mode to take the values and finalize the settings.
///
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-09" author="Thomas Wiedebusch">
/// - Recovery registers at initial login.
/// </remarks>
/// <remarks date="2023-Oct-26" author="Thomas Wiedebusch">
/// - Removed redundant marker for recovery required.
/// </remarks>
private StatusReturn ExecRegisterRecovery()
{
var retVal = StatusReturn.Okay;
_recoveryRegistersRequired = false;
if (_cordonelDeviceInfos != null)
{
foreach (var device in _cordonelDeviceInfos.Where(device => _currentGenesis.PcbId == device.PcbId))
{
// If recovery registers not defined, skip this step with success
if (device.RecoveryRegisters == null || device.RecoveryRegisters.Count == 0)
return StatusReturn.Okay;
retVal = _registerRestorer.WriteRegisters(device.RecoveryRegisters);
break;
}
_recoveryRegistersRequired = true;
Thread.Sleep(1000);
if (!StoreConfigurations())
retVal = StatusReturn.Failed;
}
return retVal;
}
/// <summary>
/// Finalization of register restore.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.es
/// </remarks>
/// <remarks date="2020-Dec-18" author="Thomas Wiedebusch">
/// - Using success from execute routine
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Reworked exit state.
/// </remarks>
/// <remarks date="2021-Apr-07" author="Thomas Wiedebusch">
/// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it.
/// </remarks>
private void FinalizeRegisterRecovery(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
if (_registerRestorer != null)
_registerRestorer.OnProcessUpdate -= ProcessUpdate_Event;
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out var breakExitState);
if (_processState == breakExitState)
{
InfoProcessFailed(lblRegisterRestore, Resources.StrRegisterUserBreak);
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
if (success == StatusReturn.Okay)
{
InfoProcessSuccess(lblRegisterRestore, Resources.StrRegisterRecoverySucceeded, false);
_processState = successExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
if (success == StatusReturn.Warning)
{
InfoProcessWarning(lblRegisterRestore, Resources.StrRegisterRecoveryWarning, false);
// but go ahead to continue writing of registers
_processState = successExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
return;
}
InfoProcessFailed(lblRegisterRestore, Resources.StrRegisterRecoveryFailed, false);
_processState = errorExitState;
// change first the process state, otherwise the timer may switch the bar on again
SetOverallProgressDisplayOff();
}
#endregion --------------------------------------- Register Recovery -----------------------------------------
#region ------------------------------------------ FW-Update --------------------------------------------------
/// <summary>
/// Execution of FW-Update.
/// </summary>
/// <param name="successExitState"></param>
/// <param name="errorExitState"></param>
/// <param name="breakExitState"></param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Changed port scanner call using state responses after execution.
/// </remarks>
private void FwUpdate(ProcessState successExitState, ProcessState errorExitState = ProcessState.Error,
ProcessState breakExitState = ProcessState.Stop)
{
if (_currentGenesis == null || _meterFwUpdate?.FileApps == null ||
_registerRestorer == null)
{
_processState = errorExitState;
return;
}
// remind the invoker being able to generate error messages based on the last state
_invokerProcessState = _processState;
var exitProcessStateObjects = new Object[3];
exitProcessStateObjects[0] = successExitState;
exitProcessStateObjects[1] = errorExitState;
exitProcessStateObjects[2] = breakExitState;
var processExec = new ProcessExec();
processExec.NewProcess(InitFwUpdate, ExecFwUpdate, FinalizeFwUpdate, _cultureInfo,
exitProcessStateObjects);
}
/// <summary>
/// Initialization of FW-Update.
/// </summary>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void InitFwUpdate()
{
_meterFwUpdate.MaxPartialFileDataSize = MaxPartialFileDataSize;
_meterFwUpdate.OnProcessUpdate += ProcessUpdate_Event;
SetControlsCommunicationActive();
SetActualProgressDisplayOn();
SetOverallProgressDisplayOn();
InfoProcessActive(lblFirmwareUpdate, Resources.StrFirmwareUpdateOngoing);
}
/// <summary>
/// Execution of FW-Update.
/// </summary>
/// <returns>true if successfully executed</returns>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private StatusReturn ExecFwUpdate()
{
return _meterFwUpdate.UpdateMeterFw() ? StatusReturn.Okay : StatusReturn.Failed;
}
/// <summary>
/// Finalization of FW-Update.
/// </summary>
/// <param name="success">successful execution</param>
/// <param name="exitProcessStateObjects">objects containing process states sorted success-, error-,
/// break exit state</param>
/// <remarks date="2020-Dec-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-12" author="Thomas Wiedebusch">
/// - Taking objects as representative for finalizing process states.
/// </remarks>
/// <remarks date="2020-Dec-18" author="Thomas Wiedebusch">
/// - Using success from execute routine.
/// </remarks>
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
/// - Reworked exit state.
/// </remarks>
private void FinalizeFwUpdate(StatusReturn success, IReadOnlyList<Object> exitProcessStateObjects)
{
if (_meterFwUpdate != null)
_meterFwUpdate.OnProcessUpdate -= ProcessUpdate_Event;
ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState,
out var breakExitState);
SetActualProgressDisplayOff();
SetOverallProgressDisplayOff();
if (_processState == breakExitState)
{
InfoProcessFailed(lblFirmwareUpdate, Resources.StrFirmwareUpdateBreak);
return;
}
if (success == StatusReturn.Okay)
{
InfoProcessActive(lblFirmwareUpdate, Resources.StrFirmwareUpdateCheck);
_processState = successExitState;
return;
}
InfoProcessFailed(lblFirmwareUpdate, Resources.StrFirmwareUpdateFailed);
_processState = errorExitState;
}
#endregion --------------------------------------- FW-Update --------------------------------------------------
#region ------------------------------------------ Reports ----------------------------------------------------
/// <summary>
/// Build the report files for application list being installed and report log.
/// </summary>
/// <remarks date="2021-Mar-09" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
/// - Changed function type as this is not time consuming.
/// - Implemented directory creation if it does not exists.
/// </remarks>
/// <remarks date="2021-Mar-29" author="Thomas Wiedebusch">
/// - Common definition for report- and app version extensions.
/// </remarks>
/// <remarks date="2021-Apr-12" author="Thomas Wiedebusch">
/// - Logging additional information: Order number, Customer, FwUpdateSw version, Date.
/// </remarks>
private Boolean BuildReportFiles()
{
try
{
if (_basePath == null || string.IsNullOrEmpty(_basePath.CustomerAndOrderNumber) ||
_currentGenesis == null || string.IsNullOrEmpty(_currentGenesis.PcbId))
{
InfoProcessFailed(null, Resources.StrTestReportUnspecified);
return false;
}
var reportNameBase = _currentGenesis.PcbId + FwUpdateConfig.FwUpdateFileFieldSeparator +
_basePath.CustomerAndOrderNumber;
var nameReportFile = reportNameBase + FwUpdateConfig.ReportFileExtension;
var nameCordonelAppVersionFile = reportNameBase + FwUpdateConfig.AppVersionFileExtension;
if (!Directory.Exists(FwUpdateConfig.UnreportedPath))
{
Directory.CreateDirectory(FwUpdateConfig.UnreportedPath);
}
_pathNameReportFile = Path.Combine(FwUpdateConfig.UnreportedPath, nameReportFile);
_pathNameCordonelAppVersionFile = Path.Combine(FwUpdateConfig.UnreportedPath,
nameCordonelAppVersionFile);
if (!string.IsNullOrEmpty(_pathNameReportFile) &&
!string.IsNullOrEmpty(_pathNameCordonelAppVersionFile))
{
LogText($"{Resources.StrTestReportDate} {DateTime.UtcNow:dddd, dd-MMM-yyyy HH:mm:ss} UTC");
LogText($"{Resources.StrTestReportSwNameVersion} " +
$"{_version.Major}.{_version.Minor}.{_version.Build}");
LogText(StrSeparator);
if (_fwUpdateSafeInfo != null)
{
LogText($"{Resources.StrTestReporSafeName} " + _fwUpdateSafeInfo.FwUpdateSafeName);
LogText($"{Resources.StrTestReportFwUpdateValidDate} " +
$"{_fwUpdateSafeInfo.FwUpdateValidDate:dddd, dd-MMM-yyyy HH:mm:ss} UTC");
LogText($"{Resources.StrTestReportFwUpdateFieldOperator} " + _fwUpdateSafeInfo.FwUpdateFieldOperatorId);
LogText($"{Resources.StrTestReportFwUpdateBuilderInfo} " + _fwUpdateSafeInfo.FwUpdateBuilderInfo);
LogText($"{Resources.StrTestReportSafeBuiltDate} " +
$"{_fwUpdateSafeInfo.SafeBuiltDateTime:dddd, dd-MMM-yyyy HH:mm:ss} UTC");
LogText($"{Resources.StrTestReportSafeBuiltOperator} " + _fwUpdateSafeInfo.FwUpdateBuilderOperatorId);
LogText(StrSeparator);
LogText($"{Resources.StrTestReportCustomerName} " + _fwUpdateSafeInfo.CustomerName);
LogText($"{Resources.StrTestReportCustomerNumber} " + _fwUpdateSafeInfo.CustomerNumber);
LogText($"{Resources.StrTestReportOrderNumber} " + _fwUpdateSafeInfo.FwUpdateOrderPosition);
}
else
{
// CustomerName_OrderNumber
var customerOrderSplit =
_basePath.CustomerAndOrderNumber.Split(FwUpdateConfig.FwUpdateFileFieldSeparator);
LogText($"{Resources.StrTestReportCustomerName} {customerOrderSplit[0]}");
LogText($"{Resources.StrTestReportOrderNumber} {customerOrderSplit[1]}");
}
LogText(StrSeparator);
return true;
}
}
catch (Exception)
{
InfoProcessFailed(null, Resources.StrTestReportFailed);
return false;
}
InfoProcessFailed(null, Resources.StrTestReportFailed);
return false;
}
#endregion --------------------------------------- Report -----------------------------------------------------
#region ------------------------------------------ Language ---------------------------------------------------
/// <summary>
/// Select language at runtime: English
/// </summary>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Feb-23" author="Thomas Wiedebusch">
/// - Change menu.
/// </remarks>
/// <remarks date="2021-Feb-26" author="Thomas Wiedebusch">
/// - Avoid repetition if current culture is equal to required.
/// </remarks>
private void RadioBtnEnglishLanguage_Click(Object sender, EventArgs e)
{
if (Thread.CurrentThread.CurrentCulture.Name == "en-GB")
return;
_cultureInfo = new CultureInfo("en-GB");
ChangeLanguageControls();
}
/// <summary>
/// Select language at runtime: German
/// </summary>
/// <remarks date="2020-Nov-27" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2021-Feb-23" author="Thomas Wiedebusch">
/// - Change menu.
/// </remarks>
/// <remarks date="2021-Feb-26" author="Thomas Wiedebusch">
/// - Avoid repetition if current culture is equal to required.
/// </remarks>
private void RadioBtnGermanLanguage_Click(Object sender, EventArgs e)
{
if (Thread.CurrentThread.CurrentCulture.Name == "de-DE")
return;
_cultureInfo = new CultureInfo("de-DE");
ChangeLanguageControls();
}
/// <summary>
/// Change language at runtime
/// </summary>
/// <remarks date="2021-Feb-23" author="Thomas Wiedebusch">
/// - Initial based on code example
/// https://stackoverflow.com/questions/52178064/winforms-localization-how-to-change-the-language-of-a-menu.
/// </remarks>
private void ChangeLanguageControls()
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
Thread.CurrentThread.CurrentCulture = _cultureInfo;
var resources = new ComponentResourceManager(typeof(FrmServiceFwUpdateSw));
resources.ApplyResources(this, "$this");
ControlExtensions.ChangeControlText(resources, Controls);
// connect again if already connected to update all information
if (_currentGenesis != null && !string.IsNullOrEmpty(_currentGenesis.PcbId))
_processState = ProcessState.InitialConnect;
ResetAllStatusLabels();
lblFwUpdateInfo.Text = $@"Version: {_version.Major}.{_version.Minor}.{_version.Build}";
// Display new history
ClearHistoryWindow();
}
#endregion --------------------------------------- Language ---------------------------------------------------
}
}