2408 lines
90 KiB
C#
2408 lines
90 KiB
C#
using Logic.ProductionToProductMapper.Cordonel;
|
||
using Newtonsoft.Json;
|
||
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Reflection;
|
||
using System.Runtime.InteropServices;
|
||
using System.Security.Authentication;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Timers;
|
||
using NLog;
|
||
using Xylem.Common.CommonCore.Configuration;
|
||
using Xylem.Common.CommonCore.Consts;
|
||
using Xylem.Common.CommonCore.ThreadWatcher;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.SerialPorts;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.EventArguments;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Applications;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Applications.Const;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.EventArguments;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.Consts;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol.Consts;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol.EventArguments;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters;
|
||
using Xylem.Common.Logic.ProductionOrderCore.File;
|
||
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
|
||
using Xylem.Common.Logic.RelatePcb;
|
||
using Xylem.Common.Logic.SoftwareAccessHelper;
|
||
using Xylem.Common.Metrology.Measurements;
|
||
using Xylem.Common.Metrology.Measurements.Consts;
|
||
using Xylem.Common.Utils.Logging;
|
||
using Timer = System.Timers.Timer;
|
||
|
||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||
{
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
/// <summary>
|
||
/// Genesis meter, main class for all action that occurs on production life of meter
|
||
/// one meter always have a linked port for UART communication and one for LED (even if you don't need them)
|
||
/// it´s has to be open for vb6 COM so don't use any record types or objects that vb6 didn't understand or
|
||
/// interprets differently than dotNet
|
||
/// </summary>
|
||
|
||
[Guid("767AEE0F-7321-4ABF-9196-05CC249009B2"),
|
||
ComVisible(true),
|
||
ClassInterface(ClassInterfaceType.None),
|
||
ComSourceInterfaces(typeof(IMeterEvents))]
|
||
public partial class GenesisMeter : IMeter, IMeterEvents, IWaterMeterWithRegisters
|
||
{
|
||
#region ctor
|
||
|
||
/// <summary> Default constructor. </summary>
|
||
///
|
||
/// <remarks> R.Drabesch, 2018-Feb-16. </remarks>
|
||
public GenesisMeter()
|
||
{
|
||
SetLogger();
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Process configuration
|
||
/// </summary>
|
||
public ProcessConfig Configuration = new ProcessConfig();
|
||
|
||
/// <summary>
|
||
/// Transmit protocol access for underlie objects to change response timeout
|
||
/// </summary>
|
||
public ITransmitProtocol TransmitProtocol;
|
||
|
||
//initially do not signal event
|
||
private readonly AutoResetEvent _onSyncKeepSessionThread = new AutoResetEvent(false);
|
||
private Thread _keepSessionThread;
|
||
private Timer _keepSessionTimer;
|
||
private readonly CancellationTokenSource _keepSessionToken = new CancellationTokenSource();
|
||
|
||
private ILogger _logger;
|
||
private ILogger _loggerRawData;
|
||
|
||
/// <summary>
|
||
/// </summary>
|
||
/// <param name="slot">Slot number for test-bench </param>
|
||
/// <param name="requestPort"><see cref="PortConfig" /> for request Port</param>
|
||
/// <param name="streamingPort"><see cref="PortConfig" /> for streaming Port</param>
|
||
/// <param name="ignoreCorruptedData">don´t send CRC error or telegrams with error flag to caller</param>
|
||
/// <param name="password">
|
||
/// If you have a password for highest access level you need, if you don't want to access the meter
|
||
/// (just read led record) than you can leave it null
|
||
/// </param>
|
||
public GenesisMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort,
|
||
Boolean ignoreCorruptedData = true, String password = null)
|
||
{
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{slot}", "Slot", "Meter", "MeterBase", "MeterBase");
|
||
SetupGenesisMeter(slot, requestPort, streamingPort, ignoreCorruptedData, password);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Const
|
||
|
||
/// <summary>
|
||
/// Keep up the highest access level for all function in <see cref="GenesisMeter" /> where used
|
||
/// </summary>
|
||
private const Int32 LoginLvl = 8;
|
||
|
||
/// <summary>
|
||
/// Logout with level 0
|
||
/// </summary>
|
||
private const Int32 LogoutLvl = 0;
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
private String _serialNumber = "";
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// Property is redundant because is also stored in
|
||
/// <see cref="T:Xylem.Sensus.WaterMeter.Genesis.Logic.Domain.Genesis.DataPackages.MeterRegister" /> but never less is
|
||
/// is the identification of the meter it has a separate Property
|
||
/// </summary>
|
||
public String SerialNumber
|
||
{
|
||
get => _serialNumber;
|
||
set
|
||
{
|
||
_serialNumber = value;
|
||
SetLogger();
|
||
}
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// Save Slot Number (position at test-bench) for logging purposes
|
||
/// </summary>
|
||
public Int32 Slot
|
||
{
|
||
get; set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represent <see cref="Register.Configexchange.PcbSerialNumber" />
|
||
/// proposed for login
|
||
/// </summary>
|
||
public String PcbId
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Int32 IntermediateUpdateTimeS { get; set; } = CommunicationConfig.MeasurementUpdateTimeS;
|
||
|
||
/// <summary>
|
||
/// Add on ctor a password and it will be used for login in if no other password is set
|
||
/// </summary>
|
||
private String _password;
|
||
|
||
/// <summary>
|
||
/// default false only enabled by <see cref="EnableAutoLogon"/>
|
||
/// when it is true, the meter will enforce the login when session is gone or access is denied
|
||
/// </summary>
|
||
private Boolean _autoLogon;
|
||
|
||
/// <summary>
|
||
/// Enable raw record logging, if set every incoming package will be logged
|
||
/// </summary>
|
||
private Boolean _enableRawDataLogging;
|
||
|
||
|
||
/// <summary>
|
||
/// Enable use of registers that are not valid (last version not match the current app)
|
||
/// </summary>
|
||
public Boolean IsDevelopmentUsage
|
||
{
|
||
get; set;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// All parameter store in this Genesis is up in here
|
||
/// if you want to read them from meter use <see cref="ReadRegister" />
|
||
/// if you want to set them to meter use <see cref="WriteRegister{T}" />
|
||
/// </summary>
|
||
private readonly MeterRegisters _configRegister = new MeterRegisters();
|
||
|
||
/// <summary>
|
||
/// All applications defined by configuration.json
|
||
/// </summary>
|
||
private readonly List<ApplicationDefinition> _configApplications = new List<ApplicationDefinition>();
|
||
|
||
/// <summary>
|
||
/// request port assignment/info
|
||
/// </summary>
|
||
public IPort RequestPort
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// request protocol assignment/info
|
||
/// </summary>
|
||
public RequestProtocol RequestProtocol;
|
||
|
||
/// <summary>
|
||
/// streaming port assignment/info
|
||
/// </summary>
|
||
public IPort StreamingPort
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// streaming protocol assignment/info
|
||
/// </summary>
|
||
public StreamingProtocol StreamingProtocol;
|
||
/// <summary>
|
||
/// to reduce the IrdA communication in test bench, preparation will be done once (when SkipPreparationForTestBench is true)
|
||
/// </summary>
|
||
public Boolean SkipPreparationForTestBench
|
||
{
|
||
get; set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Response received after request for record
|
||
/// </summary>
|
||
public event EventHandler<RequestResponseDataEventArgs> OnRequestRecordReceived;
|
||
|
||
/// <inheritdoc />
|
||
public void SetConfigRegisterDefinitions(List<IRegister> allRegisters)
|
||
{
|
||
_configRegister.AddRegistersDefinitions(allRegisters);
|
||
}
|
||
|
||
private void SetConfigApplicationDefinitions(List<ApplicationDefinition> allApplications)
|
||
{
|
||
foreach (var add in allApplications)
|
||
{
|
||
try
|
||
{
|
||
var addGenesisApplication = add;
|
||
|
||
if (addGenesisApplication != null)
|
||
{
|
||
_configApplications.Add(addGenesisApplication);
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// ignored
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Returns meter registers
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public MeterRegisters GetConfigRegistersDefinitions()
|
||
{
|
||
return _configRegister;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
void IWaterMeterWithRegisters.LoadConfiguration(String location)
|
||
{
|
||
var configFile = ProgramConfig.RegisterDefinitionFileName;
|
||
|
||
var configFilePath = Path.Combine(location, configFile);
|
||
|
||
if (!new FileInfo(configFilePath).Exists)
|
||
{
|
||
configFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||
nameof(Genesis), configFile);
|
||
if (!new FileInfo(configFilePath).Exists)
|
||
{
|
||
_logger.Fatal($"Slot:{Slot} - Missing register definition file (configuration.json). Search location: {configFilePath}");
|
||
throw new ApplicationException("Missing register definition file (configuration.json). Setup is invalid!");
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Registers definitions loaded from {configFilePath}");
|
||
if (Configuration.AutoUpdateFiles)
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Check for updates of {configFilePath}");
|
||
var fi = new FileInfo(configFilePath);
|
||
|
||
try
|
||
{
|
||
var str = LocalWebRequest.GetRequest($"{ServiceUrls.FileContentControllerUrl()}GetUpdateFileContent?FileName={fi.Name}&ClientFileDate={fi.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")}&isDeveloper=0", 5000);
|
||
if (!string.IsNullOrEmpty(str.Trim('\"')))
|
||
{
|
||
var baseFile = JsonConvert.DeserializeObject<BaseFile>(str);
|
||
_logger.Trace($"Slot:{Slot} - Updated register definitions File");
|
||
if (baseFile != null)
|
||
{
|
||
File.WriteAllText(configFilePath, baseFile.FileContent);
|
||
if (baseFile.UploadDate != null)
|
||
File.SetCreationTime(configFilePath, baseFile.UploadDate.Value.UtcDateTime);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Update not needed. Register definition file is up to date.");
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Warn(ex, $"Slot:{Slot} - Register definition file update went wrong.");
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
_logger.Trace($"Slot:{Slot} - Registers definitions loaded from {configFilePath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Warn(ex, $"Slot:{Slot} - Register definition file update went wrong.");
|
||
}
|
||
|
||
|
||
//builds a configuration file reader from configuration.json and reads all registers definitions
|
||
//and application definitions
|
||
var registerReader = new GenesisConfigurationReader(configFilePath);
|
||
|
||
SetConfigRegisterDefinitions(registerReader.ConfigRegistersDefinitions);
|
||
SetConfigApplicationDefinitions(registerReader.ConfigApplicationDefinitions);
|
||
}
|
||
|
||
event EventHandler IMeter.OnDisposeCompleted
|
||
{
|
||
add
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
|
||
remove
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region state machine and states
|
||
|
||
/// <summary>
|
||
/// do not use it to set ProcessStatus
|
||
/// if you want to change ProcessStatus of this genesis use <see cref="ProcessStatus" />
|
||
/// <see cref="_myProcessStatus" /> is just store for some routines
|
||
/// </summary>
|
||
private ProcessState _myProcessStatus;
|
||
|
||
/// <inheritdoc />
|
||
/// if state is change
|
||
/// <see cref="OnProcessStatusChanged" />
|
||
/// will be invoked.
|
||
/// on some states other events will be invoke as well
|
||
public ProcessState ProcessStatus
|
||
{
|
||
get
|
||
{
|
||
return _myProcessStatus;
|
||
}
|
||
|
||
set
|
||
{
|
||
if (_myProcessStatus == value)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_myProcessStatus = value;
|
||
Invoker(OnProcessStatusChanged);
|
||
switch (value)
|
||
{
|
||
case ProcessState.InitIsActive:
|
||
Invoker(OnInitCompleted);
|
||
break;
|
||
case ProcessState.MeasurementIsActive:
|
||
Invoker(OnMeasurementInitCompleted);
|
||
break;
|
||
case ProcessState.MeasurementIsDone:
|
||
Invoker(OnMeasurementCompleted);
|
||
break;
|
||
case ProcessState.CalibrationIsActive:
|
||
Invoker(OnCalibInitCompleted);
|
||
break;
|
||
case ProcessState.CalibrationIsDone:
|
||
Invoker(OnCalibCompleted);
|
||
break;
|
||
case ProcessState.QRefDone:
|
||
case ProcessState.WaitForQRef:
|
||
case ProcessState.WaitForCalibration:
|
||
case ProcessState.WaitForMeasurement:
|
||
case ProcessState.IsNotInit:
|
||
//no extra event needed
|
||
break;
|
||
default:
|
||
throw new OverflowException("Genesis measurement unknown process status");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// do not use it to set ErrorStatus
|
||
/// if you want to change ErrorStatus of this genesis use <see cref="ErrorStatus" />
|
||
/// <see cref="_myErrorState" /> is just store for some routines
|
||
/// </summary>
|
||
private ErrorState _myErrorState;
|
||
|
||
/// fires up
|
||
/// <see cref="OnErrorStatusChanged" />
|
||
public ErrorState ErrorStatus
|
||
{
|
||
get
|
||
{
|
||
return _myErrorState;
|
||
}
|
||
set
|
||
{
|
||
_myErrorState = value;
|
||
Invoker(OnErrorStatusChanged);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Logged in to device
|
||
/// </summary>
|
||
public Boolean IsLoggedOn
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
private String _currentActionText = "";
|
||
/// <summary>
|
||
/// Hold the current process name e.g. FlowTest, Preadjustement for logging
|
||
/// </summary>
|
||
public String CurrentActionText
|
||
{
|
||
get
|
||
{
|
||
return _currentActionText;
|
||
}
|
||
set
|
||
{
|
||
_currentActionText = value;
|
||
SetLogger();
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Setup water meter from configuration file:
|
||
/// -slot,
|
||
/// -streaming protocol,
|
||
/// -streaming port
|
||
/// -request port
|
||
/// </summary>
|
||
/// <param name="slot"></param>
|
||
/// <param name="ignoreCorruptedData"></param>
|
||
/// <exception cref="ApplicationException"></exception>
|
||
public void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData = true)
|
||
{
|
||
SetupFromConfigFile(slot, ignoreCorruptedData, true, true);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData, Boolean useRequest, Boolean useStreaming)
|
||
{
|
||
|
||
Slot = slot;
|
||
SetLogger();
|
||
|
||
|
||
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Genesis), ProgramConfig.SerialConfigFileName);
|
||
|
||
if (!File.Exists(configFile))
|
||
{
|
||
configFile = Path.Combine(ProgramConfig.SerialConfigFileName);
|
||
if (!File.Exists(configFile))
|
||
{
|
||
throw new ApplicationException($"Configuration file {configFile} not found ");
|
||
}
|
||
}
|
||
var tr = new StreamReader(configFile);
|
||
var slotConfigList = JsonConvert.DeserializeObject<SlotConfig[]>(tr.ReadToEnd());
|
||
|
||
if (slotConfigList != null)
|
||
{
|
||
var slotConfig = slotConfigList.FirstOrDefault(t => t.Slot == slot);
|
||
|
||
if (slotConfig == null)
|
||
{
|
||
throw new ApplicationException($"Slot:{slot} - File " +
|
||
$"{ProgramConfig.SerialConfigFileName} does not contain valid configuration");
|
||
}
|
||
_logger.Debug($"Slot:{slot} - Configuration loaded");
|
||
if (!useRequest)
|
||
{
|
||
SetupGenesisMeter(slot, null, slotConfig.Streaming, ignoreCorruptedData);
|
||
}
|
||
else if (!useStreaming)
|
||
{
|
||
SetupGenesisMeter(slot, slotConfig.Request, null, ignoreCorruptedData);
|
||
}
|
||
else
|
||
{
|
||
SetupGenesisMeter(slot, slotConfig.Request, slotConfig.Streaming, ignoreCorruptedData);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SetLogger()
|
||
{
|
||
if (_logger != null)
|
||
{
|
||
LogManager.Flush();
|
||
}
|
||
if (string.IsNullOrEmpty(CurrentActionText))
|
||
{
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}", $"", "Meter", "MeterBase", "MeterBase");
|
||
|
||
}
|
||
else
|
||
{
|
||
var id = !string.IsNullOrEmpty(SerialNumber) ? $"SN{SerialNumber}" : $"PCB{PcbId}";
|
||
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}_{id}_{CurrentActionText}", $"Slot", "Meter", "MeterBase", "MeterBase");
|
||
if (StreamingPort != null && StreamingPort is LedSerialPort)
|
||
{
|
||
var streamingIdent = $"LedRawData_Slot_{Slot}_{id}_{CurrentActionText}";
|
||
|
||
((LedSerialPort)StreamingPort).Ident = streamingIdent;
|
||
((LedSerialPort)StreamingPort).RefreshLogger();
|
||
|
||
|
||
if (!string.IsNullOrEmpty(SerialNumber) && CurrentActionText.Contains("PG"))
|
||
{
|
||
|
||
var pathMain = NLogHelper.GetPath(_logger);
|
||
var pathRaw = NLogHelper.GetPath(((LedSerialPort)StreamingPort).GetRawLogger());
|
||
|
||
var basePath = Path.GetDirectoryName(pathMain);
|
||
|
||
//
|
||
|
||
|
||
var testRunString = CurrentActionText.Replace("PG", "");
|
||
var lastChat = pathMain.LastIndexOf('\\');
|
||
pathMain = pathMain.Substring(lastChat + 1);
|
||
pathRaw = Path.GetFileName(pathRaw);
|
||
|
||
var tmpUrl = $"http://10.49.40.25/MeterProcessState/api/TestBench/SetMeterLogPathInfo?SerialNumber={SerialNumber}&TestRunId={testRunString}&BasePath={basePath}&MainLogName={pathMain}&LEDName={pathRaw}";
|
||
_logger.Debug($"Request Url: { tmpUrl}");
|
||
//ToDO: Update to service URl
|
||
LocalWebRequest.GetRequest(tmpUrl, 5000);
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// just internal setup. called on ctor or from vb6 setup function
|
||
/// </summary>
|
||
/// <param name="slot">Slot Number for test-bench </param>
|
||
/// <param name="requestPort"><see cref="PortConfig" /> for RFID/UART/IrDA Port</param>
|
||
/// <param name="streamingPort"><see cref="PortConfig" /> for LED Port</param>
|
||
/// <param name="ignoreCorruptedData">don´t send CRC error or telegrams with error flag to caller</param>
|
||
/// <param name="password">
|
||
/// If you have a password for highest access level you need, if you don't want to access the meter
|
||
/// (just read led record) than you can leave it nullS
|
||
/// </param>
|
||
public void SetupGenesisMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort,
|
||
Boolean ignoreCorruptedData = true, String password = "")
|
||
{
|
||
Slot = slot;
|
||
SetLogger();
|
||
_password = password;
|
||
|
||
if (requestPort != null)
|
||
{
|
||
String requestIdent;
|
||
if (requestPort.Value.Type == "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.RfidSerialPort")
|
||
{
|
||
requestIdent = $"Slot:{Slot}, Port:{requestPort.Value.PortName}, Protocol:Request, Type:RFID -";
|
||
|
||
//CRC calculation does NOT include the CRC itself
|
||
RequestProtocol = new RequestProtocol(requestIdent);
|
||
|
||
TransmitProtocol = new RfidTransmitProtocol(requestPort.Value.PortName);
|
||
RequestProtocol.SetTransmitProtocol(TransmitProtocol);
|
||
|
||
RequestPort = new RfidSerialPort(requestIdent, requestPort.Value.GetSerialPort(),
|
||
TransmitProtocol.GetTransmitPortSettings());
|
||
}
|
||
else if (requestPort.Value.Type == "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort")
|
||
{
|
||
requestIdent = $"Slot:{Slot}, Port:{requestPort.Value.PortName}, Protocol:Request, Type:UART -";
|
||
|
||
//CRC calculation does include the CRC itself filled with 0x0000 in advance
|
||
RequestProtocol = new RequestProtocol(requestIdent);
|
||
TransmitProtocol = new UartTransmitProtocol(requestPort.Value.PortName);
|
||
RequestProtocol.SetTransmitProtocol(TransmitProtocol);
|
||
|
||
RequestPort = new UartSerialPort(requestIdent, requestPort.Value.GetSerialPort(),
|
||
TransmitProtocol.GetTransmitPortSettings());
|
||
}
|
||
else if (requestPort.Value.Type == "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort")
|
||
{
|
||
requestIdent = $"Slot:{Slot}, Port:{requestPort.Value.PortName}, Protocol:Request, Type:IrDA -";
|
||
|
||
RequestProtocol = new RequestProtocol(requestIdent);
|
||
TransmitProtocol = new IrdaTransmitProtocol(requestPort.Value.PortName);
|
||
RequestProtocol.SetTransmitProtocol(TransmitProtocol);
|
||
|
||
RequestPort = new IrdaSerialPort(requestIdent, requestPort.Value.GetSerialPort(),
|
||
TransmitProtocol.GetTransmitPortSettings());
|
||
}
|
||
|
||
|
||
}
|
||
|
||
if (streamingPort != null)
|
||
{
|
||
var ledTransmit = new LedTransmitProtocol(streamingPort.Value.PortName);
|
||
|
||
var streamingIdent = $"Slot:{Slot}, Port:{streamingPort.Value.PortName}, Protocol:LED";
|
||
|
||
StreamingPort = new LedSerialPort(streamingIdent, streamingPort.Value.GetSerialPort(),
|
||
ledTransmit.GetTransmitPortSettings());
|
||
StreamingProtocol = new StreamingProtocol(streamingIdent, ignoreCorruptedData);
|
||
}
|
||
Configuration = new ProcessConfig(ProgramConfig.ConfigFileName);
|
||
_logger.Debug($"Slot:{Slot} - Setup done");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// Start Event listening, has to be called after <see cref="SetupGenesisMeter" />
|
||
/// </summary>
|
||
public void ConnectMeter()
|
||
{
|
||
if (RequestPort != null && RequestPort.GetPortName() != BaseSerialPort.PortNotAssigned)
|
||
{
|
||
LinkPortToProtocol(RequestPort, RequestProtocol);
|
||
_logger.Trace($"Slot:{Slot} - RequestPort and RequestProtocol linked");
|
||
RequestProtocol.OnMeterRegisterUpdated += RequestProtocol_MeterRegisterUpdated;
|
||
RequestProtocol.OnRecordIsDecoded += RequestProtocol_ApplyDecodedRecord;
|
||
RequestProtocol.OnAuthorizationGrant += RequestProtocol_AuthorizationGrant;
|
||
}
|
||
if (StreamingPort != null && StreamingPort.GetPortName() != BaseSerialPort.PortNotAssigned)
|
||
{
|
||
LinkPortToProtocol(StreamingPort, StreamingProtocol);
|
||
_logger.Trace($"Slot:{Slot} - StreamingPort and StreamingProtocol linked");
|
||
StreamingProtocol.OnRecordIsDecoded += StreamingProtocol_ApplyDecodedRecord;
|
||
}
|
||
|
||
|
||
_logger.Debug($"Slot:{Slot} - Connected and communication started");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add a port to working queue
|
||
/// </summary>
|
||
/// <param name="port">On <see cref="IPort" /> should one once added in runtime</param>
|
||
/// <param name="protocol"></param>
|
||
public void LinkPortToProtocol(IPort port, IProtocol protocol)
|
||
{
|
||
//for streaming mode only
|
||
if (port == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
port.OnRawRecordReceived += delegate (Object o, BasePortDataEventArgs rawMsg)
|
||
{
|
||
protocol.FillDecodingBuffer(rawMsg);
|
||
};
|
||
try
|
||
{
|
||
if (!port.IsOpen())
|
||
{
|
||
port.Open();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new ApplicationException($"Cannot Open Comport. Check TaskManager for other Genesis relevant programs and close them. Also check if the comport is valid! { Environment.NewLine }{ ex.Message}");
|
||
}
|
||
|
||
protocol.OnRecordReadyToSend += delegate (Object o, BasePortDataEventArgs dataToSend)
|
||
{
|
||
var data = (List<Byte>)dataToSend.GetData();
|
||
port.PortWrite(data.ToArray());
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Synchronization of the record for the streaming interface
|
||
/// </summary>
|
||
/// <param name="markRecord"></param>
|
||
/// <exception cref="ApplicationException"></exception>
|
||
public void SyncStreamingBuffer(SyncMarkRecord markRecord)
|
||
{
|
||
if (StreamingPort == null)
|
||
{
|
||
throw new ApplicationException("Synchronization not possible due to undefined streaming port");
|
||
}
|
||
|
||
StreamingPort.SynchronizeReceiveBuffer(markRecord);
|
||
}
|
||
|
||
private void RequestProtocol_AuthorizationGrant(Object sender, EventArgs e)
|
||
{
|
||
|
||
_logger.Debug($"Slot:{Slot} - Authorization succeeded");
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Events
|
||
|
||
//public event EventHandler<StringEventArgs> HasInformationForCaller;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnCalibCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnErrorStatusChanged;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnProcessStatusChanged;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnCalibInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnMeasurementInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnMeasurementCompleted;
|
||
|
||
/// <summary>
|
||
/// call this if session is expired and you need an re-authorization
|
||
/// </summary>
|
||
public EventHandler<AuthorizationRequiredEventArgs> AuthorizationRequired;
|
||
|
||
/// <summary>
|
||
/// A helper to invoke events only if someone is listening, otherwise nothing will happen
|
||
/// </summary>
|
||
/// <param name="handler">Event handler to call</param>
|
||
/// <param name="sender">Sender (can be null)</param>
|
||
/// <param name="e">Event arguments (can be null)</param>
|
||
private void Invoker(EventHandler handler, Object sender = null, EventArgs e = null)
|
||
{
|
||
if (handler != null)
|
||
{
|
||
if (sender == null)
|
||
{
|
||
sender = this;
|
||
}
|
||
|
||
if (e == null)
|
||
{
|
||
e = new EventArgs();
|
||
}
|
||
|
||
handler.Invoke(sender, e);
|
||
}
|
||
// ReSharper disable once RedundantIfElseBlock
|
||
else
|
||
{
|
||
//nobody is listening
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Track all incoming led record packages (Calibration and Flow)
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e">
|
||
/// I should be <see cref="FlowTestRecord" /> or <see cref="CalibDataEventArgs" />
|
||
/// otherwise this method does nothing
|
||
/// </param>
|
||
private void StreamingProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e)
|
||
{
|
||
var result = e.GetEventData();
|
||
if (result is IMeasurementRecord)
|
||
{
|
||
if (result is FlowTestRecord)
|
||
{
|
||
if (!((FlowTestRecord)result).IsValid)
|
||
{
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
if (result is CalibrationRecord)
|
||
{
|
||
if (!((CalibrationRecord)result).IsValid)
|
||
{
|
||
return;
|
||
}
|
||
|
||
}
|
||
AddData(((IMeasurementRecord)result));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// tracking request record processing
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void RequestProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e)
|
||
{
|
||
if (e is RequestResponseDataEventArgs)
|
||
{
|
||
OnRequestRecordReceived?.Invoke(sender, (RequestResponseDataEventArgs)e);
|
||
}
|
||
|
||
}
|
||
#endregion
|
||
|
||
#region Commands
|
||
|
||
/// <summary>
|
||
/// QueryCaps can send before login
|
||
/// arrange record for communication (e.g. Baud rate)
|
||
/// </summary>
|
||
/// <returns>
|
||
/// Running outgoing command
|
||
/// </returns>
|
||
public RequestRecord QueryCaps()
|
||
{
|
||
_logger.Debug($"Slot:{Slot} - Request capabilities");
|
||
return RequestProtocol.CommandToMeter(Commands.QueryCaps);
|
||
}
|
||
|
||
/// <summary>
|
||
/// The backup login level is needed for re-authorization
|
||
/// </summary>
|
||
private Int32 _backupLoginLvl;
|
||
/// <summary>
|
||
/// SetRegister <see cref="Register.Configexchange.Privilege" /> to loginLvl and wait for response
|
||
/// </summary>
|
||
/// <param name="loginLvl"></param>
|
||
/// <param name="runImmediately"> true = process command; false = add command to list. call
|
||
/// <see cref="RequestProtocolProcess()"></see> to process login command </param>
|
||
/// <returns> if is completed and no error occurred it is true </returns>
|
||
public Boolean PrivilegeCommandToMeter(Int32 loginLvl, Boolean runImmediately = true)
|
||
{
|
||
|
||
_logger.Debug($"Slot:{Slot} - Set privilege login level({loginLvl})");
|
||
_backupLoginLvl = loginLvl;
|
||
|
||
var result = WriteRegister(Register.Configexchange.Privilege, loginLvl);
|
||
if (!runImmediately)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
RequestProtocolProcess();
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set <see cref="Register.Configexchange.Password" />
|
||
/// </summary>
|
||
/// <param name="password"> if null , use password from initialization</param>
|
||
/// <param name="runImmediately"> true = process command; false = add command to list. call
|
||
/// <see cref="RequestProtocolProcess()"></see> to process login command </param>
|
||
/// <returns>
|
||
/// </returns>
|
||
private RequestRecord LoginCommandToMeter(String password, Boolean runImmediately = true)
|
||
{
|
||
try
|
||
{
|
||
var regDef = _configRegister.GetRegisterDefinitionByName(Register.Configexchange.Password);
|
||
|
||
StopKeepSession();
|
||
//restore password if new password is zero
|
||
if (string.IsNullOrEmpty(password))
|
||
{
|
||
password = _password;
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(password))
|
||
{
|
||
throw new AuthenticationException("Password is empty");
|
||
}
|
||
|
||
//set new password
|
||
_password = password;
|
||
_logger.Debug($"Slot:{Slot} - Login with password");
|
||
|
||
//Due to password is defined as uint96_t this direct communication will be used instead of
|
||
//WriteRegister. The data shouldn't be resorted MSB<->LSB like other unit data types!
|
||
var responseRecord = RequestProtocol.CommandToMeter(Commands.MultipleWriteData, regDef,
|
||
Encoding.ASCII.GetBytes(password), hideDataInLog: true);
|
||
if (runImmediately)
|
||
{
|
||
RequestProtocolProcess();
|
||
}
|
||
|
||
StartKeepSession();
|
||
return responseRecord;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_logger.Error(e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
/// <summary>
|
||
/// Clear password to force new password reading
|
||
/// </summary>
|
||
/// <remarks date="2023JAN21" author="R.Drabesch">
|
||
/// - Initial.
|
||
/// </remarks>
|
||
public void ClearPassword()
|
||
{
|
||
_password = string.Empty;
|
||
}
|
||
/// <summary>
|
||
/// Acquires the password from WEB-API.
|
||
/// </summary>
|
||
/// <returns>password</returns>
|
||
/// <remarks date="2018JAN08" author="R.Drabesch">
|
||
/// - Initial.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Nov-02" author="T.Wiedebusch">
|
||
/// - PcbId handling improved.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Dec-12" author="T.Wiedebusch">
|
||
/// - PcbId unknown returns immediately.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Jan-19" author="T.Wiedebusch">
|
||
/// - Password had been reset to empty string if read from file, corrected!
|
||
/// </remarks>
|
||
/// <remarks date="2023-Feb-28" author="T.Wiedebusch">
|
||
/// - Used <see cref="MeterPwdHandlerDb.GetPasswordFromDb"/>
|
||
/// </remarks>
|
||
private String GetPassword()
|
||
{
|
||
// without PCB ID it is not possible to login,
|
||
// but if it is already assigned to this Genesis, don't read it again
|
||
if (string.IsNullOrEmpty(PcbId))
|
||
{
|
||
//GetPcbId fills the PsbId property
|
||
|
||
if (string.IsNullOrEmpty(GetPcbId()) || string.IsNullOrEmpty(PcbId) || PcbId.Length < 8)
|
||
{
|
||
if (string.IsNullOrEmpty(GetPcbId()))
|
||
{
|
||
return "";
|
||
}
|
||
}
|
||
}
|
||
|
||
// on unknown password try to get it from DB
|
||
if (MeterPwdHandlerDb.GetPasswordFromDb(PcbId, out var password))
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Got password from GenesisPasswordService, " +
|
||
$"URL({ServiceUrls.GenesisGetPasswordServiceUrl() + PcbId})");
|
||
}
|
||
else
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Cannot get password from GenesisPasswordService, " +
|
||
$"URL({ServiceUrls.GenesisGetPasswordServiceUrl() + PcbId}))");
|
||
// try to read the password from an offline password file
|
||
if (!File.Exists(ProgramConfig.OfflineInfoFile))
|
||
return password;
|
||
|
||
var listOfOfflinePasswords = JsonConvert.DeserializeObject<List<OfflinePasswordItem>>(
|
||
File.ReadAllText(ProgramConfig.OfflineInfoFile));
|
||
if (listOfOfflinePasswords != null)
|
||
password = listOfOfflinePasswords.First(x => x.PcbId == PcbId).Password;
|
||
|
||
if (!string.IsNullOrEmpty(password))
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Got password for PcbId:{PcbId} from password file!");
|
||
}
|
||
}
|
||
|
||
return password;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <remarks date="2018JAN08" author="drabesch">
|
||
/// Get password from server
|
||
/// </remarks>
|
||
public Boolean Login()
|
||
{
|
||
if (string.IsNullOrEmpty(_password) || string.IsNullOrEmpty(PcbId))
|
||
{
|
||
var password = GetPassword();
|
||
if (string.IsNullOrEmpty(password))
|
||
{
|
||
return false;
|
||
}
|
||
return Login(password);
|
||
}
|
||
|
||
return Login(_password);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Login with identical password as last time
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
/// <remarks date="2019-Jun-23" author="Thomas Wiedebusch">
|
||
/// - Initial
|
||
/// </remarks>
|
||
/// <remarks date="2022-Jul-22" author="Thomas Wiedebusch">
|
||
/// - Immediately returns if already logged in.
|
||
/// </remarks>
|
||
public Boolean ReLogin()
|
||
{
|
||
if (IsLoggedOn)
|
||
return true;
|
||
return string.IsNullOrEmpty(_password) ? Login() : Login(_password);
|
||
}
|
||
|
||
private class OfflinePasswordItem
|
||
{
|
||
public OfflinePasswordItem(String pcbId, String password)
|
||
{
|
||
PcbId = pcbId;
|
||
Password = password;
|
||
|
||
}
|
||
|
||
public String PcbId
|
||
{
|
||
get;
|
||
}
|
||
public String Password
|
||
{
|
||
get;
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Starting a timer to keep session active,
|
||
/// starting the <see cref="KeepSessionThreadLoop"/>.
|
||
/// </summary>
|
||
/// <param name="password">password string to login</param>
|
||
/// <param name="runImmediately"> true = process command; false = add command to list. call
|
||
/// <see cref="RequestProtocolProcess()"></see> to process login command </param>
|
||
/// <param name="skipReadMeterFwAndAssignRegisters"></param>
|
||
/// <returns></returns>
|
||
/// <remarks date="2019JAN01" author="drabesch">
|
||
/// pass runImmediately for auto login
|
||
/// </remarks>
|
||
/// <remarks date="2021-Feb-02" author="Thomas Wiedebusch">
|
||
/// - Avoid retries during login, a retry will lock the Genesis for 2s, 4s, 8s, 16s and so on.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Feb-20" author="Thomas Wiedebusch">
|
||
/// - Quick login removed.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Mar-06" author="Thomas Wiedebusch">
|
||
/// - Pulse module deactivated at login.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Mar-07" author="Thomas Wiedebusch">
|
||
/// - REMOVED: Pulse module deactivated at login.
|
||
/// - Skip FW readout to quick connect being able to immediately switch off the pulse mode before App detection.
|
||
/// </remarks>
|
||
public Boolean Login(String password, Boolean runImmediately = true, Boolean skipReadMeterFwAndAssignRegisters = false)
|
||
{
|
||
|
||
var backupRetries = CommunicationConfig.RequestRetries;
|
||
CommunicationConfig.RequestRetries = 0;
|
||
PrivilegeCommandToMeter(LoginLvl, runImmediately);
|
||
|
||
var responseRecord = LoginCommandToMeter(password, runImmediately);
|
||
|
||
if (!runImmediately)
|
||
{
|
||
RequestProtocol.ReorderRecordList(Register.Configexchange.Privilege);
|
||
}
|
||
|
||
RequestProtocol.ProcessRecordList();
|
||
|
||
IsLoggedOn = responseRecord.Acknowledge == RequestAcknowledgeState.Ok
|
||
&& responseRecord.ResponseErrorBase == 0
|
||
&& responseRecord.ResponseErrorReason == 0;
|
||
_password = password;
|
||
if (IsLoggedOn)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Successful logged in to meter");
|
||
StartKeepSession();
|
||
|
||
if (Configuration != null && !Configuration.ProductionMode)
|
||
{
|
||
IsDevelopmentUsage = true;
|
||
}
|
||
|
||
if (!skipReadMeterFwAndAssignRegisters)
|
||
ReadMeterFirmwareAndAssignRegisters();
|
||
}
|
||
else
|
||
{
|
||
_logger.Warn($"Slot:{Slot} - Login to meter failed!");
|
||
}
|
||
|
||
CommunicationConfig.RequestRetries = backupRetries;
|
||
|
||
return IsLoggedOn;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Building the FW Version string out of 2 bytes of data.
|
||
/// REASON: FLEXNETVERSION is one application which does not follow the same rule of
|
||
/// decimal numbers. Instead it uses hexadecimal digits. After the conversion of this
|
||
/// number to a pure Uint32, needed for comparison of file versions in configuration.json,
|
||
/// the hexadecimal outline will be lost and the comparison with the rowproduct.txt fails.
|
||
/// FW-update needs this information to validate a tested package!
|
||
/// </summary>
|
||
/// <param name="msb">most significant byte</param>
|
||
/// <param name="lsb">last significant byte</param>
|
||
/// <returns>converted "msb.lsb" as string e.g. "12.34" or "2.0C" </returns>
|
||
public static String BuildFwVersionString(Byte msb, Byte lsb)
|
||
{
|
||
//skip leading 0
|
||
var msbPartString = (msb & 0xF0) > 0 ? $"{(msb & 0xF0) >> 4:X0}" : "";
|
||
return msbPartString + $"{msb & 0x0F:X0}" + "." + $"{(lsb & 0xF0) >> 4:X1}" + $"{lsb & 0x0F:X1}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Building a version string with 2 decimals e.g. " 12.34"
|
||
/// </summary>
|
||
/// <remarks date="2022-Dec-02" author="Thomas Wiedebusch">
|
||
/// - Initial.
|
||
/// </remarks>
|
||
/// <param name="version">the version</param>
|
||
/// <returns>converted version to string </returns>
|
||
public static String BuildFwVersionString(Int32? version)
|
||
{
|
||
return version == null ? "0.00" : $@"{version / 100}.{version % 100:D2}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// List of all present meter applications
|
||
/// </summary>
|
||
public List<MeterApplications> MeterAppListVersion = new List<MeterApplications>();
|
||
|
||
/// <summary>
|
||
/// Core revision of boot code.
|
||
/// </summary>
|
||
//public String CoreRevision;
|
||
public Int32? CoreRevision;
|
||
|
||
/// <summary>
|
||
/// This is the FLEXNETVERSION version which describes the entire packet.
|
||
/// </summary>
|
||
public String FwVersion;
|
||
|
||
/// <summary>
|
||
/// This is the Metrology Lookup Table CRC.
|
||
/// </summary>
|
||
public String LutCrc;
|
||
|
||
/// <summary>
|
||
/// This is the meter size.
|
||
/// </summary>
|
||
public String MeterSize;
|
||
|
||
/// <summary>
|
||
/// Region (EMEA, NA or China).
|
||
/// </summary>
|
||
public String Region;
|
||
|
||
/// <summary>
|
||
/// Radio frequency in MHz (433 or 868 or null).
|
||
/// </summary>
|
||
public Int32? RadioFrequencyMhz;
|
||
|
||
/// <summary>
|
||
/// Metrology upgrade permission.
|
||
/// </summary>
|
||
public Byte MetrologyUpgradePermission;
|
||
|
||
/// <summary>
|
||
/// Check region and size.
|
||
/// After reading the version, all valid registers are going to be selected.
|
||
/// </summary>
|
||
/// <remarks date="2023-Jan-19" author="Thomas Wiedebusch">
|
||
/// - Initial, extracted from <see cref="ReadMeterFirmwareAndAssignRegisters"/>
|
||
/// </remarks>
|
||
public void CheckRegionSizeLutCrc()
|
||
{
|
||
//read metrology lookup table CRC
|
||
try
|
||
{
|
||
var lutCrcRaw = ReadRegister(Register.Genesisflow.LookupFileCrc);
|
||
if (lutCrcRaw != null)
|
||
{
|
||
var lutCrc = RegisterConverter.ConvertTo<UInt32>(lutCrcRaw);
|
||
LutCrc = $@"0x{lutCrc:X4}";
|
||
}
|
||
}
|
||
catch (Exception)// exception will be thrown if register is not present on versions without LUT
|
||
{
|
||
LutCrc = Constants.StrUnknown;
|
||
}
|
||
|
||
//read meter size
|
||
MeterSize = MeterSizeConverter.ConvertMeterSizeEnumToSizeName((MeterSize)RegisterConverter.ConvertTo<Byte>(
|
||
ReadRegister(Register.Genesisflow.MeterSize)));
|
||
|
||
//read radio frequency
|
||
Region = Constants.StrUnknown;
|
||
RadioFrequencyMhz = null;
|
||
try
|
||
{
|
||
var radioFrequencyRaw = ReadRegister(Register.Sensusradio.FrequencyIndicator);
|
||
if (radioFrequencyRaw != null)
|
||
{
|
||
Region = "EMEA";
|
||
RadioFrequencyMhz = RegisterConverter.ConvertTo<Int32>(radioFrequencyRaw);
|
||
}
|
||
else
|
||
{
|
||
Region = "NA";
|
||
}
|
||
}
|
||
catch (Exception) // exception will be thrown on uninstalled SENSUSRADIO
|
||
{
|
||
Region = "NA";
|
||
}
|
||
|
||
// check meter size string contains "DN", because if "US" it cannot be an EMEA version
|
||
if ((Region.Contains("NA") && MeterSize.Contains("DN")) ||
|
||
(Region.Contains("EMEA") && MeterSize.Contains("US")))
|
||
{
|
||
Region = Constants.StrUnknown;
|
||
MeterSize = Constants.StrUnknown;
|
||
LutCrc = Constants.StrUnknown;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reading all applications which can be found in the configuration.json and have been stored
|
||
/// to the meter register dictionary in advance.
|
||
/// The read process covers the FW version and the CRC.
|
||
/// After reading the version, all valid registers are going to be selected.
|
||
/// </summary>
|
||
/// <remarks date="2021-Mar-10" author="Thomas Wiedebusch">
|
||
/// - Removed "Cordonel " from CoreRevision to have the e.g. "1.64" remaining
|
||
/// </remarks>
|
||
/// <remarks date="2021-Apr-15" author="Thomas Wiedebusch">
|
||
/// - Read radio frequency
|
||
/// </remarks>
|
||
/// <remarks date="2022-Mar-30" author="Thomas Wiedebusch">
|
||
/// - Read lockup table CRC,
|
||
/// - Read meter size.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Apr-27" author="Thomas Wiedebusch">
|
||
/// - Read lockup table CRC bugfix with try, catch for legacy versions, where this register does not exists.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Dec-02" author="Thomas Wiedebusch">
|
||
/// - CoreRevision from String to Int32?,
|
||
/// - MeterSize from String to MeterSize,
|
||
/// - Region separated from RadioRegion as String,
|
||
/// - RadioFrequencyMhz introduced as Int32? (null if "NA" region or not installed),
|
||
/// - LutCrc from String to Int32?.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Jan-16" author="Thomas Wiedebusch">
|
||
/// - Checked meter size string for "DN", than it cannot be an EMEA version.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Feb-13" author="Thomas Wiedebusch">
|
||
/// - Removed retry as this will be handled by the <see cref="RequestProtocol"/>.
|
||
/// </remarks>
|
||
private void ReadMeterFirmwareAndAssignRegisters()
|
||
{
|
||
//avoid overwriting of list if this already exits
|
||
if (MeterAppListVersion.Any())
|
||
{
|
||
_logger.Warn($"Slot:{Slot} - Trying to override meter dictionary. Skip read out FW");
|
||
return;
|
||
}
|
||
|
||
//Get all existing applications from meter register dictionary
|
||
foreach (var appVersionToRead in _configApplications.OrderBy(o => o.AppId))
|
||
{
|
||
UInt32 readFwVersion = 0;
|
||
UInt16 readFwCrc = 0;
|
||
var isInstalled = false;
|
||
var versionString = "";
|
||
if (WriteRegister(Register.System.CheckFwPresence, appVersionToRead.AppId))
|
||
{
|
||
//read version, skip retries on SYSTEM 0x04-> FW nor present is a valid feedback
|
||
var readFwVersionBytes = ReadRegister(Register.System.CheckFwPresence, 4);
|
||
|
||
//the FW version as Uint32 will be used for comparison with the configuration.json
|
||
if (readFwVersionBytes != null && readFwVersionBytes.Length > 0)
|
||
{
|
||
readFwVersion = (UInt32)(((readFwVersionBytes[1] & 0xF0) >> 4) * 10 +
|
||
(readFwVersionBytes[1] & 0x0F)) * 100;
|
||
readFwVersion += (UInt32)(((readFwVersionBytes[0] & 0xF0) >> 4) * 10 +
|
||
(readFwVersionBytes[0] & 0x0F));
|
||
//build the version string, because e.g. FLEXNETVERSION is one application which does not
|
||
//follow the same rule. After the upper conversion, the hexadecimal outline will be lost
|
||
versionString = BuildFwVersionString(readFwVersionBytes[1], readFwVersionBytes[0]);
|
||
if (appVersionToRead.AppName == "FLEXNETVERSION")
|
||
FwVersion = versionString;
|
||
|
||
if (readFwVersion != 0)
|
||
{
|
||
|
||
isInstalled = true;
|
||
//write application Id to CRC register
|
||
if (WriteRegister(Register.System.CheckFwCrc, appVersionToRead.AppId))
|
||
{
|
||
readFwCrc = RegisterConverter.ConvertTo<UInt16>(ReadRegister(Register.System.CheckFwCrc));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//add application always, even if this is not installed. This will be used for FW updates
|
||
MeterAppListVersion.Add(new MeterApplications()
|
||
{
|
||
AppId = appVersionToRead.AppId,
|
||
Version = readFwVersion,
|
||
StrVersion = versionString,
|
||
AppName = appVersionToRead.AppName,
|
||
Crc = readFwCrc,
|
||
Status = isInstalled ? MeterAppState.Unknown : MeterAppState.MeterAppNotInstalled,
|
||
IsInstalled = isInstalled,
|
||
});
|
||
}
|
||
|
||
CheckRegionSizeLutCrc();
|
||
|
||
//read metrology upgrade permission
|
||
MetrologyUpgradePermission = RegisterConverter.ConvertTo<Byte>(
|
||
ReadRegister(Register.System.MetrologyUpgradePermission));
|
||
|
||
//read core revision
|
||
var coreRegisterRead = RegisterConverter.ConvertTo<String>(ReadRegister(Register.System.CoreRevision, 16));
|
||
CoreRevision = null;
|
||
if (!string.IsNullOrEmpty(coreRegisterRead))
|
||
{
|
||
var coreRevisionStrings = coreRegisterRead.Split('\0');
|
||
//remove Cordonel sub-string and the blanks
|
||
var coreRevisionSubString = $"{coreRevisionStrings[0].ToLower().Replace("cordonel ", "")}";
|
||
var coreRevisionSubStrings = coreRevisionSubString.Split('.');
|
||
if (int.TryParse(coreRevisionSubStrings[0], out var coreRevisionMsb))
|
||
{
|
||
if (int.TryParse(coreRevisionSubStrings[1], out var coreRevisionLsb))
|
||
{
|
||
CoreRevision = coreRevisionMsb * 100 + coreRevisionLsb;
|
||
}
|
||
}
|
||
}
|
||
|
||
//log application information to file
|
||
_logger.Info($"Slot:{Slot} - PCB ID: {PcbId}");
|
||
_logger.Info($"Slot:{Slot} - Date time (UTC): {DateTimeOffset.UtcNow}");
|
||
if (CoreRevision != null)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - System Core Revision: {BuildFwVersionString(CoreRevision)}");
|
||
}
|
||
|
||
_logger.Info($"Slot:{Slot} - Region: {Region}");
|
||
|
||
if (RadioFrequencyMhz != null)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Radio frequency [MHz]: {RadioFrequencyMhz}");
|
||
}
|
||
|
||
foreach (var fm in MeterAppListVersion.OrderBy(o => o.AppId))
|
||
{
|
||
var versionString = fm.IsInstalled
|
||
? $"V: {fm.StrVersion} - CRC: 0x{fm.Crc:X4}" : "Not installed";
|
||
_logger.Info($"Slot:{Slot} - AppId: 0x{fm.AppId:X2} - {versionString} - AppName: {fm.AppName}");
|
||
}
|
||
|
||
_logger.Info($"Slot:{Slot} - Meter size: {MeterSize}");
|
||
_logger.Info($"Slot:{Slot} - Upgrade permission: {MetrologyUpgradePermission:X2}");
|
||
//remove unused registers with invalid version
|
||
var tempConfigRegisters = _configRegister.MeterRegisterDic.ToList();
|
||
|
||
_configRegister.MeterRegisterDic.Clear();
|
||
foreach (var registerToCheck in tempConfigRegisters)
|
||
{
|
||
var currentGroup = MeterAppListVersion.First(f => f.AppId == registerToCheck.Key.AppAddress);
|
||
|
||
if (!registerToCheck.Key.RegisterDetail.Version.First.HasValue &&
|
||
!registerToCheck.Key.RegisterDetail.Version.Last.HasValue)
|
||
{
|
||
//TODO legacy to collect not clear specified registers
|
||
registerToCheck.Key.IsAvailable = true;
|
||
_configRegister.MeterRegisterDic.TryAdd(registerToCheck.Key, null);
|
||
continue;
|
||
}
|
||
if (registerToCheck.Key.RegisterDetail.Version.Last != null &&
|
||
registerToCheck.Key.RegisterDetail.Version.First != null &&
|
||
registerToCheck.Key.RegisterDetail.Version.First.Value <= currentGroup.Version &&
|
||
registerToCheck.Key.RegisterDetail.Version.Last.Value >= currentGroup.Version)
|
||
{
|
||
registerToCheck.Key.IsAvailable = true;
|
||
_configRegister.MeterRegisterDic.TryAdd(registerToCheck.Key, null);
|
||
}
|
||
}
|
||
foreach (var checkMinRegister in Register.GetMinRequiredRegisters())
|
||
{
|
||
if (_configRegister.MeterRegisterDic.All(a => a.Key.GetIdent() != checkMinRegister))
|
||
{
|
||
// These registers ar required for essential operation, they have to be added even if the
|
||
// version in the configuration.json is outdated or they are not defined!
|
||
try
|
||
{
|
||
var allVersion = tempConfigRegisters.Where(a => a.Key.GetIdent() == checkMinRegister)?.OrderByDescending(
|
||
s => s.Key.RegisterDetail.Version.Last)?.First();
|
||
if (allVersion.HasValue)
|
||
{
|
||
allVersion.Value.Key.RegisterDetail.Version.Last = int.MaxValue;
|
||
_configRegister.MeterRegisterDic.TryAdd(allVersion.Value.Key, null);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
_logger.Warn(ex, $"Slot:{Slot} - Not able to set minimum list of registers. check json file for {checkMinRegister} ");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (IsDevelopmentUsage)
|
||
{
|
||
var registerToPotentialAdd = new List<RegisterDefinition>();
|
||
foreach (var allRegisterItem in tempConfigRegisters)
|
||
{
|
||
if (_configRegister.MeterRegisterDic.All(a => a.Key.GetIdent() != allRegisterItem.Key.GetIdent()))
|
||
{
|
||
registerToPotentialAdd.Add(allRegisterItem.Key);
|
||
}
|
||
}
|
||
if (registerToPotentialAdd.Any())
|
||
{
|
||
var registerAddForOldConfigs = registerToPotentialAdd.GroupBy(item => item.GetIdent())
|
||
.Select(grp => grp.Aggregate((max, cur) =>
|
||
(max == null || cur.RegisterDetail.Version.Last > max.RegisterDetail.Version.Last) ?
|
||
cur : max));
|
||
|
||
foreach (var registerAddOldConfig in registerAddForOldConfigs)
|
||
{
|
||
registerAddOldConfig.IsAvailable = false;
|
||
_configRegister.MeterRegisterDic.TryAdd(registerAddOldConfig, null);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Automatic login to meter enabled if logged out by lost authentication
|
||
/// </summary>
|
||
public void EnableAutoLogon()
|
||
{
|
||
_autoLogon = true;
|
||
StartKeepSession();
|
||
}
|
||
|
||
private void StartKeepSession()
|
||
{
|
||
if (!_autoLogon || string.IsNullOrEmpty(PcbId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (RequestProtocol == null)
|
||
{
|
||
return;
|
||
}
|
||
if (_keepSessionThread == null)
|
||
{
|
||
_keepSessionThread = new Thread(KeepSessionThreadLoop) { Name = $"Slot:{Slot} Keep session thread" };
|
||
ThreadWatcher.Instance.Start(_keepSessionThread);
|
||
|
||
_keepSessionTimer = new Timer(CommunicationConfig.KeepSessionTimeS * 1000);
|
||
_keepSessionTimer.Elapsed += KeepSessionTimerElapsed_SyncKeepSessionThread;
|
||
_keepSessionTimer.Start();
|
||
}
|
||
|
||
}
|
||
|
||
private void StopKeepSession()
|
||
{
|
||
//if (!_autoLogon) return;
|
||
_autoLogon = false;
|
||
if (_keepSessionTimer != null)
|
||
{
|
||
_keepSessionTimer.Stop();
|
||
_keepSessionTimer.Dispose();
|
||
}
|
||
|
||
if (_keepSessionThread != null)
|
||
{
|
||
_onSyncKeepSessionThread.Dispose();
|
||
_keepSessionThread = null;
|
||
|
||
}
|
||
|
||
if (RequestProtocol != null)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// If the timeout timer elapsed to keep the session active, this routine will be called
|
||
/// awaking the <see cref="KeepSessionThreadLoop"/>.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="eventARgs"></param>
|
||
private void KeepSessionTimerElapsed_SyncKeepSessionThread(Object sender, ElapsedEventArgs eventARgs)
|
||
{
|
||
if (_autoLogon)
|
||
{
|
||
//put KeepSessionThread state from WaitSleepJoin to Running
|
||
_onSyncKeepSessionThread.Set();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Thread to keep the connection to the meter open and the session alive,
|
||
/// because on missing communication the meter is going to logout automatically.
|
||
/// This thread will be started at <see cref="Login()"/> and aborted on
|
||
/// <see cref="Logout"/>
|
||
/// </summary>
|
||
private void KeepSessionThreadLoop()
|
||
{
|
||
try
|
||
{
|
||
while (_keepSessionThread != null && _keepSessionThread.IsAlive && !_keepSessionToken.IsCancellationRequested)
|
||
{
|
||
//put KeepSessionThread to WaitSleepJoin until next timer elapsed
|
||
_onSyncKeepSessionThread.WaitOne();
|
||
if (_autoLogon)
|
||
{
|
||
//check the communication time
|
||
_logger.Info($"Slot:{Slot} - Check last communication time");
|
||
if (RequestProtocol.LastCommTime.HasValue &&
|
||
RequestProtocol.LastCommTime.Value.AddSeconds(CommunicationConfig.KeepSessionTimeS)
|
||
< DateTimeOffset.UtcNow)
|
||
{
|
||
|
||
_logger.Info($"Slot:{Slot} - Force communication to keep session");
|
||
ReadRegister(Register.Genesisflow.LedMode);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (ThreadAbortException)
|
||
{
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Boolean Logout()
|
||
{
|
||
//check if not logged in
|
||
if (!IsLoggedOn)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
StopKeepSession();
|
||
_autoLogon = false;
|
||
|
||
if (PrivilegeCommandToMeter(LogoutLvl))
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Successful logged out from meter");
|
||
IsLoggedOn = false;
|
||
return true;
|
||
}
|
||
_logger.Warn($"Slot:{Slot} - Logout from meter failed");
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Check if EMPTY_PIPE or REBOOT is set
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public Boolean CheckStateAfterMeasurement()
|
||
{
|
||
return HasAlarms(Alarm.EMPTY_PIPE | Alarm.REBOOT);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Check if alarmToCheck is set on register
|
||
/// </summary>
|
||
/// <param name="alarmToCheck">is Flags so you can use more than one alarms (like Alarm.EMPTY_PIPE | Alarm.REBOOT)</param>
|
||
/// <returns></returns>
|
||
public Boolean HasAlarms(Alarm alarmToCheck)
|
||
{
|
||
var currentAlarms = GetAlarms();
|
||
return currentAlarms.HasFlag(alarmToCheck);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Read out all AlarmStatus register and combine them to one <see cref="Alarm"/>
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public Alarm GetAlarms()
|
||
{
|
||
List<String> alarms = new List<String>()
|
||
{
|
||
Register.Customer.AlarmStatus0,
|
||
Register.Customer.AlarmStatus1,
|
||
Register.Customer.AlarmStatus2,
|
||
Register.Customer.AlarmStatus3,
|
||
Register.Customer.AlarmStatus4,
|
||
Register.Customer.AlarmStatus5,
|
||
Register.Customer.AlarmStatus6,
|
||
Register.Customer.AlarmStatus7
|
||
};
|
||
List<Byte> responseAllAlarms = new List<Byte>();
|
||
|
||
foreach (var regRead in alarms)
|
||
{
|
||
var a = ReadRegister(regRead);
|
||
if (a != null)
|
||
{
|
||
foreach (var resp in a)
|
||
{
|
||
responseAllAlarms.Add(resp);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
var i = 0;
|
||
var cReg = 0;
|
||
foreach (var val in responseAllAlarms)
|
||
{
|
||
if (val > 0)
|
||
{
|
||
i += (1 << cReg);
|
||
}
|
||
cReg += 1;
|
||
}
|
||
|
||
var fullAlarm = (Alarm)i;
|
||
|
||
return fullAlarm;
|
||
//ReadRegister()
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reset the empty pipe alarm
|
||
/// </summary>
|
||
public void ResetAlarm(Alarm clear)
|
||
{
|
||
WriteRegister(Register.Customer.TriggerAlarmCancel, clear.GetHashCode());
|
||
}
|
||
/// <summary>
|
||
/// Alarm messages
|
||
/// </summary>
|
||
[Flags]
|
||
public enum Alarm
|
||
{
|
||
// ReSharper disable InconsistentNaming
|
||
#pragma warning disable CS1591
|
||
REBOOT = 1 << 0,
|
||
LOW_BATTERY = 1 << 1,
|
||
EMPTY_PIPE = 1 << 4,
|
||
REVERSE_FLOW = 1 << 6,
|
||
SUSPECT_LEAK = 1 << 7,
|
||
BROKEN_PIPE = 1 << 8,
|
||
LOW_PRESSURE = 1 << 9,
|
||
HIGH_PRESSURE = 1 << 10,
|
||
LOW_TEMPERATURE = 1 << 11,
|
||
HIGH_TEMPERATURE = 1 << 12,
|
||
RADIO_ERROR = 1 << 13,
|
||
METROLOGY_PARAMS = 1 << 14,
|
||
METROLOGY_MEASURE = 1 << 15,
|
||
|
||
ALL = 0xFFFFFF,
|
||
#pragma warning restore CS1591
|
||
// ReSharper restore InconsistentNaming
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads the PCB Identification, can be accessed at all login levels,
|
||
/// Read Privilege is always needed to BUGFIX read access to PCB ID
|
||
/// </summary>
|
||
/// <returns>PCB ID</returns>
|
||
/// <remarks date="2018JAN08" author="R.Drabesch">
|
||
/// - PcbId handling improved.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Nov-02" author="T.Wiedebusch">
|
||
/// - PcbId handling improved,
|
||
/// - Removed retries as these are handled by the protocol.
|
||
/// </remarks>
|
||
public String GetPcbId()
|
||
{
|
||
|
||
if (RequestPort == null)
|
||
{
|
||
return "Request port not assigned";
|
||
}
|
||
// Read Privilege is always needed to BUGFIX read access to PCB ID
|
||
_logger.Info($"Slot:{Slot} - Get privilege level");
|
||
ReadRegister(Register.Configexchange.Privilege);
|
||
|
||
_logger.Info($"Slot:{Slot} - Old PCB identification was {PcbId}");
|
||
|
||
var pcbIdRaw = ReadRegister(Register.Configexchange.PcbSerialNumber);
|
||
if (pcbIdRaw != null)
|
||
{
|
||
PcbId = Encoding.ASCII.GetString(pcbIdRaw).Split('\0')[0];
|
||
_logger.Info($"Slot:{Slot} - Get PCB identification {PcbId}");
|
||
return PcbId;
|
||
}
|
||
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the actual register dictionary
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public ConcurrentDictionary<RegisterDefinition, Byte[]> GetRegistersDic()
|
||
{
|
||
if (_configRegister?.MeterRegisterDic == null || !_configRegister.MeterRegisterDic.Any())
|
||
{
|
||
return new ConcurrentDictionary<RegisterDefinition, Byte[]>();
|
||
}
|
||
return _configRegister.MeterRegisterDic;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Serial number of meter married with PcbId
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public Int32 GetSerialNumber()
|
||
{
|
||
return RelatePcb.GetSerialNumber(ServiceUrls.MarriageServiceUrl(), GetPcbId());
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetLcdText(Boolean release, Byte[] textInHex)
|
||
{
|
||
if (release)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Set LCD into active mode");
|
||
WriteRegister(Register.Genesisflow.TriggerIdle, new Byte[] { 0x00, 0x00, 0x00, 0x00 });
|
||
}
|
||
else
|
||
{
|
||
if (!textInHex.Any() || textInHex.Length != 2)
|
||
{
|
||
throw new ApplicationException("Text cannot be set, please use 2 byte array for 4 hexadecimal digits");
|
||
}
|
||
//Trigger write is allowed only during logged in with level 7 or 8
|
||
if (IsLoggedOn)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Set LCD text to {textInHex[0]} {textInHex[1]}");
|
||
WriteRegister(Register.Genesisflow.TriggerIdle,
|
||
new Byte[] { textInHex[1], textInHex[0], 0x00, 0x00 }, true, true);
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Register
|
||
|
||
/// <summary>
|
||
/// Event to Sync Register on MeterSide and <see cref="_configRegister" />
|
||
/// Fired on Write or read register
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e">Register to update with value to update</param>
|
||
private void RequestProtocol_MeterRegisterUpdated(Object sender, RegisterUpdatedEventArgs e)
|
||
{
|
||
_configRegister.Set(e.Register, e.Value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Write Register, optional: wait for result and validate,
|
||
/// write register will ALWAYS log the data DON'T use for password write
|
||
/// </summary>
|
||
/// <typeparam name="T">Data type of Register</typeparam>
|
||
/// <param name="reg">Register to change</param>
|
||
/// <param name="value">Value to save</param>
|
||
/// <param name="waitForResult">wait until result is ready</param>
|
||
/// <param name="checkRegister">check the content of the register by read back</param>
|
||
/// <param name="skipRetryErrorCode">skip retries on this error code return</param>
|
||
/// <returns>true on successful operation</returns>
|
||
public Boolean WriteRegister<T>(String reg, T value, Boolean waitForResult = true,
|
||
Boolean checkRegister = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
|
||
{
|
||
try
|
||
{
|
||
var regDef = _configRegister.GetRegisterDefinitionByName(reg);
|
||
var data = RegisterConverter.ConvertFrom(value);
|
||
var hideDataInLog = regDef.RegisterName.Contains("EncryptionKey") || regDef.RegisterName.Contains("Password");
|
||
|
||
var rawMsg = hideDataInLog ? "(*****)" : $"({BitConverter.ToString(data.ToArray())})";
|
||
_logger.Info($"Slot:{Slot} - Write register({regDef.GetIdent()}), record({rawMsg})");
|
||
|
||
PreRegisterWrite(regDef, data);
|
||
|
||
var command = data.Length > RegisterDefinition.ChunkSize ?
|
||
Commands.MultipleWriteData : Commands.WriteData;
|
||
|
||
var responseRecord = RequestProtocol.CommandToMeter(command, regDef, data,
|
||
hideDataInLog: hideDataInLog, skipRetryErrorCode: skipRetryErrorCode);
|
||
|
||
if (!waitForResult && !checkRegister)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
RequestProtocolProcess();
|
||
|
||
//if register check by read back is not required we return the acknowledge state
|
||
if (!checkRegister || regDef.RegisterDetail.Privilege.Lvl8 == Registers.Access.WO)
|
||
{
|
||
return responseRecord.Acknowledge == RequestAcknowledgeState.Ok;
|
||
}
|
||
|
||
var registerData = ReadRegister(reg, data.Length, skipRetryErrorCode);
|
||
//check if register read was okay and read and written data are EQUAL
|
||
if (registerData == null || registerData.Length < data.Length ||
|
||
data.Where((t, i) => registerData[i] != t).Any())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return responseRecord.Acknowledge == RequestAcknowledgeState.Ok;
|
||
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_logger.Error($"Slot:{Slot} - {e}");
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Read Register and return byte array
|
||
/// </summary>
|
||
/// <param name="reg"></param>
|
||
/// <param name="expectedLength">expected length for response</param>
|
||
/// <param name="skipRetryErrorCode">skip retries on this error code return</param>
|
||
/// <returns></returns>
|
||
public Byte[] ReadRegister(String reg, Int32? expectedLength = null,
|
||
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
|
||
{
|
||
|
||
try
|
||
{
|
||
var regDef = _configRegister.GetRegisterDefinitionByName(reg);
|
||
_configRegister.Set(regDef, null);
|
||
_logger.Info($"Slot:{Slot} - Read register({regDef.GetIdent()})");
|
||
|
||
var hideDataInLog = regDef.RegisterName.Contains("EncryptionKey");
|
||
|
||
// setup length based on data type
|
||
if (!expectedLength.HasValue)
|
||
{
|
||
expectedLength = RegisterConverter.SizeOf(regDef);
|
||
}
|
||
|
||
var dataLengthPreset = expectedLength.Value;
|
||
if (expectedLength.Value <= RegisterDefinition.ChunkSize)
|
||
{
|
||
RequestProtocol?.CommandToMeter(Commands.ReadData, regDef, hideDataInLog: hideDataInLog,
|
||
skipRetryErrorCode: skipRetryErrorCode);
|
||
}
|
||
else
|
||
{
|
||
RequestProtocol?.CommandToMeter(Commands.MultipleReadData, regDef, expectedLength: dataLengthPreset,
|
||
hideDataInLog: hideDataInLog, skipRetryErrorCode: skipRetryErrorCode);
|
||
}
|
||
|
||
RequestProtocolProcess();
|
||
var result = _configRegister.Get(reg);
|
||
PostRegisterRead(regDef, result);
|
||
return result;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_logger.Error($"Slot:{Slot} - {e}");
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <remarks date="2020-Dec-19" author="Thomas Wiedebusch">
|
||
/// - Removed throw because the throw always kicks in to
|
||
/// the FW-Update process causing an unpredictable stop!
|
||
/// </remarks>
|
||
private void RequestProtocolProcess()
|
||
{
|
||
switch (RequestProtocol.ProcessRecordList())
|
||
{
|
||
case RequestAcknowledgeState.AuthorizationRequired:
|
||
|
||
//access only if IsLoggedOn marker is set and authorization has been lost
|
||
if (IsLoggedOn)
|
||
{
|
||
_logger.Debug($"Slot:{Slot} - Authorization is required");
|
||
|
||
if (!string.IsNullOrEmpty(_password))
|
||
{
|
||
IsLoggedOn = false;
|
||
if (Login(_password, false))
|
||
{
|
||
}
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case RequestAcknowledgeState.Ok:
|
||
break;
|
||
|
||
//try out this
|
||
case RequestAcknowledgeState.CommandError:
|
||
case RequestAcknowledgeState.MeterError:
|
||
Thread.Sleep(200);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Send UI1236 command
|
||
/// </summary>
|
||
/// <remarks date="2022-Aug-24" author="R.Drabesch">
|
||
/// - Initial
|
||
/// </remarks>
|
||
// ReSharper disable once InconsistentNaming UI1236 is a naming forced by the caller
|
||
public Byte[] SendUI1236Command(Byte[] ui1236Frame, Boolean waitForResult = true, Boolean checkRegister = false,
|
||
UInt16 skipRetryErrorCode = 4)
|
||
{
|
||
var requestIdent = $"Slot:{Slot} - Sending Ui-1236 frame";
|
||
_logger.Info(requestIdent);
|
||
var sendFifoUi1236 = RequestProtocol.AddRecordToSendFifoUI1236(requestIdent, ui1236Frame);
|
||
RequestProtocolProcess();
|
||
return sendFifoUi1236.ResponsePayload.ToArray();
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region Class handling
|
||
/// <summary>
|
||
/// Clear Ports
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
StreamingPort?.Clear();
|
||
RequestPort?.Clear();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void DisposeMeter()
|
||
{
|
||
Dispose();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void Dispose()
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Start to dispose");
|
||
|
||
|
||
LogRawData(false);
|
||
LogManager.Flush();
|
||
//isPart of Logout
|
||
//StopKeepSession();
|
||
|
||
//Cancel receive tokens
|
||
_keepSessionToken?.Cancel();
|
||
|
||
try
|
||
{
|
||
Logout();
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// ignored
|
||
}
|
||
|
||
|
||
//Run thread again to notice CancellationToken has changed
|
||
_onSyncKeepSessionThread?.Set();
|
||
|
||
//this timeout counter is being used for dispose only
|
||
var timeoutCounter = 100;
|
||
if (_keepSessionThread != null)
|
||
{
|
||
while (_keepSessionThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0)
|
||
{
|
||
Thread.Sleep(1);
|
||
timeoutCounter -= 1;
|
||
}
|
||
|
||
if (_keepSessionThread.ThreadState != ThreadState.Stopped)
|
||
{
|
||
//if thread is still running try to abort
|
||
// try to avoid abort (takes age to run and is not safe)
|
||
_keepSessionThread.Abort();
|
||
}
|
||
}
|
||
|
||
_keepSessionThread = null;
|
||
|
||
RequestPort?.Dispose();
|
||
StreamingPort?.Dispose();
|
||
|
||
RequestProtocol?.Dispose();
|
||
StreamingProtocol?.Dispose();
|
||
_logger.Trace($"Slot:{Slot} - Dispose is finished");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Logging
|
||
/// <inheritdoc />
|
||
public void WriteLog(String text)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - {text}");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void LogRawData(Boolean on)
|
||
{
|
||
_enableRawDataLogging = on;
|
||
if (StreamingPort is LedSerialPort port)
|
||
{
|
||
port.RecordStreamingRawData = _enableRawDataLogging;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set the process state to display and to the production data base
|
||
/// </summary>
|
||
/// <param name="newState"></param>
|
||
public void SetProcessState(DisplayCodes newState)
|
||
{
|
||
SetProcessState(newState, true);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetProcessState(DisplayCodes newState, Boolean webRequest)
|
||
{
|
||
|
||
if (webRequest)
|
||
{
|
||
var url = ServiceUrls.MeterProcessStateUrl();
|
||
url += $"setState?PcbID={PcbId}&code={newState.GetHashCode()}&version={Assembly.GetExecutingAssembly().GetName().Version}";
|
||
try
|
||
{
|
||
LocalWebRequest.PostRequestAsync(url);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Info(
|
||
$"Slot:{Slot} - Cannot store process state to service, URL({url}), Error({ex.Message})");
|
||
}
|
||
}
|
||
|
||
SetLcdText(false, BitConverter.GetBytes((Int64)newState).Take(2).ToArray());
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public DisplayCodes GetLastProcessState()
|
||
{
|
||
var url = ServiceUrls.MeterProcessStateUrl();
|
||
|
||
url += $"GetLastState?PcbID={PcbId}";
|
||
|
||
try
|
||
{
|
||
|
||
Int32 i;
|
||
if (int.TryParse(LocalWebRequest.GetRequest(url), out i))
|
||
{
|
||
return ((DisplayCodes)i);
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Cannot store process state to service, URL({url}), Error({ex.Message})");
|
||
}
|
||
return DisplayCodes.None;
|
||
}
|
||
|
||
|
||
private void PreRegisterWrite(RegisterDefinition reg, Byte[] value)
|
||
{
|
||
//if (_configuration.UseMinMaxCheck)
|
||
//{
|
||
// var result = RegisterCheck.CheckWrite(reg, value);
|
||
// if (!string.IsNullOrEmpty(result))
|
||
// {
|
||
// if (reg.GetIdent().ToUpper() != "GENESISFLOW_DisplayPow10".ToUpper())
|
||
// {
|
||
// throw new Exception(result);
|
||
// }
|
||
|
||
// }
|
||
//}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Executes all StoreConfiguration and StoreCalibration for each application.
|
||
/// </summary>
|
||
/// <returns>true if all configurations are stored</returns>
|
||
/// <remarks date="2019-Apr-06" author="Roland Drabesch">
|
||
/// - Initial
|
||
/// </remarks>
|
||
/// <remarks date="2022-Apr-21" author="Thomas Wiedebusch">
|
||
/// - Corrected logic to enter write and read loop,
|
||
/// - Removed SENSUSRADIO from read check as it returns NULL on read of StoreConfiguration.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Apr-26" author="Thomas Wiedebusch">
|
||
/// - After write store configuration extended sleep.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Jun-23" author="Thomas Wiedebusch">
|
||
/// - Removed comparison of allConfigRegisters less than 8 because the NA version has less registers and new
|
||
/// apps will have a new register.
|
||
/// </remarks>
|
||
/// <remarks date="2022-Jul-22" author="Thomas Wiedebusch">
|
||
/// - Removed temporary NA2ALARMS read back as this has no handler in the FW.
|
||
/// </remarks>
|
||
/// <remarks date="2023-Nov-22" author="Thomas Wiedebusch">
|
||
/// - Stored first all configurations for ever app, then read it back.
|
||
/// </remarks>
|
||
public Boolean StoreAllConfigurations()
|
||
{
|
||
|
||
var retVal = true;
|
||
|
||
try
|
||
{
|
||
foreach (var a in _configRegister.MeterRegisterDic.Where(a =>
|
||
a.Key.RegisterName.ToLower() == "storeconfiguration" ||
|
||
a.Key.RegisterName.ToLower() == "storecalibration"))
|
||
{
|
||
if (!MeterAppListVersion.Where(v => v.AppId == a.Key.AppAddress).Any() ||
|
||
!MeterAppListVersion.Where(v => v.AppId == a.Key.AppAddress).First().IsInstalled)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
// retries for each register access
|
||
var retriesLeft = 2;
|
||
Boolean result;
|
||
do
|
||
{
|
||
Thread.Sleep(150);
|
||
result = WriteRegister(a.Key.GetIdent(), 1);
|
||
|
||
} while (!result && retriesLeft-- > 0);
|
||
|
||
if (!result)
|
||
{
|
||
retVal = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
retVal = false;
|
||
}
|
||
|
||
// check loop
|
||
try
|
||
{
|
||
foreach (var a in _configRegister.MeterRegisterDic.Where(a =>
|
||
a.Key.RegisterName.ToLower() == "storeconfiguration" ||
|
||
a.Key.RegisterName.ToLower() == "storecalibration"))
|
||
{
|
||
if (!MeterAppListVersion.Where(v => v.AppId == a.Key.AppAddress).Any() ||
|
||
!MeterAppListVersion.Where(v => v.AppId == a.Key.AppAddress).First().IsInstalled)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
//TODO enable this for R1.2.x take the version of FW
|
||
if (a.Key.AppName == "SENSUSRADIO" || a.Key.AppName == "NA2WALARMS")
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// retries for each register access
|
||
var retriesLeft = 2;
|
||
Boolean result;
|
||
do
|
||
{
|
||
var readResult = ReadRegister(a.Key.GetIdent());
|
||
result = readResult != null && readResult.ToList().All(array => array == 0x00);
|
||
} while (!result && retriesLeft-- > 0);
|
||
|
||
if (!result)
|
||
{
|
||
retVal = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
retVal = false;
|
||
}
|
||
|
||
return retVal;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Upload app list to data base
|
||
/// </summary>
|
||
/// <param name="progressName"></param>
|
||
/// <param name="version"></param>
|
||
public void PushProgress(String progressName, String version)
|
||
{
|
||
var apps = new List<CordonelAppVersion>();
|
||
foreach (var item in MeterAppListVersion)
|
||
{
|
||
apps.Add(new CordonelAppVersion(item.AppId, item.StrVersion, item.AppId != 15));
|
||
}
|
||
|
||
apps.Add(new CordonelAppVersion(-1, BuildFwVersionString(CoreRevision), false));
|
||
|
||
var url = $"{ServiceUrls.MeterProcessStateUrl()}SetCordonelProgress?PcbId={PcbId}" +
|
||
$"&ProgressName={progressName}&version={version}";
|
||
LocalWebRequest.PostRequestAsync(url, json: apps);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Backup current register dictionary to data base
|
||
/// </summary>
|
||
/// <exception cref="Exception"></exception>
|
||
public void StoreCurrentRegisterDic()
|
||
{
|
||
try
|
||
{
|
||
var url = ServiceUrls.RegisterWatchServiceUrl();
|
||
if (!string.IsNullOrEmpty(Configuration?.RegisterWatchServiceUrl))
|
||
{
|
||
url = Configuration.RegisterWatchServiceUrl;
|
||
}
|
||
|
||
var d = new List<RegisterToDb>();
|
||
|
||
foreach (var a in _configRegister.MeterRegisterDic)
|
||
{
|
||
try
|
||
{
|
||
d.Add(new RegisterToDb(a.Key.RegisterAddress, a.Value));
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// ignored
|
||
}
|
||
}
|
||
|
||
|
||
|
||
var r = LocalWebRequest.PostRequestAsync($"{url}FullRegisters?PcbID={PcbId}", 2000, d);
|
||
if (!r)
|
||
{
|
||
throw new Exception("Could not store CalibrationResults on server");
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error($"Slot:{Slot} - {ex} - Can't store at RegisterWatchService");
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Logging of register write processes to meter
|
||
/// ATTENTION: DON'T USE FOR PASSWORDS!
|
||
/// </summary>
|
||
/// <param name="reg"></param>
|
||
/// <param name="value"></param>
|
||
private void PostRegisterRead(RegisterDefinition reg, Byte[] value)
|
||
{
|
||
if (Configuration.UseRegisterWatchService)
|
||
{
|
||
try
|
||
{
|
||
var valuestring = "NULL";
|
||
|
||
if (value != null)
|
||
{
|
||
valuestring = BitConverter.ToString(value);
|
||
}
|
||
|
||
var url = $"{Configuration.RegisterWatchServiceUrl}RegisterWrite?PcbID={PcbId}&Address={BitConverter.ToString(reg.RegisterAddress)}&value={valuestring}";
|
||
|
||
var result = LocalWebRequest.PostRequestAsync(url, 1500, string.Empty);
|
||
|
||
if (!result)
|
||
{
|
||
_logger.Error(new Exception($"Slot:{Slot} - HTTP status not OK"),
|
||
"Can't store at RegisterWatchService");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error($"Slot:{Slot} - {ex} - Can't store at RegisterWatchService");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Storage of calibration values
|
||
/// </summary>
|
||
/// <param name="calibrationFactor1"></param>
|
||
/// <param name="calibrationFactor2"></param>
|
||
/// <param name="calibrationFactor3"></param>
|
||
public void LogCalibrationData(UInt16 calibrationFactor1, UInt16 calibrationFactor2, UInt16 calibrationFactor3)
|
||
{
|
||
if (Configuration.UseCalibrationLogger)
|
||
{
|
||
try
|
||
{
|
||
var request = (HttpWebRequest)WebRequest.Create(
|
||
$"{Configuration.CalibrationLoggerServiceUrl}PostCalibrationResult?PcbID={PcbId}" +
|
||
$"&flowCalibrationPath1={calibrationFactor1}&flowCalibrationPath2={calibrationFactor2}" +
|
||
$"&flowCalibrationPath3={calibrationFactor3}");
|
||
|
||
var data = Encoding.ASCII.GetBytes(string.Empty);
|
||
|
||
request.Method = "POST";
|
||
request.ContentType = "application/x-www-form-urlencoded";
|
||
request.ContentLength = data.Length;
|
||
|
||
using (var stream = request.GetRequestStream())
|
||
{
|
||
stream.Write(data, 0, data.Length);
|
||
}
|
||
|
||
var response = (HttpWebResponse)request.GetResponse();
|
||
//var responseStream = response.GetResponseStream();
|
||
//if (responseStream == null) return;
|
||
//var responseString = new StreamReader(responseStream).ReadToEnd();
|
||
|
||
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Accepted)
|
||
{
|
||
_logger.Error(new Exception($"Slot:{Slot} - HTTP status not OK({response.StatusCode})"), "can not store at RegisterWatchService");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error($"Slot:{Slot} - {ex} - can not store at RegisterWatchService");
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
} |