This commit is contained in:
Roland Drabesch
2024-02-12 15:20:55 +01:00
48 changed files with 878 additions and 803 deletions
+2 -2
View File
@@ -14,5 +14,5 @@ using System.Reflection;
//[assembly: AssemblyVersion("1.2.*.0")]
[assembly: AssemblyVersion("2.6.6.*")]
[assembly: AssemblyFileVersion("2.6.6.0")]
[assembly: AssemblyVersion("2.6.8.*")]
[assembly: AssemblyFileVersion("2.6.8.0")]
+1 -1
View File
@@ -155,7 +155,7 @@
</site>
<site name="MeterProcessState" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="C:\Users\drabesch_ro\Repo\lab\la_operations\laa_production\Common\Service\MeterProcessState" />
<virtualDirectory path="/" physicalPath="C:\Users\SZLATEV\source\repos\laa_production\Common\Service\MeterProcessState" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:56011:localhost" />
+1 -1
View File
@@ -28,7 +28,7 @@
<s:Boolean x:Key="/Default/Environment/ExcludedFiles/FilesAndFoldersToSkip/=1057AD21_002DBA22_002D4CAB_002DB3FE_002D88C5C9980E6A_002Fd_003AScripts_002Ff_003Ajquery_002D1_002E10_002E2_002Eintellisense_002Ejs/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/Hierarchy/Build/SolBuilderDuo/UseMsbuildSolutionBuilder/@EntryValue">NewVersion</s:String>
<s:Boolean x:Key="/Default/Environment/Hierarchy/EntityFrameworkOptions/IsAlreadyNotifiedAboutEntityFramework/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/Highlighting/HighlightingSourceSnapshotLocation/@EntryValue">C:\Users\Thomas\AppData\Local\Temp\JetBrains\ReSharperPlatformVs15\vAny_33007af5\CoverageData\_Common.-1885154465\Snapshot\snapshot.utdcvr</s:String>
@@ -1,13 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.Properties;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
using Xylem.Common.Logic.ProductionOrderCore;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Utils.ProcessExec;
using Xylem.Common.Utils.ProcessExec.EventArguments;
using Access = Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Access;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
{
@@ -24,49 +29,47 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
/// <summary>
/// PCB ID for pre update read for comparison
/// </summary>
private String _pcbIdInitialRegisterRead = "X";
private String _pcbIdInitialRegisterRead = Constants.StrUnknown;
/// <summary>
/// PCB ID for pre update write for comparison
/// </summary>
private String _pcbIdRecoveryRegisters = "X";
private String _pcbIdRecoveryRegisters = Constants.StrUnknown;
/// <summary>
/// PCB ID for post update for comparison
/// </summary>
private String _pcbIdFinalRegisterRead = Constants.StrUnknown;
/// <summary>
/// Collection of recovery registers and values
/// </summary>
public List<RecoveryRegisterItem> RegistersSetup;
/// <summary>
/// Register content on initial connect to keep those during update
/// </summary>
private readonly MeterRegisters _initialReadRegisters = new MeterRegisters();
private MeterRegisters _initialReadRegisters = new MeterRegisters();
/// <summary>
/// Register content after all operation had been performed
/// </summary>
private readonly MeterRegisters _finalReadRegisters = new MeterRegisters();
private MeterRegisters _finalReadRegisters = new MeterRegisters();
/// <summary>
/// Register which shall be restored after change of FW or after register recovery
/// </summary>
private readonly MeterRegisters _restoreRegisters = new MeterRegisters();
private readonly MeterRegisters _recoveryRegisters = new MeterRegisters();
/// <summary>
/// Register access event for message dispatcher to caller
/// </summary>
public event EventHandler<ProcessExecEventArgs> OnProcessUpdate;
/// <summary>
/// List for parameters which need to be removed from parameter list
/// </summary>
public static readonly List<String> ProhibitedParameters = new List<String>
{
"CUSTOMER_AppArrangement"
};
/// <summary>
/// List for un-reversed parameters, all others need to be swapped byte-wise
/// </summary>
public static readonly List<String> UnRevertedParameters = new List<String>
private static readonly List<String> _unRevertedParameters = new List<String>
{
"SENSUSRADIO_EncryptionKey",
"GENESISFLOW_DisplayPow10",
@@ -75,44 +78,72 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
/// <summary>
///
/// </summary>
public static readonly List<String> ExcludedFromCompareParameters = new List<String>
private static readonly List<String> _excludedFromCompareParameters = new List<String>
{
"SENSUSRADIO_UpgFWVersion" // After a FW-Update the FW version has changed and the compare will fail
"SENSUSRADIO_UpgFWVersion" // After a FW-Update the FW version has changed and the compare will fail
};
/// <summary>
/// Pre programming parameters for all devices defined here, will be filled with radio parameters
/// if meter has radio
/// </summary>
public static readonly List<ProgrammingParameters> PrepareProgramming = new List<ProgrammingParameters>
private static readonly List<RecoveryRegisterItem> _prepareProgramming = new List<RecoveryRegisterItem>
{
// the display needs to be sealed for change of unit and resolution
new ProgrammingParameters("GENESISFLOW_SealDisplay", new Byte[] { 0x00 })
// The display needs to be unsealed for change of unit and resolution
new RecoveryRegisterItem("GENESISFLOW_SealDisplay", new Byte[] { 0x00 })
};
/// <summary>
///
/// </summary>
// ReSharper disable once CollectionNeverQueried.Local IS FOR DEBUG
private readonly List<RecoveryRegisterItem> _failedComparisonRegisters = new List<RecoveryRegisterItem>();
/// <summary>
///
/// </summary>
// ReSharper disable once CollectionNeverQueried.Local IS FOR DEBUG
private List<String> _dbParameterRegisterList = new List<String>();
private readonly List<String> _recoveryRegisterList = new List<String>();
private List<String> _initialRegisterList = new List<String>();
private List<String> _finalRegisterList = new List<String>();
/// <summary>
/// Post programming parameters for radio only
/// </summary>
public static readonly List<ProgrammingParameters> FinalizeProgrammingRadioOnly = new List<ProgrammingParameters>
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingRadioOnly = new List<RecoveryRegisterItem>
{
new ProgrammingParameters("LOGGER_TriggerLogFlush", new Byte[] { 0x01 }),
new ProgrammingParameters("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }),
new RecoveryRegisterItem("LOGGER_TriggerLogFlush", new Byte[] { 0x01 }),
// set counter to 0 as this is a value how often the radio detected a reset
new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }),
// System state 0xFF will force a storage of all parameters and reset, followed by system state 0x02
// for customer mode, the reset needs only a few milliseconds
// Attention logout-delay-login required after this step!
new ProgrammingParameters("SENSUSRADIO_SystemState", new Byte[] { 0xFF })
new RecoveryRegisterItem("SENSUSRADIO_SystemState", new Byte[] { 0xFF })
};
/// <summary>
/// Post programming parameters for all devices
/// </summary>
public static readonly List<ProgrammingParameters> FinalizeProgramming = new List<ProgrammingParameters>
private static readonly List<RecoveryRegisterItem> _finalizeProgramming = new List<RecoveryRegisterItem>
{
new ProgrammingParameters("GENESISFLOW_SealDisplay", new Byte[] { 0x01 }),
new ProgrammingParameters("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 }),
new RecoveryRegisterItem("GENESISFLOW_SealDisplay", new Byte[] { 0x01 }),
// trigger idle 0 releases the display to normal operation
new RecoveryRegisterItem("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 }),
};
/// <summary>
/// Actual Port Counter
/// Post programming parameters for EOL (end of line, prepare for shipping workplace)
/// </summary>
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingEol = new List<RecoveryRegisterItem>
{
new RecoveryRegisterItem("GENESISFLOW_ResetAccumulators", new Byte[] { 0x01 }),
new RecoveryRegisterItem("NFC_EraseRMA", new Byte[] { 0x01 }),
new RecoveryRegisterItem("CUSTOMER_RebootCount", new Byte[] {0x00})
};
/// <summary>
/// Actual register counter
/// </summary>
public Int32 ActualRegisterCtr;
@@ -147,6 +178,122 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
_currentGenesis = genesisMeter;
}
/// <summary>
/// Collect programming parameters from DB.
/// </summary>
/// <param name="pcbId"></param>
/// <param name="recoveryRegisters"></param>
/// <returns>recovery registers</returns>
/// <remarks date="2021-Feb-01" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-10-19" author="Thomas Wiedebusch">
/// - Sort the registers from DB but keep the PrepareProgramming and FinalizeProgramming in the required sequence.
/// </remarks>
/// <remarks date="2023-10-25" author="Thomas Wiedebusch">
/// - Removed preparation and finalization of register recovery to remove sequencing as this will be done in the
/// RegisterRestorer by the FwUpdateSw worker giving the ability to log this in the report.
/// </remarks>
/// <remarks date="2024-01-17" author="Thomas Wiedebusch">
/// - Data base access imported to have a common interface for programming parameters for EOL (shipping) and CUST.
/// </remarks>
/// <remarks date="2024-01-29" author="Thomas Wiedebusch">
/// - Removed prohibited parameters.
/// </remarks>
public static Boolean DownloadProgrammingParametersFromDb(String pcbId, List<RecoveryRegisterItem> recoveryRegisters)
{
try
{
var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetProgrammingParameters?PcbID={pcbId}";
var ppJson = LocalWebRequest.GetRequest(url, 30000);
if (string.IsNullOrEmpty(ppJson))
{
return false;
}
var programmingParameters = JsonConvert.DeserializeObject<List<ProgrammingParameters>>(ppJson);
if (programmingParameters == null || programmingParameters.Count == 0)
{
return false;
}
// Temporary storage for sorting before assembling those registers with the preparation and
// finalization of programming and therefore building a sequence
var sortableProgrammingParameters = new List<ProgrammingParameters>();
foreach (var proPar in programmingParameters)
{
// reverse byte order for all parameters except those listed below
if (!string.IsNullOrEmpty(proPar.RegisterName))
{
// ATTENTION: These parameters are coming out of the DB in wrong order
if (_unRevertedParameters.Contains(proPar.RegisterName))
{
// Add parameter if it is not in the list
if (sortableProgrammingParameters.All(a => !proPar.RegisterName.Equals(a.RegisterName)))
{
sortableProgrammingParameters.Add(proPar);
continue;
}
// if parameter from VAKO is in the list and the new is from CSD, replace this with CSD
if (proPar.Source == ProgrammingSource.Csd && sortableProgrammingParameters.Any(a =>
proPar.RegisterName.Equals(a.RegisterName) && a.Source == ProgrammingSource.Vako))
{
var removeProPar =
sortableProgrammingParameters.Where(a => proPar.RegisterName.Equals(a.RegisterName));
sortableProgrammingParameters.Remove(removeProPar.LastOrDefault());
sortableProgrammingParameters.Add(proPar);
}
}
else
{
// Add parameter if it is not in the list
if (sortableProgrammingParameters.All(a => !proPar.RegisterName.Equals(a.RegisterName)))
{
sortableProgrammingParameters.Add(new ProgrammingParameters(proPar.RegisterName,
proPar.RegisterValue.Reverse().ToArray(), proPar.Source));
continue;
}
// if parameter from VAKO is in the list and the new is from CSD, replace this with CSD
if (proPar.Source == ProgrammingSource.Csd && sortableProgrammingParameters.Any(a =>
proPar.RegisterName.Equals(a.RegisterName) && a.Source == ProgrammingSource.Vako))
{
var removeProPar = sortableProgrammingParameters.Where(a => proPar.RegisterName.Equals(a.RegisterName));
sortableProgrammingParameters.Remove(removeProPar.LastOrDefault());
sortableProgrammingParameters.Add(new ProgrammingParameters(proPar.RegisterName,
proPar.RegisterValue.Reverse().ToArray(), proPar.Source));
}
}
} // register for this FW is defined
else
{
return false;
}
}
// Sort the programming parameters coming from DB (VAKO/CSD)
sortableProgrammingParameters.Sort((x, y) => string.Compare(x.RegisterName, y.RegisterName,
StringComparison.Ordinal));
// convert to RecoveryRegisterItem
if (sortableProgrammingParameters.Count > 0)
{
foreach (var proPar in sortableProgrammingParameters)
{
var recReg = new RecoveryRegisterItem(proPar.RegisterName, proPar.RegisterValue);
recoveryRegisters.Add(recReg);
}
}
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Avoid doubling of register restore on subsequent FW-Update trials.
/// </summary>
@@ -183,15 +330,28 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
}
/// <summary>
/// Recover all registers set from caller in the original sequence as they may prepare and finalize
/// settings.
///
/// 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.
///
/// EOL (end of line) programming contains a reset of the accumulators. Else it is identical with recover
/// registers.
/// </summary>
/// <param name="recoveryRegisters">list of registers to update</param>
/// <param name="recoveryRegistersDb">list of registers to update from data base without special sequence
/// </param>
/// <returns>true if setup succeeded</returns>
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public Boolean EolProgrammingRegisters(List<RecoveryRegisterItem> recoveryRegistersDb)
{
// mark EOL as true to force accu reset
return RecoverRegisters(recoveryRegistersDb, true);
}
/// <summary>
/// Recover all registers set from caller. Additional preparation and finalization will be added.
/// settings.
/// </summary>
/// <param name="recoveryRegistersDb">list of registers to update from data base without special sequence
/// </param>
/// <param name="eolProgramming">EOL (end of line) programming with accu reset</param>
/// <returns>true if restoring succeeded</returns>
/// <remarks date="2023-Oct-09" author="Thomas Wiedebusch">
/// - Initial.
@@ -210,16 +370,25 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
/// <remarks date="2023-Dec-11" author="Thomas Wiedebusch">
/// - Logging of some exception messages.
/// </remarks>
public Boolean RecoverRegisters(List<RecoveryRegisterItem> recoveryRegisters)
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
/// - EOL sequence for accumulators reset,
/// - Remind pcbId as already recovered before the true return,
/// - Exclude lock of repeated execution for EOL.
/// </remarks>
/// <remarks date="2024-Jan-29" author="Thomas Wiedebusch">
/// - Avoid writing of parameters if not writable. The DB delivers parameters which exclusively have to be
/// compared to the specified value.
/// </remarks>
public Boolean RecoverRegisters(List<RecoveryRegisterItem> recoveryRegistersDb, Boolean eolProgramming = false)
{
if (_currentGenesis == null ||
string.IsNullOrEmpty(_currentGenesis.PcbId) ||
recoveryRegisters == null ||
recoveryRegisters.Count == 0)
recoveryRegistersDb == null ||
recoveryRegistersDb.Count == 0)
return false;
// if already recovered skip this with positive feedback
if (_currentGenesis.PcbId == _pcbIdRecoveryRegisters)
// if not EOL processing and already recovered skip this with positive feedback
if (!eolProgramming && _currentGenesis.PcbId == _pcbIdRecoveryRegisters)
return true;
// remind pcbId of this run to avoid endless repetition
@@ -228,6 +397,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
StopRegisterAccess = false;
ActualRegisterCtr = 0;
// Build string list for debug
_dbParameterRegisterList.Clear();
_dbParameterRegisterList = recoveryRegistersDb.Select(register => register.RegisterIdent).ToList();
_dbParameterRegisterList.Sort();
// write values to Genesis
_currentGenesis.ReLogin();
if (!_currentGenesis.IsLoggedOn)
@@ -237,37 +411,44 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
var meterRegisters = _currentGenesis.GetConfigRegistersDefinitions();
// prepare list for finalization depending on SENSUSRADIO installation
var finalizeProgrammingAll = new List<ProgrammingParameters>();
finalizeProgrammingAll.AddRange(FinalizeProgramming);
if (recoveryRegisters.Any(x => x.RegisterIdent.Contains("SENSUSRADIO")))
var finalizeProgrammingAll = new List<RecoveryRegisterItem>();
if (recoveryRegistersDb.Any(x => x.RegisterIdent.Contains("SENSUSRADIO")))
{
finalizeProgrammingAll.AddRange(FinalizeProgrammingRadioOnly);
finalizeProgrammingAll.AddRange(_finalizeProgrammingRadioOnly);
}
// add EOL (shipping reset of accumulators, alarms and setup of manufacturing date)
if (eolProgramming)
{
finalizeProgrammingAll.AddRange(_finalizeProgrammingEol);
}
// standard end of programming sequence
finalizeProgrammingAll.AddRange(_finalizeProgramming);
// set the process counter
var recRegistersCount = recoveryRegisters.Count > 0 ? recoveryRegisters.Count : 1;
recRegistersCount += PrepareProgramming.Count;
var recRegistersCount = recoveryRegistersDb.Count > 0 ? recoveryRegistersDb.Count : 1;
recRegistersCount += _prepareProgramming.Count;
recRegistersCount += finalizeProgrammingAll.Count;
// prepare recovery
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryPreparation,
actualProcessMessage: Resources.StrRegisterRecoveryPreparation));
foreach (var prepRecReg in PrepareProgramming)
foreach (var prepRecReg in _prepareProgramming)
{
ActualRegisterCtr++;
var overallProcessCtrPercent = 100.0 * ActualRegisterCtr / recRegistersCount;
try
{
_currentGenesis.WriteRegister(prepRecReg.RegisterName, prepRecReg.RegisterValue);
_currentGenesis.WriteRegister(prepRecReg.RegisterIdent, prepRecReg.WriteValue);
// if this call does not throw an exception, the register is present
var regDef = meterRegisters.GetRegisterDefinitionByName(prepRecReg.RegisterName);
var regDef = meterRegisters.GetRegisterDefinitionByName(prepRecReg.RegisterIdent);
var strRawAndConvertedValue = "";
try
{
strRawAndConvertedValue = RegisterConverter.GetRegisterContentText(regDef, prepRecReg.RegisterValue);
strRawAndConvertedValue = RegisterConverter.GetRegisterContentText(regDef, prepRecReg.WriteValue);
}
catch (Exception)
{
@@ -294,29 +475,33 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
// register recovery
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryExec,
actualProcessMessage: Resources.StrRegisterRecoveryExec));
foreach (var recReg in recoveryRegisters)
foreach (var recReg in recoveryRegistersDb)
{
ActualRegisterCtr++;
var overallProcessCtrPercent = 100.0 * ActualRegisterCtr / recRegistersCount;
try
{
_currentGenesis.WriteRegister(recReg.RegisterIdent, recReg.WriteValue);
// if this call does not throw an exception, the register is present
var regDef = meterRegisters.GetRegisterDefinitionByName(recReg.RegisterIdent);
// avoid register write if not allowed
if (regDef.RegisterDetail.Privilege.Lvl8 != Access.RW)
continue;
_currentGenesis.WriteRegister(recReg.RegisterIdent, recReg.WriteValue);
var strRawAndConvertedValue = "";
try
{
strRawAndConvertedValue = RegisterConverter.GetRegisterContentText(regDef, recReg.WriteValue);
// replace value in _restoreRegister with the required recovery value
_restoreRegisters?.Set(regDef, recReg.WriteValue);
_recoveryRegisters?.Set(regDef, recReg.WriteValue);
}
catch (Exception ex)
{
_currentGenesis.WriteLog("Could not replace register value of " + recReg.RegisterIdent + "; " + ex.Message);
// nothing to do
}
finally
{
@@ -346,15 +531,15 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
try
{
_currentGenesis.WriteRegister(finalRecReg.RegisterName, finalRecReg.RegisterValue);
_currentGenesis.WriteRegister(finalRecReg.RegisterIdent, finalRecReg.WriteValue);
// if this call does not throw an exception, the register is present
var regDef = meterRegisters.GetRegisterDefinitionByName(finalRecReg.RegisterName);
var regDef = meterRegisters.GetRegisterDefinitionByName(finalRecReg.RegisterIdent);
var strRawAndConvertedValue = "";
try
{
strRawAndConvertedValue = RegisterConverter.GetRegisterContentText(regDef, finalRecReg.RegisterValue);
strRawAndConvertedValue = RegisterConverter.GetRegisterContentText(regDef, finalRecReg.WriteValue);
}
catch (Exception)
{
@@ -378,7 +563,14 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
}
}// finalize recovery
// remind pcbId of this run to avoid endless repetition
_pcbIdRecoveryRegisters = _currentGenesis.PcbId;
_currentGenesis?.Logout();
// Wait for completion as storing needs a certain time
Thread.Sleep(1000);
return true;
}
@@ -397,26 +589,30 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
/// "StaticType": “approximate”.
/// </remarks>
/// <remarks date="2023-Dec-08" author="Thomas Wiedebusch">
/// - Removed <see cref="ExcludedFromCompareParameters"/> as those will change on e.g. FW update.
/// - Removed <see cref="_excludedFromCompareParameters"/> as those will change on e.g. FW update.
/// </remarks>
/// <remarks date="2023-Dec-11" author="Thomas Wiedebusch">
/// - Avoid message event if message is empty.
/// </remarks>
/// <remarks date="2024-Jan-23" author="Thomas Wiedebusch">
/// - Remind registers which failed the comparison including values.
/// </remarks>
public Boolean CompareRegisters()
{
if (_pcbIdFinalRegisterRead != _pcbIdInitialRegisterRead ||
_restoreRegisters == null ||
_recoveryRegisters == null ||
_finalReadRegisters == null)
return false;
StopRegisterAccess = false;
ActualRegisterCtr = 0;
var retVal = true;
_failedComparisonRegisters?.Clear();
// build a string list of the registers being able to sort those
var registerNames = _restoreRegisters.MeterRegisterDic.Select(register => register.Key.GetIdent()).ToList();
var registerNames = _recoveryRegisters.MeterRegisterDic.Select(register => register.Key.GetIdent()).ToList();
// Remove all excluded registers
foreach (var regName in ExcludedFromCompareParameters.Where(regName => registerNames.Any(reg => reg == regName)))
foreach (var regName in _excludedFromCompareParameters.Where(regName => registerNames.Any(reg => reg == regName)))
{
registerNames.Remove(regName);
}
@@ -430,19 +626,19 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
{
ActualRegisterCtr++;
var regDef = _restoreRegisters.GetRegisterDefinitionByName(regName);
var regDef = _recoveryRegisters.GetRegisterDefinitionByName(regName);
var overallProcessCtrPercent = 100.0 * ActualRegisterCtr / registersCount;
try
{
// Get the raw byte array of this register
var restorableRawRegister = _restoreRegisters.Get(regName);
var restorableRawRegister = _recoveryRegisters.Get(regName);
var finalReadRawRegister = _finalReadRegisters.Get(regName);
// take the size into account as a read value will be filled up to a chunk of 4 bytes
var dataSize = RegisterConverter.SizeOf(regDef);
if (restorableRawRegister == null)
continue;
@@ -499,7 +695,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
if (restorableRawRegister[c] != finalReadRawRegister[c])
{
isIdentical = false;
break;
break;
}
isIdentical = true;
}
@@ -521,6 +717,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
strRequiredRegisterValue += $" - {Resources.StrMeterValue}: ({strReadBackRegisterValue})" +
$" - {Resources.StrRegisterCompareFailed}";
_failedComparisonRegisters?.Add(new RecoveryRegisterItem(regName, restorableRawRegister,
finalReadRawRegister));
retVal = false;
}
}
@@ -581,30 +779,26 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
if (_currentGenesis == null)
return false;
_pcbIdFinalRegisterRead = _currentGenesis.PcbId;
_pcbIdFinalRegisterRead = _currentGenesis.PcbId;
// clear the contents of the restore registers if the water meter backed up was not identical
if (_pcbIdInitialRegisterRead != _pcbIdFinalRegisterRead)
{
return false;
}
// get the pre-defined dictionary
var meterRegisters = _currentGenesis.GetConfigRegistersDefinitions();
// extract all registers which can be read and create two dictionaries for before update to safe program
// internally the register content for comparison ability.
foreach (var reg in meterRegisters.MeterRegisterDic.Where(r => r.Key.RegisterDetail?.Version.First != null
&& r.Key.RegisterDetail.Version.Last != null && r.Key.RestoreCapability.StaticTypeValue != null
&& r.Key.DataType != typeof(Rpc) && r.Key.IsAvailable &&
(r.Key.RegisterDetail.Privilege.Lvl8 == Access.RW || r.Key.RegisterDetail.Privilege.Lvl8 == Access.RO)))
{
_finalReadRegisters.MeterRegisterDic.TryAdd(reg.Key, reg.Value);
}
// create a dictionary for final read being able to compare those with the initial read and restored
if (!BuildReadableRegisterDictionary(ref _finalReadRegisters))
return false;
// log to report file the after update register read string
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
Resources.StrReadRegistersAfterUpdate));
// Build string list for debug
_finalRegisterList.Clear();
_finalRegisterList = _finalReadRegisters.MeterRegisterDic.Select(register => register.Key.GetIdent()).ToList();
_finalRegisterList.Sort();
// executes login read logout
return ReadRegistersSet(_finalReadRegisters);
}
@@ -645,13 +839,83 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
// deny update of registers if already done
if (_pcbIdInitialRegisterRead == _currentGenesis.PcbId &&
_initialReadRegisters.MeterRegisterDic.Count > 0 &&
_restoreRegisters.MeterRegisterDic.Count > 0)
_recoveryRegisters.MeterRegisterDic.Count > 0)
return true;
// remind the PCB ID before update for comparison
_pcbIdInitialRegisterRead = _currentGenesis.PcbId;
_initialReadRegisters.MeterRegisterDic.Clear();
_restoreRegisters.MeterRegisterDic.Clear();
// create the dictionaries out of the configuration.json
if (!BuildInitialDictionaries())
return false;
// log to report file the initial register read string
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
Resources.StrReadRegistersBeforeUpdate));
return ReadRegistersSet(_initialReadRegisters, _recoveryRegisters);
}
/// <summary>
/// Get DEFINED registers from GenesisMeter class and build a list all registers which are flagged
/// with read write (RW) or read only (RO). Build a list of all registers being able to restore flagged
/// with <see cref="StaticType"/> equals <see cref="StaticType.RestoreRequired"/>".
/// The defined registers are based on the "configuration.json" and the installed application with
/// a specific version.
/// </summary>
/// <returns>true if registers could be read</returns>
/// <remarks date="2024-Jan-22" author="Thomas Wiedebusch">
/// - Initial from <see cref="InitialReadRegisters"/>.
/// </remarks>
private Boolean BuildInitialDictionaries()
{
if (_currentGenesis == null)
return false;
_recoveryRegisters.MeterRegisterDic.Clear();
_recoveryRegisterList.Clear();
// create a dictionary for initial read being able to compare those with the final read and restored
if (!BuildReadableRegisterDictionary(ref _initialReadRegisters))
return false;
// take the pre-selected registers for final compare restore capability check even if the registers after
// the update may have changed, only registers which are present before the update can be restored!
foreach (var reg in _initialReadRegisters.MeterRegisterDic.Where(r =>
r.Key.RestoreCapability.CheckRestoreCapability() &&
r.Key.RegisterDetail.Privilege.Lvl8 == Access.RW))
{
_recoveryRegisters.MeterRegisterDic.TryAdd(reg.Key, reg.Value);
_recoveryRegisterList.Add(reg.Key.GetIdent());
}
_recoveryRegisterList.Sort();
// Build string list for debug
_initialRegisterList.Clear();
_initialRegisterList = _initialReadRegisters.MeterRegisterDic.Select(register => register.Key.GetIdent()).ToList();
_initialRegisterList.Sort();
return _recoveryRegisters.MeterRegisterDic.Count > 0 && _initialReadRegisters.MeterRegisterDic.Count > 0;
}
/// <summary>
/// Get DEFINED registers from GenesisMeter class and build a list all registers which are flagged
/// with read write (RW) or read only (RO).
/// The defined registers are based on the "configuration.json" and the installed application with
/// a specific version.
/// </summary>
/// <para name = "registers">all readable registers dictionary creation </para>
/// <returns>true if register dictionary is not empty</returns>
/// <returns>true if registers could be read</returns>
/// <remarks date="2024-Jan-22" author="Thomas Wiedebusch">
/// - Initial from <see cref="InitialReadRegisters"/>.
/// </remarks>
private Boolean BuildReadableRegisterDictionary(ref MeterRegisters registers)
{
if (_currentGenesis == null || registers?.MeterRegisterDic == null)
return false;
registers.MeterRegisterDic.Clear();
// get the pre-defined dictionary
var meterRegisters = _currentGenesis.GetConfigRegistersDefinitions();
@@ -663,23 +927,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
&& r.Key.DataType != typeof(Rpc) && r.Key.IsAvailable &&
(r.Key.RegisterDetail.Privilege.Lvl8 == Access.RW || r.Key.RegisterDetail.Privilege.Lvl8 == Access.RO)))
{
_initialReadRegisters.MeterRegisterDic.TryAdd(reg.Key, reg.Value);
registers.MeterRegisterDic.TryAdd(reg.Key, reg.Value);
}
// take the pre-selected registers for final compare restore capability check even if the registers after
// the update may have changed, only registers which are present before the update can be restored!
foreach (var reg in _initialReadRegisters.MeterRegisterDic.Where(r =>
r.Key.RestoreCapability.CheckRestoreCapability() &&
r.Key.RegisterDetail.Privilege.Lvl8 == Access.RW))
{
_restoreRegisters.MeterRegisterDic.TryAdd(reg.Key, reg.Value);
}
// log to report file the initial register read string
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
Resources.StrReadRegistersBeforeUpdate));
return ReadRegistersSet(_initialReadRegisters, _restoreRegisters);
return registers.MeterRegisterDic.Count > 0;
}
/// <summary>
@@ -412,12 +412,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
/// <remarks date="2023-Sep-01" author="T.Wiedebusch">
/// - HideDataInLog.
/// </remarks>
/// <remarks date="2024-Jan-24" author="T.Wiedebusch">
/// - Ignore wakeup if message already acknowledged (avoid to set
/// "_recordInProcess.Acknowledge = RequestAcknowledgeState.NotDecoded"),
/// - Avoid to overwrite "_recordInProcess.Acknowledge = RequestAcknowledgeState.Acknowledge" with
/// "RequestAcknowledgeState.WakeupMessage".
/// </remarks>
protected override void DecodeRecord(IPortDataEventArgs data)
{
if (_recordInProcess == null)
return;
//new request has to be analyzed
_recordInProcess.Acknowledge = RequestAcknowledgeState.NotDecoded;
//the request response record covers the entire request protocol
var responseRecord = GetTransmitProtocol().DecodeDataForLogicLayer(_ident, (List<Byte>)data.GetData(),
_recordInProcess.HideDataInLog);
@@ -433,9 +437,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
return;
}
//avoid wakeup retry on acknowledged record
if (responseRecord.Count == WakeupMessage.Length
&& RequestAcknowledgeState.Ok != _recordInProcess.Acknowledge)
if (responseRecord.Count == WakeupMessage.Length)
{
// check for wakeup message
var wakeUp = true;
@@ -447,12 +449,15 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
if (wakeUp)
{
_recordInProcess.Acknowledge = RequestAcknowledgeState.WakeupMessage;
//avoid wakeup retry on acknowledged record
if (RequestAcknowledgeState.Ok != _recordInProcess.Acknowledge)
_recordInProcess.Acknowledge = RequestAcknowledgeState.WakeupMessage;
//initiate a single retry on wakeup message response
if (_recordInProcess.WakeupMessageRetryCtr < 2)
{
_recordInProcess.WakeupMessageRetryCtr++;
_logger.Debug($"{_ident} Wakeup message({_recordInProcess.WakeupMessageRetryCtr}) received");
_logger.Debug(
$"{_ident} Wakeup message({_recordInProcess.WakeupMessageRetryCtr}) received");
}
}
else
@@ -461,6 +466,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
_recordInProcess.Acknowledge = RequestAcknowledgeState.DecodingError;
_logger.Debug($"{_ident} Message decoding error.");
}
// reset retry counter to get all required retries
_recordInProcess.RetryCtr = -1;
//reset timeout to a normal value if timeout is extremely high but response received
@@ -469,7 +475,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
return;
}
//log request protocol content
_logger.Debug(_recordInProcess.HideDataInLog
? $"{_ident} DecodedRecord(*****)"
@@ -549,20 +554,69 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
new RequestResponseDataEventArgs { RequestResponseData = responseRecord });
}
/// <summary>
/// Method to send basic Commands to Meter. Creates an integer of 4 bytes for the payload.
/// Supported Commands are: <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
/// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
/// Calculate CRCs and Command length and check if command is valid.
/// Use <see cref="AddRecordToSendFifo" /> to push data to Port/Meter.
/// </summary>
/// <param name="command">
/// Supported <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
/// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
/// </param>
/// <param name="meterRegister">Register to Read or Write, null for <see cref="Commands.QueryCaps" /></param>
/// <param name="payload">
/// Data to push into the <see cref="RegisterDefinition" />.
/// must be null on Read Commands ( <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />)
/// and not null on WriteData (<see cref="Commands.WriteData" /> and <see cref="Commands.MultipleWriteData" />)
/// </param>
/// <param name="expectedLength"></param>
/// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
/// <param name="skipRetryErrorCode">mask to skip reties on error</param>
/// <returns>an new command just send to port/meter </returns>
/// <remarks date="2018-Mar-15" author="R.Drahbesch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Dec-08" author="T.Wiedebusch">
/// - Reworked to zero pad payload with chunks of 4 bytes, the caller needn't take care of the size
/// </remarks>
/// <remarks date="2018-Dec-09" author="T.Wiedebusch">
/// - Corrected payload content in request protocol
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Jun-28" author="T.Wiedebusch">
/// - Skip retries on specific error mask to speed up communication on functional errors
/// or informational feed backs (e.g FW not installed 0x0004)
/// <see cref="RequestRecord.ResponseErrorCode"/>
/// </remarks>
/// <remarks date="2019-Aug-28" author="R.Drabesch">
/// - Multiple read for string split to simple reads.
/// </remarks>
/// <remarks date="2019-Jan-18" author="T.Wiedebusch">
/// - Default <see cref="CommunicationConfig.SkipRetryErrorCode"/> set.
/// </remarks>
/// <remarks date="2023-Sep-06" author="T.Wiedebusch">
/// - MultipleReadData based on data size.
/// </remarks>
/// <remarks date="2023-Nov-24" author="T.Wiedebusch">
/// - Exit multiple read date if retries exceeded.
/// </remarks>
public RequestRecord CommandToMeter(Byte command, RegisterDefinition meterRegister = null,
Byte[] payload = null, Int32? expectedLength = null, Boolean hideDataInLog = false,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
Byte[] payload = null, Int32 expectedLength = 4, Boolean hideDataInLog = false,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
if (command == Commands.MultipleReadData && meterRegister != null && meterRegister.DataType == typeof(String))
{
var completeResponse = new List<Byte>();
//TODO THW check this including the recursive call CommandToMeter
while (true)
while (!completeResponse.Contains(0) && _recordInProcess.RetryCtr < CommunicationConfig.RequestRetries)
{
var cRecord = CommandToMeter(Commands.ReadData, meterRegister, payload, expectedLength,
hideDataInLog, skipRetryErrorCode);
var cRecord = CommandToMeter(Commands.ReadData, meterRegister, payload, 4, hideDataInLog,
skipRetryErrorCode);
var rest = ProcessRecordList();
if (rest == RequestAcknowledgeState.Ok && cRecord != null && cRecord.ResponsePayload != null)
if (rest == RequestAcknowledgeState.Ok && cRecord?.ResponsePayload != null)
{
completeResponse.AddRange(cRecord.ResponsePayload);
if (cRecord.ResponsePayload.Contains(0))
@@ -570,40 +624,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
cRecord.ResponsePayload = completeResponse;
OnMeterRegisterUpdated?.Invoke(this,
new RegisterUpdatedEventArgs(meterRegister, completeResponse.ToArray()));
if (expectedLength.HasValue && completeResponse.Count < expectedLength.Value)
{
_logger.Debug("Too Short. Payload contains zero");
}
return cRecord;
}
}
else
{
if (expectedLength.HasValue && completeResponse.Any() &&
completeResponse.Count < expectedLength.Value)
{
if (rest != RequestAcknowledgeState.Ok)
{
_logger.Debug($"Too Short.Request Acknowledge State is {rest}");
}
else if (cRecord == null)
{
_logger.Debug("Too Short.Record is empty or null");
}
else if (cRecord.ResponsePayload == null)
{
_logger.Debug("Too Short.ResponsePayload is empty or null");
}
else
{
_logger.Debug("Too Short.Unkown reason");
}
}
return cRecord;
}
}
}
//the minimum payload is one 4 byte chunk
@@ -648,20 +671,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
if (command == Commands.MultipleReadData)
{
//TODO THW create expected size out of meterRegister.DataType
if (!expectedLength.HasValue)
{
if ((meterRegister.DataType == typeof(UInt64) || meterRegister.DataType == typeof(Int64)))
{
expectedLength = 8;
}
else
{
expectedLength = 24;
}
}
var numberOfChunks = (UInt16)(expectedLength.Value / chunkSize);
var numberOfChunks = (UInt16)(expectedLength / chunkSize);
if (expectedLength % chunkSize != 0)
numberOfChunks++;
requestProtocol.Add((Byte)(numberOfChunks & 0xFF));
@@ -676,138 +686,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
hideDataInLog, skipRetryErrorCode);
}
///// <summary>
///// Method to send basic Commands to Meter. Creates an integer of 4 bytes for the payload.
///// Supported Commands are: <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
///// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
///// Calculate CRCs and Command length and check if command is valid.
///// Use <see cref="AddRecordToSendFifo" /> to push data to Port/Meter.
///// </summary>
///// <param name="command">
///// Supported <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
///// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
///// </param>
///// <param name="meterRegister">Register to Read or Write, null for <see cref="Commands.QueryCaps" /></param>
///// <param name="payload">
///// Data to push into the <see cref="RegisterDefinition" />.
///// must be null on Read Commands ( <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />)
///// and not null on WriteData (<see cref="Commands.WriteData" /> and <see cref="Commands.MultipleWriteData" />)
///// </param>
///// <param name="expectedLength"></param>
///// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
///// <param name="skipRetryErrorCode">mask to skip reties on error</param>
///// <returns>an new command just send to port/meter </returns>
///// <remarks date="2018-Mar-15" author="R.Drahbesch">
///// - Initial
///// </remarks>
///// <remarks date="2018-Dec-08" author="T.Wiedebusch">
///// - Reworked to zero pad payload with chunks of 4 bytes, the caller needn't take care of the size
///// </remarks>
///// <remarks date="2018-Dec-09" author="T.Wiedebusch">
///// - Corrected payload content in request protocol
///// </remarks>
///// <remarks date="2019-Apr-11" author="T.Wiedebusch">
///// - Hide data in logging for e.g. passwords
///// </remarks>
///// <remarks date="2019-Jun-28" author="T.Wiedebusch">
///// - Skip retries on specific error mask to speed up communication on functional errors
///// or informational feed backs (e.g FW not installed 0x0004)
///// <see cref="RequestRecord.ResponseErrorCode"/>
///// </remarks>
///// <remarks date="2019-Aug-28" author="R.Drabesch">
///// - Multiple read for string split to simple reads.
///// </remarks>
///// <remarks date="2019-Jan-18" author="T.Wiedebusch">
///// - Default <see cref="CommunicationConfig.SkipRetryErrorCode"/> set.
///// </remarks>
///// <remarks date="2023-Sep-06" author="T.Wiedebusch">
///// - MultipleReadData based on data size.
///// </remarks>
///// <remarks date="2023-Nov-24" author="T.Wiedebusch">
///// - Exit multiple read date if retries exceeded.
///// </remarks>
//public RequestRecord CommandToMeter(Byte command, RegisterDefinition meterRegister = null,
// Byte[] payload = null, Int32 expectedLength = 4, Boolean hideDataInLog = false,
// UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
//{
// if (command == Commands.MultipleReadData && meterRegister != null && meterRegister.DataType == typeof(String))
// {
// var completeResponse = new List<Byte>();
// while (!completeResponse.Contains(0) && _recordInProcess.RetryCtr < (CommunicationConfig.RequestRetries*3))
// {
// var cRecord = CommandToMeter(Commands.ReadData, meterRegister, payload, 4, hideDataInLog,
// skipRetryErrorCode);
// var rest = ProcessRecordList();
// if (rest == RequestAcknowledgeState.Ok && cRecord?.ResponsePayload != null)
// {
// completeResponse.AddRange(cRecord.ResponsePayload);
// if (cRecord.ResponsePayload.Contains(0))
// {
// cRecord.ResponsePayload = completeResponse;
// OnMeterRegisterUpdated?.Invoke(this,
// new RegisterUpdatedEventArgs(meterRegister, completeResponse.ToArray()));
// return cRecord;
// }
// }
// }
// }
// //the minimum payload is one 4 byte chunk
// var chunkSize = RegisterDefinition.ChunkSize;
// if (payload == null)
// {
// payload = new Byte[chunkSize];
// for (var i = 0; i < chunkSize; i++)
// {
// payload[i] = FillByte;
// }
// }
// //real payload based on 4 byte chunks
// var requestPayload = new List<Byte>();
// requestPayload.AddRange(payload);
// //fill-byte padding
// if (payload.Length % chunkSize != 0)
// {
// for (var i = 0; i < chunkSize - payload.Length % chunkSize; i++)
// {
// requestPayload.Add(FillByte);
// }
// }
// //request protocol excluding the command
// var requestProtocol = new List<Byte>();
// if (meterRegister != null)
// {
// requestProtocol.Add(meterRegister.RegisterAddress[1]);
// requestProtocol.Add(meterRegister.RegisterAddress[0]);
// if (command == Commands.MultipleWriteData)
// {
// //set counter to inform meter writes are expected
// //the payload size is already padded to 4 byte chunks
// var numberOfChunks = (UInt16)(requestPayload.Count / chunkSize);
// requestProtocol.Add((Byte)(numberOfChunks & 0xFF));
// requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF));
// }
// if (command == Commands.MultipleReadData)
// {
// var numberOfChunks = (UInt16)(expectedLength / chunkSize);
// if (expectedLength % chunkSize != 0)
// numberOfChunks++;
// requestProtocol.Add((Byte)(numberOfChunks & 0xFF));
// requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF));
// }
// //add always the payload to the request protocol
// requestProtocol.AddRange(requestPayload);
// }
// return AddRecordToSendFifo(command, requestProtocol.ToArray(), meterRegister,
// hideDataInLog, skipRetryErrorCode);
//}
/// <summary>
/// Analyzes the error code
/// </summary>
@@ -7,6 +7,24 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers
/// </summary>
public class RecoveryRegisterItem
{
/// <summary>
/// Ctor
/// </summary>
public RecoveryRegisterItem()
{
}
/// <summary>
/// Ctor for write access parameter setup
/// </summary>
public RecoveryRegisterItem(String registerIdent, Byte[] writeValue, Byte[] readBackValue = null)
{
RegisterIdent = registerIdent;
WriteValue = writeValue;
ReadBackValue = readBackValue;
}
/// <summary>
/// Register name, use the latest configuration.json to make sure that the address will be correct
/// </summary>
@@ -1 +1 @@
c43c40a030005a8f189e6179faf40a6d42bfef5c
bf6bb8fceb3db5a9977dadc407cf576684bf7127
@@ -1 +1 @@
65d75c3322f8a137c69963cfdc0cb6f1ffacf62a
247396738e2c5d26ac00f9df05c0cc5c61d7f33a
+2 -18
View File
@@ -1,10 +1,10 @@
namespace Xylem.Common.Ui.GenesisToolBox.Ctls
{
using System;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using Xylem.Common.Ui.GenesisToolBox.Infrastructure;
public partial class LoginForm : UserControl
{
private readonly Action<string, string, object> submittedCallback;
@@ -49,20 +49,4 @@
internal void ShowError(string errorMessage)
=> this.ErrorMessage.Text = errorMessage;
}
internal static class PasswordExtensions
{
internal static string ComputeBase64Hash(this string plainText)
{
if (string.IsNullOrWhiteSpace(plainText))
{
return plainText;
}
var buffer = Encoding.UTF8.GetBytes(plainText);
var hash = SHA256.Create().ComputeHash(buffer);
return Convert.ToBase64String(hash);
}
}
}
@@ -1,6 +1,6 @@
namespace Xylem.Common.Ui.GenesisToolBox.Infrastructure
{
internal class ADLoginModel
internal class LDAPLoginModel
{
public string Assembly { get; set; }
@@ -5,10 +5,10 @@
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
internal class SoftwareUser
internal class LDAPUser
{
public SoftwareUser()
=> this.InitializeSoftwareUser();
public LDAPUser()
=> this.InitializeLDAPUser();
public int EmployeeId { get; private set; }
@@ -22,7 +22,7 @@
public string Email { get; private set; }
private void InitializeSoftwareUser()
private void InitializeLDAPUser()
{
try
{
@@ -1,13 +1,16 @@
namespace Xylem.Common.Ui.GenesisToolBox.Infrastructure
{
using CommonCore.Configuration;
using Logic.SoftwareAccessHelper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using CommonCore.Configuration;
using Logic.SoftwareAccessHelper;
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// Holds information over current software and logged in user.
@@ -24,9 +27,9 @@
public static void Initialize()
{
var version = SoftwareVersion.Current;
var user = new SoftwareUser();
var user = new LDAPUser();
Login(new ADLoginModel
Login(new LDAPLoginModel
{
Assembly = version.AssemblyName.Name,
Email = user.Email,
@@ -44,16 +47,17 @@
/// <summary>
/// Sends login request and obtains an bearer authorization token.
/// </summary>
/// <param name="model"><see cref="ADLoginModel"/> - containing all software and user info required for authentication.</param>
public static void Login(ADLoginModel model)
/// <param name="model"><see cref="LDAPLoginModel"/> - containing all software and user info required for authentication.</param>
public static void Login(LDAPLoginModel model)
{
var authenticationResponse = LocalWebRequest.PostRequestAndGetResponseAsync(
url: $"{ServiceUrls.LaaProductionAPI}/LaaProductionWeb/API/Login/AD",
json: model);
bearer = authenticationResponse.Content;
bearer = authenticationResponse
?.Content
?.Trim()
?.Trim('"');
softwareFunctions = GetFunctions();
UserName = "Options";
if (HasUser)
{
@@ -82,10 +86,11 @@
MinorV = SoftwareVersion.Current.MinorV,
BuildV = SoftwareVersion.Current.BuildV,
});
bearer = authenticationResponse.Content;
bearer = authenticationResponse
?.Content
?.Trim()
?.Trim('"');
softwareFunctions = GetFunctions();
UserName = "Options";
if (HasUser)
{
@@ -129,7 +134,7 @@
return JsonConvert
.DeserializeObject<IEnumerable<String>>(functionsResponse)
?.Select(x => Enum.TryParse($"{x}", out SoftwareFunctions f) ? f : default(SoftwareFunctions))
?? Array.Empty<SoftwareFunctions>();
?.Distinct() ?? Array.Empty<SoftwareFunctions>();
}
catch (Exception e)
{
@@ -150,5 +155,18 @@
Initialize();
}
internal static string ComputeBase64Hash(this string plainText)
{
if (string.IsNullOrWhiteSpace(plainText))
{
return plainText;
}
var buffer = Encoding.UTF8.GetBytes(plainText);
var hash = SHA256.Create().ComputeHash(buffer);
return Convert.ToBase64String(hash);
}
}
}
@@ -19,11 +19,12 @@
GENERAL_SETUP,
GENERAL_STREAM_QUALITY,
GENERAL_TEMP_METER,
LAA_MAPPING_NA,
LAA_PASSWORD,
LAA_PARAMETERIZATION,
LAA_PROCESS_PARAMETER,
LAA_REGISTER_HISTORY,
LAA_ADMIN,
}
}
+13 -3
View File
@@ -54,6 +54,7 @@
this.optionsMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.loginMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.logoutMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.registerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.gpGerneral.SuspendLayout();
this.gpLAA.SuspendLayout();
this.gbDeveloper.SuspendLayout();
@@ -334,7 +335,8 @@
//
this.optionsMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.loginMenuItem,
this.logoutMenuItem});
this.logoutMenuItem,
this.registerMenuItem});
this.optionsMenuItem.Name = "optionsMenuItem";
this.optionsMenuItem.Size = new System.Drawing.Size(61, 20);
this.optionsMenuItem.Text = "Options";
@@ -343,17 +345,24 @@
// loginMenuItem
//
this.loginMenuItem.Name = "loginMenuItem";
this.loginMenuItem.Size = new System.Drawing.Size(112, 22);
this.loginMenuItem.Size = new System.Drawing.Size(180, 22);
this.loginMenuItem.Text = "Login";
this.loginMenuItem.Click += new System.EventHandler(this.OnLoginClick);
//
// logoutMenuItem
//
this.logoutMenuItem.Name = "logoutMenuItem";
this.logoutMenuItem.Size = new System.Drawing.Size(112, 22);
this.logoutMenuItem.Size = new System.Drawing.Size(180, 22);
this.logoutMenuItem.Text = "Logout";
this.logoutMenuItem.Click += new System.EventHandler(this.OnLogoutClick);
//
// registerMenuItem
//
this.registerMenuItem.Name = "registerMenuItem";
this.registerMenuItem.Size = new System.Drawing.Size(180, 22);
this.registerMenuItem.Text = "Register";
this.registerMenuItem.Click += new System.EventHandler(this.OnRegisterClick);
//
// FrmMainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -410,6 +419,7 @@
private System.Windows.Forms.ToolStripMenuItem optionsMenuItem;
private System.Windows.Forms.ToolStripMenuItem loginMenuItem;
private System.Windows.Forms.ToolStripMenuItem logoutMenuItem;
private System.Windows.Forms.ToolStripMenuItem registerMenuItem;
}
}
+44 -1
View File
@@ -3,6 +3,7 @@
using global::GenesisToolBox;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
@@ -406,6 +407,8 @@
/// </summary>
private void SetSoftwareFunctions()
{
registerMenuItem.Tag = SoftwareFunctions.LAA_ADMIN;
btnSetup.Tag = SoftwareFunctions.GENERAL_SETUP;
btnAlarm.Tag = SoftwareFunctions.GENERAL_ALARM;
btnRegisterStore.Tag = SoftwareFunctions.GENERAL_REGISTER_STORE;
@@ -462,18 +465,53 @@
if (button.Tag is SoftwareFunctions softwareFunction)
{
// enable all buttons for DEBUGGING, else use access rights of user
button.Enabled = System.Diagnostics.Debugger.IsAttached || Software.IsEnabled(softwareFunction);
button.Enabled = Debugger.IsAttached || Software.IsEnabled(softwareFunction);
}
}
}
groupBox.Show();
}
else if (control is MenuStrip menu && menu.Items != null)
{
foreach (var item in menu.Items)
{
if (item is ToolStripMenuItem toolStripMenuItem)
{
this.ShowAvailableMenuItems(toolStripMenuItem);
}
}
}
}
optionsMenuItem.Text = Software.UserName;
}
private void ShowAvailableMenuItems(ToolStripMenuItem toolStripMenuItem)
{
if (toolStripMenuItem is null)
{
return;
}
else if (toolStripMenuItem.Tag is SoftwareFunctions softwareFunction)
{
toolStripMenuItem.Visible = Debugger.IsAttached || Software.IsEnabled(softwareFunction);
}
var dropdownItems = toolStripMenuItem.DropDownItems;
if (dropdownItems != null && dropdownItems.Count > 0)
{
foreach (var item in dropdownItems)
{
if (item is ToolStripMenuItem _toolStripMenuItem)
{
this.ShowAvailableMenuItems(_toolStripMenuItem);
}
}
}
}
/// <summary>
/// Hides all members but the login form if the app is initialized otherwise shows error message.
/// In case that the app is initialized and user is authorized shows all buttons as enabled or disabled depending on the user permissions.
@@ -558,5 +596,10 @@
form.Closed += Form_Closed;
Hide();
}
private void OnRegisterClick(Object _, EventArgs _1)
{
MessageBox.Show("Register new user");
}
}
}