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