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.GenesisCore.Consts; 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 { /// /// FW update form /// /// /// - Initial /// [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; // 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 to 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 _cordonelDeviceInfos = new List(); 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 _pulseModeRegistersRestore = new Dictionary(); // 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 _readMeterFiles; // remind customer serial number for logging private String _customerSerialNumber = "?"; // error message collection to decide for field operator action after one run private List _errorCollectionMessages = new List(); // 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 _stateSequencePrepareMeterRelease = new List { ProcessState.RestoreMeterFiles, ProcessState.RestoreLutFile, ProcessState.RecoverRegisters, ProcessState.FinalReadRegisters, ProcessState.CompareRegisters, ProcessState.RestorePasswordFile, ProcessState.FinalConnect, ProcessState.ReadEngineeringLogs, ProcessState.CorrectPowerOverestimation, ProcessState.CalculateLifeTime, ProcessState.VerifyMeterFiles, 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 _stateSequencePrepareAndCheckMeter = new List { 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 _stateSequenceFwUpdate = new List { ProcessState.CheckUpdateCapability, ProcessState.EraseMeterFiles, ProcessState.FirmwareUpdate, ProcessState.Reboot, ProcessState.RebootConnect, ProcessState.RestoreMeterFiles }; /// /// Timer for progress update /// private readonly System.Threading.Timer _tmrProgressUpdate; private const Int32 TimerInterval_ms = 200; // cancellation token private CancellationTokenSource _cancellationTokenSource; private CancellationToken _cancellationToken; private static CancellationTokenSource _processTokenSource; private CancellationToken _processToken; #endregion --------------------------------------- Variables -------------------------------------------------- #region ------------------------------------------ State Machine ---------------------------------------------- /// /// 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! /// /// /// - Initial /// /// /// - State execution locked if not changed! /// /// /// - Reboot logic implemented. /// /// /// - States reworked. /// /// /// - Validate software state added. /// /// /// - Password file installation forces re-login trial. /// /// /// - Added meter file readout, erasure, restore and verification. /// /// /// - 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. /// /// /// - Disable all buttons until license validated. /// /// /// - Report file generation. /// - Call of LogInstalledFw /// /// /// - Report file generation removed, /// - Meter files pre update read does not stop process if it fails, /// - Log installed Meter FW removed. /// /// /// - On Init set buttons to download disable but connect enable. /// /// /// - On final connection after update and password restore, the "connect" will be exited with /// check update request to make the final check. /// /// /// - Sleep on equal process state to force suspend of actual thread. /// /// /// - 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. /// /// /// - Release display on idle entry. /// /// /// - Release display on success entry and on error state. /// /// /// - Reorganized to support maintenance before the FW update capability check. /// /// /// - Release meter to normal operation after error state. /// /// /// - Major rework of sequence. /// /// /// - Prepared for engineering logs. /// /// /// - State machine sequences based on ProcessState tables forcing the sequencing. /// /// /// - Restore meter files and compare registers removed for this release. /// /// /// - Compare registers reactivated based on new interface (configuration.json) with new /// "StaticType": “approximate”. /// /// /// - Power correction for FW update to new version starting with R1.3.0 for EMEA and /// R2.0.06 for NA applied. /// /// /// - Force stop on stop request. /// private void FwUpdateSwStateMachine() { // Execute the state machine until processes should be kept alive while (!_processToken.IsCancellationRequested) { // Avoid repeated run on identical state 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 --------------------------------------------- /// /// 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". /// /// /// - Initial /// /// /// - Timer interval expired introduced. /// /// /// - Added label waiting for meter response at reboot. /// /// /// - Added meter file access states. /// /// /// - Timeout for license information. /// /// /// - Update to all cyclic progress bar changes. /// /// /// - 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. /// /// /// - Added new states. /// /// /// - Added new states. /// 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(); } /// /// Timing measurement to keep user informed about expired time. /// /// /// - Initial /// /// /// - UiInvoker as timer is now in separate task calling this routine. /// 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 ------------------------------------------------- /// /// 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 simple data type will be passed and /// then converted to the complex type. /// /// /// /// true if succeeded /// /// - Initial /// /// /// - Set process state to validate the software if this message comes in. /// /// /// - Set path to FW-Update SW directory to take all DLLs from there. /// /// /// - Software license object at incoming license information. /// /// /// - Single FW-package. /// /// /// - Single FW-package. /// /// /// - Added FW update safe information for report /// // 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(serialJsonStream); return true; case DataContainerName.CordonelDeviceInfo: _cordonelDeviceInfos.Add(JsonConvert.DeserializeObject(serialJsonStream)); return true; case DataContainerName.SoftwareLicense: _softwareLicense = new SoftwareLicense(); _softwareLicense = JsonConvert.DeserializeObject(serialJsonStream); _processState = ProcessState.ValidateSoftware; return true; case DataContainerName.UpdatePackageBasePath: _basePath = JsonConvert.DeserializeObject(serialJsonStream); return true; case DataContainerName.UpdatePackage: _cordonelFirmwarePackage = JsonConvert.DeserializeObject(serialJsonStream); return true; default: InfoProcessFailed(null, Resources.StrDataContainerUnknown); break; } return false; } #endregion --------------------------------------- Interfaces ------------------------------------------------- #region ------------------------------------------ Form Load Unload ------------------------------------------- /// /// Ctor FW update /// /// /// - Initial /// /// /// - 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. /// /// /// - Culture info fixed to English. /// /// /// - _cancellationToken. /// 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}.{_version.Revision}"); _logger.Info(StrSeparator); _meterBatch = new MeterBatch(); InitializeComponent(); _cultureInfo = Thread.CurrentThread.CurrentCulture; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; _processTokenSource = new CancellationTokenSource(); _processToken = _processTokenSource.Token; radioBtnEnglishLanguage.Checked = true; if (_cultureInfo.IetfLanguageTag == "de-DE") { radioBtnGermanLanguage.Checked = true; } // switch always to English RadioBtnEnglishLanguage_Click(this, null); 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); } /// /// Logout from Genesis and dispose it to force a new read of registers. /// /// /// - Initial /// /// /// - Release display to normal operation, /// - switch measurement LED off. /// /// /// - Release display removed. /// private void DisposeGenesis() { _currentGenesis?.Logout(); // Batch removal disposes the meter! _meterBatch?.RemoveAllMeters(); _currentGenesis = null; } /// /// Reset all status labels, clear data table and set genesis to not connected. /// /// /// - Initial /// /// /// - Cleaned for thread safe access to labels and controls. /// /// /// - Removed clear history. /// /// /// - Split label reset to pre-update and post-update to keep the pre-update information. /// /// /// - Used UiInvoker /// /// /// - Removed button settings. /// /// /// - Disabled language selection, culture info fixed to english. /// 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); UiInvoker.ControlEnableInvoker(grpLanguageSelection, 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(); } /// /// Clear history window. /// /// /// - Initial /// 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; } /// /// Finalize update and clean up. Bring the display back to normal operation and switch the LED off. /// /// /// - Added LED off. /// /// /// - Login before display back to normal operation. /// /// /// - Release display removed. /// /// /// - _cancellationToken. /// private void FrmFwUpdate_FormClosing(Object sender, FormClosingEventArgs e) { _cancellationTokenSource?.Cancel(); _processTokenSource?.Cancel(); _frmHistory?.Close(); _frmHistory?.Dispose(); // 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 ----------------------------------------- /// /// Build data grid and fill it with meter and file information, /// compare version and CRC of meter and file /// 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(); })); } /// /// Set meter application information to data grid view /// /// 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) : ""; } } /// /// Set file application information to data grid view. /// /// 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}"; } } } /// /// Display the update information /// /// /// - MeterAppState.FileAppInvalid changed from _styleDownloadOngoing to _styleNotInstalled /// /// /// - New style for communication error /// 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 ----------------------------------------------------- /// /// Loading update package ABC and description file ADF. /// /// /// /// /// - Initial. /// /// /// - Error messages exported. /// /// /// - 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. /// /// /// - Update app installation status. /// 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(); foreach (var app in _cordonelFirmwarePackage.BinaryApplicationFiles.Where(f => f.FileName.EndsWith(".bin"))) { var fileApplication = new FileApplications(app.FileName) { BinData = new List(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; } } /// /// Validate the version and the expiration date of the software. /// /// /// /// /// - Initial based on previous implementation of CheckUpdateEnabled(). /// /// /// - Duty Date implemented. /// /// /// - Enable [Connect] and [Port Scan] if license is validated. /// /// /// - Software license element naming adapted to DB content. /// 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; } /// /// State call to check the update capability. /// /// /// /// /// - Initial based on previous implementation of CheckUpdateEnabled(). /// /// /// - Check metrology update capability. /// /// /// - Check region radio. /// /// /// - Exported CheckPackageFileAndFileApps to CheckUpdateRequest to separate capability check from /// applications are up-to-date. /// 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(); } /// /// State call to check for FW-Update request. /// /// update is needed /// skip update /// incompatible update in safe /// /// - Initial based on previous implementation of CheckUpdateEnabled(). /// /// /// - Check for communication issues to avoid wrong information on installed apps. /// 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; } })); } /// /// Correct power overestimation at FW change from /// , /// . /// Precondition: /// Meter registers have to be read in advance, /// Meter files have to be read in advance. /// /// /// /// /// - Initial. /// 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 and build a string list of the file content powCorrFile.ReadPowCorrMeterFile(); LogStringList(Resources.StrPowerCorrection, powCorrFile.FileContent, Resources.StrPowerCorrectionNotNeeded); _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 // using the preset _fwUpdatePowerCorrection.ActualFwVersion on initial connect as after // the FW update this is lost in _currentGenesis.InstalledFwVersion and overwritten by the // new version after the FW update! // REMIND: Update only allowed if the actual FW version is higher or equal to threshold (Update succeeded) if (_fwUpdatePowerCorrection != null && ((_currentGenesis.Region == "EMEA" && _cordonelFirmwarePackage.FwPackageInfo.Version >= PowCorrConst.EmeaFwThresholdForPowCorr && _fwUpdatePowerCorrection.ActualFwVersion < PowCorrConst.EmeaFwThresholdForPowCorr && _currentGenesis.InstalledFwVersion >= PowCorrConst.EmeaFwThresholdForPowCorr) || (_currentGenesis.Region == "NA" && _cordonelFirmwarePackage.FwPackageInfo.Version >= PowCorrConst.NaFNaFwThresholdForPowCorr && _fwUpdatePowerCorrection.ActualFwVersion < PowCorrConst.NaFNaFwThresholdForPowCorr && _currentGenesis.InstalledFwVersion >= PowCorrConst.NaFNaFwThresholdForPowCorr))) { // CASE 2.1: Missing power correction settings from FW update package // preset meter power correction file _fwUpdatePowerCorrection.RequiredFwVersion = (UInt32?)_cordonelFirmwarePackage.FwPackageInfo.Version; // estimate the power correction values which includes the pulse adapter installation marker and // the readout of Cordonel registers to get the actual status var powCorrCalc = new PowerCorrectionCalc(_currentGenesis, _fwUpdatePowerCorrection); var retVal = powCorrCalc.PowerCorrEstimation(); if (retVal) { // pulse mode was switched off, skip power correction if (!_fwUpdatePowerCorrection.PulseAdapterMarkedAsInstalled) { InfoProcessSuccess(null, Resources.StrPowerCorrectionNotNeeded); _processState = successExitState; return; } // correct the total used charge in meter including store configuration retVal = powCorrCalc.CorrectPowerConsumption(); if (retVal) { // store results to "1\\powcorr" in the meter powCorrFile.WritePowCorrMeterFile(_fwUpdatePowerCorrection); } } // one step of the power correction execution failed if (!retVal) { InfoProcessFailed(null, Resources.StrPowerCorrectionFailed); _processState = errorExitState; return; } }// check for region and required FW version threshold else // power correction mustn't be performed as the jump in version is not given { InfoProcessSuccess(null, Resources.StrPowerCorrectionNotNeeded); _processState = successExitState; return; } // log result of successfully executed power correction LogStringList(Resources.StrPowerCorrection, powCorrFile.FileContent, Resources.StrPowerCorrectionSucceeded); _processState = successExitState; } /// /// Calculate the consumed power and remaining lifetime. /// /// /// /// /// - Initial. /// 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, out _)) { var lifeTimeInfo = new List { $"{Resources.StrLifeTimeDrainedBattery} {status.DrainedBatteryLoadPercent:F2} %", $"{Resources.StrLifeTimeRemainingYears} {status.RemainingLifeTimeYears:F2} " + $"{Resources.StrLifeTimeYears}" }; LogStringList(Resources.StrLifeTimeCalculation, lifeTimeInfo, Resources.StrLifeTimeCalculationSucceeded); _processState = successExitState; } else { InfoProcessFailed(null, Resources.StrLifeTimeCalculationFailed); _processState = errorExitState; } } /// /// Core revision check /// /// true if core revision is valid /// /// - Initial /// /// /// - Moved success message to caller. /// /// /// - Success message implemented. /// /// /// - Core revision data type changed from string ti int32?. /// 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 { @"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; } /// /// Metrology update capability check /// /// true if core revision is valid /// /// - Initial. /// /// /// - Success message implemented. /// /// /// - Success message implemented. /// private Boolean MetrologyUpdateCapability() { // check the update capability if (MeterFwUpdateCapability.CheckMetrology(_meterFwUpdate, _currentGenesis)) { if (_currentGenesis.MetrologyUpgradePermission == MetrologyDefinition.MetrologyUpgradePermitted) { InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityMetrologyUpdateable); } else InfoProcessSuccess(null, Resources.StrCordonelUpdateCapabilityMetrologySucceeded); return true; } InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelUpdateCapabilityMetrologyFailed); _processState = ProcessState.Error; return false; } /// /// 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! /// /// true if during app detection every message returns a response /// /// - Initial. /// private Boolean AllAppsDetected() { // check the update capability if (_currentGenesis.MeterAppListVersion.All(f => f.Status != MeterAppState.Unknown)) return true; InfoProcessFailed(lblCordonelUpdateCapability, Resources.StrCordonelCommunicationFailed); return false; } /// /// 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 this information is needed /// for this update capability check. /// /// true all checks are approved and valid /// /// - Initial. /// /// /// - Starting with FW 1.2.* all frequencies will be supported, removed frequency check, /// - Supported China as new region. /// /// /// - Region, RadioFrequencyMhz, MeterSize taken from fw package info instead of package name, /// - Meter sizes list taken into account. /// /// /// - Meter sizes check exported to separate routine. /// 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 { $"{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 { 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; } /// /// 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 this information is needed /// for this update capability check. /// /// true if meter size is approved /// /// - Initial. /// 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 { 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; } /// /// Compare the package information file and package content with installed applications /// /// /// - Initial /// /// /// - Control view invoker added. /// /// /// - Used UiInvoker /// /// /// - Name of fw package taken from fw package info. /// 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 ------------------------------------------- /// /// Establish connection to water-meter /// Avoid port scan if meter has been detected. /// /// /// /// /// - Initial /// /// /// - _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. /// /// /// - _cancellationToken. /// private void BtnConnect_Click(Object sender, EventArgs e) { // Wait for cancellation if (_cancellationToken.IsCancellationRequested) { Thread.Sleep(500); // Reset the cancellation request _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; } ClearHistoryWindow(); // 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; } /// /// Repeat port scan on changed interface. /// This removed the current meter from lists /// /// /// /// /// - Initial /// 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); } /// /// User request to stop all operations /// /// /// /// /// - Initial /// /// /// - _cancellationToken. /// private void BtnStop_Click(Object sender, EventArgs e) { _cancellationTokenSource?.Cancel(); _processState = ProcessState.Stop; } /// /// Automatic update of entire FW /// /// /// /// /// - Initial /// /// /// - Start of update procedure changed to read files to erase and restore. /// /// /// - _cancellationToken. /// private void BtnUpdateFw_Click(Object sender, EventArgs e) { if (_currentGenesis == null || _meterFwUpdate?.FileApps == null || _registerRestorer == null) return; // Wait for cancellation if (_cancellationToken.IsCancellationRequested) { Thread.Sleep(500); // Reset the cancellation request _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; } _startTime = DateTimeOffset.UtcNow; _resetTimeMeasurement = false; _processState = ProcessState.ReadInfoEraseRestoreFiles; } /// /// Open/Toggle history window /// /// /// - Initial /// private void BtnHistory_Click(Object sender, EventArgs e) { Thread.CurrentThread.CurrentUICulture = _cultureInfo; Thread.CurrentThread.CurrentCulture = _cultureInfo; 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; } } /// /// Release Cordonel display from"Idle" to normal operation /// /// /// - Initial /// private void BtnReleaseDisplay_Click(Object sender, EventArgs e) { ReleaseDisplayAndSwitchLedOff(); } #endregion --------------------------------------- User Interaction ------------------------------------------- #region ------------------------------------------ Tools ------------------------------------------------------ /// /// Logging of password file read from meter. /// /// /// - Initial. /// /// /// - Output message changed to get colored error or success messages. /// /// /// - Message for uninstalled password file moved from ExecConnect to here. /// /// /// - Avoid comparison if password file is not in FW-Update safe. /// 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(); 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; } /// /// 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. /// /// /// - Initial. /// /// /// - LUT file invalid as it cannot be read. /// 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(); 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; } /// /// Set data and time. /// /// /// - Initial. /// /// /// - Avoid to set up IRDA_MfgDate as this is the manufacturing date and not the FW-Update date! /// 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; } /// /// Activate the radio in customer mode with logging of success or failed. /// /// /// - Initial. /// /// /// - Reworked with new information from Joerg Lachenmayer: /// 1. Write 0xff, /// 2. logout, /// 3. Wait 1000 ms, /// 4. login /// 5. read if not 0x01. /// /// /// - Handle SystemState 0 = OFF /// /// /// - Store configuration of radio settings /// 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(_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(_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; } /// /// 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. /// /// /// - Initial, sequence of deactivation based on code from Roland Drabesch. /// 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(_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>(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; } /// /// Restore all listed pulse mode registers. /// /// /// - Initial /// 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; } /// /// Store configurations of all applications with logging of success or failed. /// /// /// - Initial. /// 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; } /// /// Release display and switch measurement LED off. /// /// /// - Initial. /// /// /// - 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! /// /// /// - Additional message of LED off /// 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; } /// /// Store all made settings and release meter to normal operation. /// /// /// - Initial. /// private Boolean ReleaseMeterToNormalOperation() { var retVal = ReleaseDisplayAndSwitchLedOff(); retVal &= RestorePulseMode(); SetOverallProgressDisplayOff(); SetDisableDownloadAndEnableConnect(); return retVal; } /// /// Logging of string lists. /// /// Headline for comment /// string list /// optional message if process succeeded /// optional message if process failed /// /// - Output message changed to get colored error or success messages. /// /// /// - LogErrorText. /// private void LogStringList(String strHeader, IEnumerable 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); } /// /// Logging of installed meter firmware and meter information. /// /// /// - Added application list file handling as json file: One file with all new installed versions. /// /// /// - Added region and radio. /// /// /// - Core revision data type changed from string ti int32?. /// /// /// - LUT CRC and MeterSize added. /// /// /// - FW version added. /// 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(); // 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 == MetrologyDefinition.MetrologyName && _currentGenesis.MetrologyUpgradePermission != MetrologyDefinition.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); } } /// /// Take the process state from a sorted list of sequenced processes. /// /// /// - Initial. /// private ProcessState GetNextProcessState(ProcessState invokerProcessState, IReadOnlyList 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; } /// /// Common message window. /// /// /// - Control view invoker added. /// /// /// - Force main form being TopMost to see the message box on top. /// /// /// - Used UiInvoker /// /// /// - Forcing message box being modal and on top. /// /// /// - Return user selection. /// 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); } /// /// Output exclusively to user update remarks text window. /// /// /// - Color added. /// private void LogText(String txtHistory) { InfoWindowColoredText(txtHistory, ColorDefault); _logger.Info(txtHistory); ReportText(txtHistory); } /// /// Output exclusively to user update remarks text window. /// private void LogErrorText(String txtHistory) { InfoWindowColoredText(txtHistory, ColorProcessFailed); _logger.Info(txtHistory); ReportText(txtHistory); LogText(StrSeparator); } /// /// Output exclusively to user update remarks text window. /// private void LogWarningText(String txtHistory) { InfoWindowColoredText(txtHistory, ColorWarning); _logger.Info(txtHistory); ReportText(txtHistory); LogText(StrSeparator); } /// /// Output exclusively to user update remarks text window. /// private void LogSuccessText(String txtHistory) { InfoWindowColoredText(txtHistory, ColorSuccess); _logger.Info(txtHistory); ReportText(txtHistory); LogText(StrSeparator); } /// /// Output exclusively to user update remarks text window. /// 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(); })); } /// /// Output to user update remarks text window and report file. /// /// /// - Additional use for report file. /// 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); } } /// /// Neutral information to label, user text box and logger. /// /// /// - Used UiInvoker /// private void InfoStatusUnknown(Control label, String msg, Boolean userTextOutput = false) { if (userTextOutput) LogText(msg); UiInvoker.ControlInvoker(label, ColorUnknownStatus, msg); } /// /// Success information to label, user text box and logger. /// /// /// - Used UiInvoker /// private void InfoProcessActive(Control label, String msg, Boolean log = true) { if (log) { LogText(msg); } UiInvoker.ControlInvoker(label, ColorOngoingProcess, msg); } /// /// Success information to label, user text box and logger. /// private void InfoProcessSuccess(Control label, String msg, Boolean log = true) { if (log) { LogSuccessText(msg); } UiInvoker.ControlInvoker(label, ColorSuccess, $@"{SuccessSign} {msg}"); } /// /// Success information to label, user text box and logger. /// private void InfoProcessWarning(Control label, String msg, Boolean log = true) { if (log) { LogWarningText(msg); } UiInvoker.ControlInvoker(label, ColorWarning, $@"{WarningSign} {msg}"); } /// /// Failed information to label, user text box and logger. /// /// /// - Used UiInvoker /// private void InfoProcessFailed(Control label, String msg, Boolean log = true) { if (log) { LogErrorText(msg); } UiInvoker.ControlInvoker(label, ColorProcessFailed, $@"{FailedSign} {msg}"); } /// /// Action on FW-Update success /// /// /// - Initial /// /// /// - Control invoker implemented. /// 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); } /// /// Common routine to enable download last control gets focus /// /// /// - Used UiInvoker /// /// /// - Disabled language selection, culture info fixed to english. /// private void SetControlDownloadEnable() { UiInvoker.ControlEnableInvoker(grpLanguageSelection, false); UiInvoker.ControlEnableInvoker(btnStop, false); UiInvoker.ControlEnableInvoker(btnReleaseDisplay, true); UiInvoker.ControlEnableInvoker(btnPortScan, true); UiInvoker.ControlEnableInvoker(btnConnect, true); // set focus UiInvoker.ControlEnableInvoker(btnUpdateFw, true); } /// /// Common routine to lock download last control gets focus /// /// /// - Used UiInvoker /// /// /// - Enable "release Display" if Cordonel is connected /// /// /// - Disabled language selection, culture info fixed to english. /// private void SetDisableDownloadAndEnableConnect() { UiInvoker.ControlEnableInvoker(grpLanguageSelection, false); 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); } /// /// Common routine to set controls during register access with water meter /// /// /// - Used UiInvoker /// 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); } /// /// Common routine to set controls during communication with water meter /// last control gets focus /// /// /// - Used UiInvoker /// 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); } /// /// Common routine to activate overall process bar and text /// /// /// - Used UiInvoker /// private void SetOverallProgressDisplayOn(String startText = "") { _timerDisplayOn = true; _progressBarValueCounter = 0; UiInvoker.ProgressBarInvoker(barOverallProgressUpdate); UiInvoker.ControlInvoker(lblOverallProcess, ColorDefault, startText); } /// /// Common routine to hide overall process bar and text. /// /// /// - Used UiInvoker /// private void SetOverallProgressDisplayOff() { _timerDisplayOn = false; _progressBarValueCounter = 0; UiInvoker.ControlViewInvoker(barOverallProgressUpdate, false); UiInvoker.ControlInvoker(lblWaitingForMeterResponse, ColorProcessFailed, visible: false); UiInvoker.ControlViewInvoker(lblOverallProcess, false); } /// /// Common routine to activate actual process bar and text /// /// /// - Used UiInvoker /// private void SetActualProgressDisplayOn(String startText = "") { UiInvoker.ProgressBarInvoker(barActualProgressUpdate); UiInvoker.ControlInvoker(lblActualProcess, ColorDefault, startText); } /// /// Common routine to hide actual process bar and text /// /// /// - Used UiInvoker /// private void SetActualProgressDisplayOff() { UiInvoker.ControlViewInvoker(barActualProgressUpdate, false); UiInvoker.ControlViewInvoker(lblActualProcess, false); } #endregion --------------------------------------- Tools ------------------------------------------------------ #region ------------------------------------------ Common Process Routines ------------------------------------ /// /// Common routine to convert objects to process states. /// Objects are sorted: /// 1. successState /// 2. errorState /// 3. breakState /// /// input of object array /// success state, default set to idle /// error state, default set to error /// break state, default set to stop /// /// - Initial /// private static void ObjectsToProcessStates(IReadOnlyList 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]; } /// /// Error processes. /// /// /// - Initial /// /// /// - Error message dispatcher. /// /// /// - Additional messages. /// /// /// - Additional messages. /// /// /// - Password file restore added. /// /// /// - Dispose meter at stop during port scan to avoid lock of port. /// /// /// - Disable download on selected errors instead of all. /// /// /// - Enable download on previously failed update procedure as this has been check in advance. /// /// /// - Region radio message. /// /// /// - Meter size message. /// /// /// - Meter size message. /// /// /// - Meter LUT message. /// /// /// - Error state decides what to do next. /// /// /// - If LUT restore generates an error all processes have to be stopped as the meter is NOT functional. /// /// /// - Communication error on app detection. /// /// /// - Added file access events. /// 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: case ProcessState.PreUpdateReadMeterFiles: msg = Resources.StrFileReadoutFailed; ErrorProcessCommon(msg); InfoProcessFailed(lblFileErase, Resources.StrFileReadoutFailed, false); // 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); InfoProcessFailed(lblFileRestore, Resources.StrFileReadoutFailed, false); _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; case ProcessState.EraseMeterFiles: msg = Resources.StrFileReadoutFailed; ErrorProcessCommon(msg); InfoProcessFailed(lblFileErase, Resources.StrFileReadoutFailed); _processState = ProcessState.ReleaseMeterToNormalOperation; 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; } } /// /// 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. /// /// message to display and log /// /// /// user selection of message window /// /// - Initial /// 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); } /// /// Stop all ongoing processes. /// /// /// - Initial /// /// /// - Break message dispatcher. /// /// /// - Check update capability state required on exit. /// /// /// - Switch depending on process to create correct message. /// /// /// - Dispose meter at stop during port scan to avoid lock of port. /// /// /// - Enable [Connect] and [Port Scan], /// - _processState to idle. /// /// /// - Removed registerRestorer.StopRegisterAccess. /// 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 (_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; } } /// /// Common routine to process update event to display and log results /// /// /// /// /// - Initial /// /// /// - Take all senders for processing bar and text output. /// /// /// - Change to log text for RegisterRestorer and FW-Update to fill report file. /// /// /// - Removed log for FW-Update process as this gives no additional useful information. /// 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); } /// /// Update of overall process bar and text output /// /// /// /// - Initial /// /// /// - Invoker implemented. /// /// /// - Used UiInvoker /// 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); } /// /// Update of single process bar and text output /// /// /// /// - Initial /// /// /// - Invoker implemented. /// /// /// - Used UiInvoker /// 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 ----------------------------------------------------- /// /// Update the firmware and restart the water-meter. /// INFO: Takes a lot of time until the water-meter answers again. /// /// /// /// /// /// - Changed port scanner call using state responses after execution. /// /// /// - _cancellationToken. /// 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, null, ExecReboot, FinalizeReboot, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of reboot procedure. /// /// /// - Initial /// private void InitReboot() { SetOverallProgressDisplayOn(Resources.StrUpdateAppAndRestartMeter); _timerIntervalExpired = false; } /// /// Execution of reboot. /// /// true if successfully executed /// /// - Initial /// /// /// - Timer interval expired introduced. /// private StatusReturn ExecReboot() { String text = null; 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( _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; } /// /// Finalization of reboot. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Taking objects as representative for finalizing process states. /// /// /// - Using success from execute routine /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Set date and time. /// /// /// - Check all installed meter apps. /// /// /// - Request new pcbId as the auto-detected may be corrupt. /// private void FinalizeReboot(StatusReturn success, IReadOnlyList 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 -------------------------------------------------- /// /// 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! /// /// /// /// /// /// - Initial /// /// /// - Changed port scanner call using state responses after execution. /// /// /// - _cancellationToken. /// 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, null, ExecPortScanner, FinalizePortScanner, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of port scanner. /// /// /// - Initial /// 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; } /// /// Execution of port scanner. /// /// true if successfully executed /// /// - Initial /// private StatusReturn ExecPortScanner() { _serialPortScanner.ScanAllSerialPorts(Slot, _basePath?.PortConfigFilePathName); return _serialPortScanner.AutoDetectedPortName == null ? StatusReturn.Failed : StatusReturn.Okay; } /// /// Finalization of port scanner. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Initial /// /// /// - Taking objects as representative for finalizing process states. /// /// /// - Using success from execute routine /// /// /// - Reworked exit state. /// /// /// - Result display moved to here. /// /// /// - Removed additional stop user stop message, /// - Dispose port scanner. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Delay after port scan to free serial port for new assignment. /// private void FinalizePortScanner(StatusReturn success, IReadOnlyList 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 ---------------------------------------------------- /// /// Establish connection to genesis water meter /// /// /// /// /// /// - Initial /// /// /// - Changed port scanner call using state responses after execution. /// /// /// - _cancellationToken. /// 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, null, ExecConnect, FinalizeConnect, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of connect to water-meter. /// /// /// - Initial /// /// /// - Delay at start up to allow dispose for eventually assigned port from scanner. /// private void InitConnect() { SetControlsAllButtonsDisabled(); 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; } SetOverallProgressDisplayOn(Resources.StrConnecting); } /// /// 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 , /// - 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. /// /// true if successfully executed /// /// - Initial /// /// /// - Returns true if _currentGenesis is logged on. /// /// /// - 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. /// /// /// - If login with password Level 8 failed, keep delay and try login with skeletonKey. /// /// /// - Reset marker for password corruption. /// /// /// - Display information if Genesis is not in update list (password = null and skeletonKey = null). /// /// /// - 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. /// /// /// - Execute even if still logged on to meter, /// - Store all configurations . /// /// /// - Switch radio on if not NA version. /// /// /// - "Release Display" before "Store Configuration". /// /// /// - "Deactivate Pulse Mode" at initial login to avoid interference of pulses to IrDA communication. /// /// /// - Create report file early to catch radio and pulse mode activity. /// /// /// - 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 , /// - 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. /// /// /// - Restructured. /// /// /// - Avoid report file generation on finalLoginAfterUpdate. /// /// /// - Message moved to . /// /// /// - Enabled final connect with passwordLvl8 and skeletonKey to enable installation of password file. /// /// /// - LoginDelay dynamically based on trials, reset after successfully login. /// /// /// - Added password valid information main screen and log file. /// /// /// - Added power correction on FW update to specific version. /// 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 log in 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; } /// /// 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. /// /// lvl8 or skeleton /// true if successfully executed /// /// - Initial /// /// /// - Date time setup, /// - Read and log meter password file. /// /// /// - Clean error collection messages on initial connect. /// /// /// - Retries on pulse mode switching off as this is very critical for a healthy communication and /// may cause unpredictable behaviour if it fails. /// /// /// - Fill data grid with all FW infos. /// /// /// - Changed program flow. /// /// /// - Update app information on new connect. /// /// /// - Remind installed FW version on initial connect to _fwUpdatePowerCorrection as this will be lost /// after FW update in _currentGenesis.InstalledFwVersion and set to the version after the update. /// /// /// - Dispose register restorer to get a new one on connect. This is essential for change of the Cordonel /// without closure of the program. /// 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(); // Dispose register restorer on new connection _registerRestorer = null; 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; } } //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); // 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 (_fwUpdatePowerCorrection == null) { //do something _fwUpdatePowerCorrection = new PowerCorrection(); } // fill all currently known infos to power correction _fwUpdatePowerCorrection.ActualPulseAdapterIsInstalled = _currentGenesis.PulseAdapterAutoDetected; _fwUpdatePowerCorrection.ActualFwVersion = _currentGenesis.InstalledFwVersion; 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; } /// /// Finalize of connection process to water-meter. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Initial /// /// /// - Taking objects as representative for finalizing process states. /// /// /// - Using success from execute routine /// /// /// - Reworked exit state. /// /// /// - Label corrected for user stop. /// /// /// - Added Cordonel detected. /// /// /// - Removed additional stop user stop message. /// /// /// - Log information if password file is not installed. /// /// /// - Display information if Genesis is not in update list. /// /// /// - Used UiInvoker /// /// /// - Build report files added. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Avoid report of versions on final login after update, this is already reported. /// /// /// - Final login message produced to log password file validation. /// /// /// - Enable connect if error. /// /// /// - Report file generation moved to . /// /// /// - Log always installed meter FW. /// /// /// - Final login is not a grant for valid password file anymore. /// private void FinalizeConnect(StatusReturn success, IReadOnlyList 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 ---------------------------------------------- /// /// Execution of register read. /// /// /// /// /// /// - Initial /// /// /// - Changed port scanner call using state responses after execution. /// /// /// - _cancellationToken. /// 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, null, ExecReadRegister, FinalReadRegisters, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of register backup. /// /// /// - Initial /// /// /// - Activity message moved to ExecReadRegister() /// private void InitReadRegister() { SetControlsAllButtonsDisabled(); _registerRestorer.OnProcessUpdate += ProcessUpdate_Event; } /// /// Execution of register read. /// /// true if successfully executed /// /// - Initial /// /// /// - Initial /// /// /// - Skip final read and compare if not updated or no recovery request. /// /// /// - Status unknown implemented, /// - activity message moved from InitReadRegister() /// /// /// - _cancellationToken. /// private StatusReturn ExecReadRegister() { Boolean retVal; switch (_invokerProcessState) { case ProcessState.InitialReadRegisters: SetOverallProgressDisplayOn(Resources.StrRegisterReadoutActive); InfoProcessActive(lblRegisterReadout, Resources.StrRegisterReadoutActive); retVal = _registerRestorer.InitialReadRegisters(_cancellationToken); break; case ProcessState.FinalReadRegisters: if (_afterUpdateConnect || _recoveryRegistersRequired) { SetOverallProgressDisplayOn(Resources.StrRegisterReadoutActive); InfoProcessActive(lblRegisterReadout, Resources.StrRegisterReadoutActive); retVal = _registerRestorer.FinalReadRegisters(_cancellationToken); } else { return StatusReturn.Unknown; } break; case ProcessState.CompareRegisters: if (_recoveryRegistersRequired) { SetOverallProgressDisplayOn(Resources.StrRegisterReadoutActive); InfoProcessActive(lblRegisterReadout, Resources.StrRegisterCompareActive); retVal = _registerRestorer.CompareRegisters(_cancellationToken); } else { return StatusReturn.Unknown; } break; default: SetOverallProgressDisplayOn(Resources.StrRegisterReadoutActive); InfoProcessActive(lblRegisterReadout, Resources.StrRegisterReadoutActive); retVal = _registerRestorer.ReadRegisters(_cancellationToken); break; } return retVal ? StatusReturn.Okay : StatusReturn.Failed; } /// /// Finalization of register reading. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Initial /// /// /// - Taking objects as representative for finalizing process states. /// /// /// - Using success from execute routine /// /// /// - Reworked exit state. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Context depending message. /// /// /// - Status unknown implemented. /// private void FinalReadRegisters(StatusReturn success, IReadOnlyList 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; } if (success == StatusReturn.Failed) { 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; } // Go ahead as it is nothing to do _processState = successExitState; // change first the process state, otherwise the timer may switch the bar on again SetOverallProgressDisplayOff(); } #endregion --------------------------------------- Register Read ---------------------------------------------- #region ------------------------------------------ File Access ------------------------------------------------ /// /// Read all meter files for erase and restore from file defined in FW-Update safe. /// /// /// /// /// - Initial. /// /// /// - Added password file to erase if password file is marked as corrupted. It has to be replaced!!!! /// 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(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; } } /// /// Reading all meter files and log the content of it. /// /// /// /// /// - Initial. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Output message changed. /// /// /// - If verify meter files succeeded the lblFileErase can be set to success as every file could be read. /// /// /// - ReLogin replaced with Logout, Delay 250 ms and Login. /// /// /// - Moved error events to errorProcesses. /// 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) { SetControlsAllButtonsDisabled(); 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(); _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; } } _processState = errorExitState; SetOverallProgressDisplayOff(); } /// /// Tries to erase unused files to free space for the FW-Update files. /// /// /// /// /// - Initial. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Output message changed to get colored error or success messages. /// /// /// - Moved error events to errorProcesses. /// 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) { SetControlsAllButtonsDisabled(); InfoProcessActive(lblFileErase, Resources.StrFileAccessActive); _currentGenesis.ReLogin(); // read all meter files var meterFile = new MeterFile(_currentGenesis); var erasedMeterFiles = new List(); var filesToErase = new List(); var wildcardList = new List(); // 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; } } } _processState = errorExitState; // change first the process state, otherwise the timer may switch the bar on again SetOverallProgressDisplayOff(); } /// /// Tries to install the erased files. /// /// /// /// /// - Initial. /// 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) { SetControlsAllButtonsDisabled(); InfoProcessActive(lblFileRestore, Resources.StrFileAccessActive); _currentGenesis.ReLogin(); var meterFile = new MeterFile(_currentGenesis); var restoredMeterFiles = new List(); // 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(); } /// /// Tries to install the password file if this is corrupted, Genesis has to be logged in. /// /// /// /// /// - Initial. /// /// /// - Message if succeeded. /// /// /// - Reset marker for password corruption. /// /// /// - 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 /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// /// /// - Mark for final login as this allows EXPLICIT login with password level 8 to validate password file. /// /// /// - Changed password file valid message. /// /// /// - Checked for password file not in update container. /// /// /// - Release display to normal operation, /// - switch measurement LED off. /// /// /// - Release display removed. /// /// /// - Hashed password file read on connect from FW update safe. /// /// /// - Read and log password file after restoring. /// /// /// - Removed check for password file length, leave it to the /// /// /// - Removed state change to final connect. /// 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)) { SetControlsAllButtonsDisabled(); _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(); } /// /// Reads the LUT and restores it if different from given LUT in FwUpdateSafe. /// /// /// /// /// - Initial. /// /// /// - For FW version without LUT file needed return with success state, /// - Reading initially the header to get the length of the LUT. /// /// /// - Taking meter response of invalid LUT file indicated by 0x8000xxxx. /// /// /// - Added check for string LutCrc is not null or empty. /// /// /// - Use installed FW version to decide if LUT is required. /// /// /// - Uses LUT CRC and file list for installation requirement. /// 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. LUT file is needed but invalid if (!string.IsNullOrEmpty(_currentGenesis.LutCrc) && _currentGenesis.LutCrc.Contains(MeterLutFile.StrLutFileInvalidCrc)) { InfoProcessFailed(lblLutFileCheck, Resources.StrLutFileNotInstalled); _processState = errorExitState; return; } // 2. 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)) && !_readMeterFiles.Any(f => f.Contains(MeterLutFile.StrLutBackupMeterFileName))) { // 2. 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; } // 2. 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; return; } // 3. LUT file is installed: // 3.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 // 3.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 ----------------------------------------- /// /// Execution of register recovery. /// /// /// /// /// /// - Initial /// /// /// - _cancellationToken. /// 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, null, ExecRegisterRecovery, FinalizeRegisterRecovery, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of register backup. /// /// /// - Initial /// /// /// - Moved process messages to ExecRegisterRecovery(). /// private void InitRegisterRecovery() { SetControlsAllButtonsDisabled(); _registerRestorer.OnProcessUpdate += ProcessUpdate_Event; } /// /// 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. /// /// /// true if successfully executed /// /// - Initial /// /// /// - Recovery registers at initial login. /// /// /// - Removed redundant marker for recovery required. /// /// /// - Moved process messages from InitRegisterRecovery(). /// /// /// - _cancellationToken. /// 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; InfoProcessActive(lblRegisterRestore, Resources.StrRegisterAccessActive); //set the process bars SetOverallProgressDisplayOn(Resources.StrRegisterAccessActive); retVal = _registerRestorer.WriteRegisters(device.RecoveryRegisters, _cancellationToken); break; } _recoveryRegistersRequired = true; Thread.Sleep(1000); if (!StoreConfigurations()) retVal = StatusReturn.Failed; } return retVal; } /// /// Finalization of register restore. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Initial /// /// /// - Taking objects as representative for finalizing process states.es /// /// /// - Using success from execute routine /// /// /// - Reworked exit state. /// /// /// - Change the process state before setting the SetOverallProgressDisplayOff as timer may reactivate it. /// private void FinalizeRegisterRecovery(StatusReturn success, IReadOnlyList 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 -------------------------------------------------- /// /// Execution of FW-Update. /// /// /// /// /// /// - Initial /// /// /// - Changed port scanner call using state responses after execution. /// /// /// - _cancellationToken. /// 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, null, ExecFwUpdate, FinalizeFwUpdate, _cultureInfo, exitProcessStateObjects, _cancellationToken); } /// /// Initialization of FW-Update. /// /// /// - Initial /// private void InitFwUpdate() { _meterFwUpdate.MaxPartialFileDataSize = MaxPartialFileDataSize; _meterFwUpdate.OnProcessUpdate += ProcessUpdate_Event; SetControlsCommunicationActive(); SetActualProgressDisplayOn(); SetOverallProgressDisplayOn(); InfoProcessActive(lblFirmwareUpdate, Resources.StrFirmwareUpdateOngoing); } /// /// Execution of FW-Update. /// /// true if successfully executed /// /// - Initial /// private StatusReturn ExecFwUpdate() { return _meterFwUpdate.UpdateMeterFw() ? StatusReturn.Okay : StatusReturn.Failed; } /// /// Finalization of FW-Update. /// /// successful execution /// objects containing process states sorted success-, error-, /// break exit state /// /// - Initial /// /// /// - Taking objects as representative for finalizing process states. /// /// /// - Using success from execute routine. /// /// /// - Reworked exit state. /// /// /// - Handle user break identical to error to return to the correct state for clean retry for just the FW update. /// private void FinalizeFwUpdate(StatusReturn success, IReadOnlyList exitProcessStateObjects) { if (_meterFwUpdate != null) _meterFwUpdate.OnProcessUpdate -= ProcessUpdate_Event; ObjectsToProcessStates(exitProcessStateObjects, out var successExitState, out var errorExitState, out _); SetActualProgressDisplayOff(); SetOverallProgressDisplayOff(); if (success == StatusReturn.Okay) { InfoProcessActive(lblFirmwareUpdate, Resources.StrFirmwareUpdateCheck); _processState = successExitState; return; } InfoProcessFailed(lblFirmwareUpdate, Resources.StrFirmwareUpdateFailed); _processState = errorExitState; } #endregion --------------------------------------- FW-Update -------------------------------------------------- #region ------------------------------------------ Reports ---------------------------------------------------- /// /// Build the report files for application list being installed and report log. /// /// /// - Initial /// /// /// - Changed function type as this is not time-consuming. /// - Implemented directory creation if it does not exist. /// /// /// - Common definition for report- and app version extensions. /// /// /// - Logging additional information: Order number, Customer, FwUpdateSw version, Date. /// 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}.{_version.Revision}"); 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 --------------------------------------------------- /// /// Select language at runtime: English /// /// /// - Initial /// /// /// - Change menu. /// /// /// - Avoid repetition if current culture is equal to required. /// private void RadioBtnEnglishLanguage_Click(Object sender, EventArgs e) { if (Thread.CurrentThread.CurrentCulture.Name == "en-GB") return; _cultureInfo = new CultureInfo("en-GB"); ChangeLanguageControls(); } /// /// Select language at runtime: German /// /// /// - Initial /// /// /// - Change menu. /// /// /// - Avoid repetition if current culture is equal to required. /// private void RadioBtnGermanLanguage_Click(Object sender, EventArgs e) { if (Thread.CurrentThread.CurrentCulture.Name == "de-DE") return; _cultureInfo = new CultureInfo("de-DE"); ChangeLanguageControls(); } /// /// Change language at runtime /// /// /// - Initial based on code example /// https://stackoverflow.com/questions/52178064/winforms-localization-how-to-change-the-language-of-a-menu. /// 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 --------------------------------------------------- } }