using Logic.ProductionToProductMapper.Cordonel;
using Newtonsoft.Json;
using NLog;
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 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.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.Protocols.ProtocolCore;
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.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
{
///
///
/// 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
///
[Guid("767AEE0F-7321-4ABF-9196-05CC249009B2"),
ComVisible(true),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(IMeterEvents))]
public partial class GenesisMeter : IMeter, IMeterEvents, IWaterMeterWithRegisters
{
#region ctor
/// Default constructor.
///
/// R.Drabesch, 2018-Feb-16.
public GenesisMeter()
{
SetLogger();
}
///
/// Process configuration
///
public ProcessConfig Configuration = new ProcessConfig();
///
/// Transmit protocol access for underlie objects to change response timeout
///
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;
///
///
/// Slot number for test-bench
/// for request Port
/// for streaming Port
/// don´t send CRC error or telegrams with error flag to caller
///
/// 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
///
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
///
/// Keep up the highest access level for all function in where used
///
private const Int32 LoginLvl = 8;
///
/// Logout with level 0
///
private const Int32 LogoutLvl = 0;
#endregion
#region Properties
private String _serialNumber = "";
///
///
/// Property is redundant because is also stored in
/// but never less is
/// is the identification of the meter it has a separate Property
///
public String SerialNumber
{
get => _serialNumber;
set
{
_serialNumber = value;
SetLogger();
}
}
///
///
/// Save Slot Number (position at test-bench) for logging purposes
///
public Int32 Slot
{
get; set;
}
///
/// Represent
/// proposed for login
///
public String PcbId
{
get; private set;
}
///
public Int32 IntermediateUpdateTimeS { get; set; } = CommunicationConfig.MeasurementUpdateTimeS;
///
/// Add on ctor a password and it will be used for login in if no other password is set
///
private String _password;
///
/// default false only enabled by
/// when it is true, the meter will enforce the login when session is gone or access is denied
///
private Boolean _autoLogon;
///
/// Enable raw record logging, if set every incoming package will be logged
///
private Boolean _enableRawDataLogging;
///
/// Enable use of registers that are not valid (last version not match the current app)
///
public Boolean IsDevelopmentUsage
{
get; set;
}
///
/// All parameter store in this Genesis is up in here
/// if you want to read them from meter use
/// if you want to set them to meter use
///
private readonly MeterRegisters _configRegister = new MeterRegisters();
///
/// All applications defined by configuration.json
///
private readonly List _configApplications = new List();
///
/// request port assignment/info
///
public IPort RequestPort
{
get; private set;
}
///
/// request protocol assignment/info
///
public RequestProtocol RequestProtocol;
///
/// streaming port assignment/info
///
public IPort StreamingPort
{
get; private set;
}
///
/// streaming protocol assignment/info
///
public StreamingProtocol StreamingProtocol;
///
/// to reduce the IrdA communication in test bench, preparation will be done once (when SkipPreparationForTestBench is true)
///
public Boolean SkipPreparationForTestBench
{
get; set;
}
///
/// Response received after request for record
///
public event EventHandler OnRequestRecordReceived;
///
public void SetConfigRegisterDefinitions(List allRegisters)
{
_configRegister.AddRegistersDefinitions(allRegisters);
}
private void SetConfigApplicationDefinitions(List allApplications)
{
foreach (var add in allApplications)
{
try
{
var addGenesisApplication = add;
if (addGenesisApplication != null)
{
_configApplications.Add(addGenesisApplication);
}
}
catch (Exception)
{
// ignored
}
}
}
///
/// Returns meter registers
///
///
public MeterRegisters GetConfigRegistersDefinitions()
{
return _configRegister;
}
///
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(str);
_logger.Trace($"Slot:{Slot} - Updated register definitions File");
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
///
/// do not use it to set ProcessStatus
/// if you want to change ProcessStatus of this genesis use
/// is just store for some routines
///
private ProcessState _myProcessStatus;
///
/// if state is change
///
/// 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");
}
}
}
///
/// do not use it to set ErrorStatus
/// if you want to change ErrorStatus of this genesis use
/// is just store for some routines
///
private ErrorState _myErrorState;
///
/// fires up
///
public ErrorState ErrorStatus
{
get
{
return _myErrorState;
}
set
{
_myErrorState = value;
Invoker(OnErrorStatusChanged);
}
}
///
/// Logged in to device
///
public Boolean IsLoggedOn
{
get; private set;
}
private String _currentActionText = "";
///
/// Hold the current process name e.g. FlowTest, Preadjustemtn for logging
///
public String CurrentActionText
{
get
{
return _currentActionText;
}
set
{
_currentActionText = value;
SetLogger();
}
}
///
public void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData = true)
{
SetupFromConfigFile(slot, ignoreCorruptedData, true, true);
}
///
/// Setup water meter from configuration file:
/// -slot,
/// -streaming protocol,
/// -streaming port
/// -request port
///
///
///
///
///
///
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(tr.ReadToEnd());
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);
}
}
}
}
///
/// just internal setup. called on ctor or from vb6 setup function
///
/// Slot Number for test-bench
/// for RFID/UART/IrDA Port
/// for LED Port
/// don´t send CRC error or telegrams with error flag to caller
///
/// 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
///
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}/{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");
}
///
///
/// Start Event listening, has to be called after
///
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");
}
///
/// Add a port to working queue
///
/// On should one once added in runtime
///
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)dataToSend.GetData();
port.PortWrite(data.ToArray());
};
}
///
/// Synchronization of the record for the streaming interface
///
///
///
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 HasInformationForCaller;
///
public event EventHandler OnCalibCompleted;
///
public event EventHandler OnErrorStatusChanged;
///
public event EventHandler OnProcessStatusChanged;
///
public event EventHandler OnCalibInitCompleted;
///
public event EventHandler OnInitCompleted;
///
public event EventHandler OnMeasurementInitCompleted;
///
public event EventHandler OnMeasurementCompleted;
///
/// call this if session is expired and you need an re-authorization
///
public EventHandler AuthorizationRequired;
///
/// A helper to invoke events only if someone is listening, otherwise nothing will happen
///
/// Event handler to call
/// Sender (can be null)
/// Event arguments (can be null)
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
}
}
///
/// Track all incoming led record packages (Calibration and Flow)
///
///
///
/// I should be or
/// otherwise this method does nothing
///
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));
}
}
///
/// tracking request record processing
///
///
///
private void RequestProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e)
{
if (e is RequestResponseDataEventArgs)
{
OnRequestRecordReceived?.Invoke(sender, (RequestResponseDataEventArgs)e);
}
}
#endregion
#region Commands
///
/// QueryCaps can send before login
/// arrange record for communication (e.g. Baud rate)
///
///
/// Running outgoing command
///
public RequestRecord QueryCaps()
{
_logger.Debug($"Slot:{Slot} - Request capabilities");
return RequestProtocol.CommandToMeter(Commands.QueryCaps);
}
///
/// The backup login level is needed for re-authorization
///
private Int32 _backupLoginLvl;
///
/// SetRegister to loginLvl and wait for response
///
///
/// true = process command; false = add command to list. call
/// to process login command
/// if is completed and no error occurred it is true
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;
}
///
/// Set
///
/// if null , use password from initialization
/// true = process command; false = add command to list. call
/// to process login command
///
///
private RequestRecord LoginCommandToMeter(String password, Boolean runImmediately = true)
{
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;
}
private String GetPassword()
{
var url = ServiceUrls.GenesisGetPasswordServiceUrl();
String password;
//without PCB ID it is not possible to login
var firstPcbId = GetPcbId();
if (string.IsNullOrEmpty(firstPcbId) || firstPcbId.Length < 8)
{
if (firstPcbId != null && firstPcbId.Length < 8)
{
Thread.Sleep(250);
}
if (string.IsNullOrEmpty(GetPcbId()))
{
return "";
}
}
url += PcbId;
try
{
//use
var http = (HttpWebRequest)WebRequest.Create(url);
var response = (HttpWebResponse)http.GetResponse();
var responseStream = response.GetResponseStream();
if (responseStream == null)
{
throw new InvalidOperationException();
}
using (var sr = new StreamReader(responseStream))
{
var responseJson = sr.ReadToEnd();
password = responseJson.Trim('"');
}
_logger.Info($"Slot:{Slot} - Got password from GenesisPasswordServiceUrl, URL({url})");
}
catch (Exception ex)
{
_logger.Info($"Slot:{Slot} - Cannot get password from GenesisPasswordService, " +
$"URL({url}), Error({ex.Message})");
var listOfOfflinePasswords = JsonConvert.DeserializeObject>(File.ReadAllText(ProgramConfig.OfflineInfoFile));
password = listOfOfflinePasswords.First(x => x.PcbId == PcbId).Password;
if (!string.IsNullOrEmpty(password))
{
_logger.Info($"Slot:{Slot} - Got password from password file!");
}
}
return password;
}
///
///
/// Get password from server
///
public Boolean Login()
{
var password = GetPassword();
return !string.IsNullOrEmpty(password) && Login(password);
}
///
/// Get password from server
///
public Boolean QuickLogin()
{
var password = GetPassword();
return !string.IsNullOrEmpty(password) && Login(password, true, true);
}
///
/// Login with identical password as last time
///
///
///
/// - Initial
///
///
/// - Immediately returns if already logged in.
///
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;
}
}
///
///
/// Starting a timer to keep session active,
/// starting the .
///
/// password string to login
/// true = process command; false = add command to list. call
/// to process login command
///
///
///
/// pass runImmediately for auto login
///
///
/// - Avoid retries during login, a retry will lock the Genesis for 2s, 4s, 8s, 16s and so on.
///
public Boolean Login(String password, Boolean runImmediately = true, Boolean quickLogin = 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 (quickLogin)
{
WriteRegister(Register.Mertrologyasst.PulseMode, 0, true, true);
WriteRegister(Register.Sensusradio.WakeupInterval, 6, true, true);
WriteRegister("SENSUSRADIO_SystemState", 1, true, true);
}
if (Configuration != null && !Configuration.ProductionMode)
{
IsDevelopmentUsage = true;
}
ReadMeterFirmwareAndAssignRegisters();
}
else
{
_logger.Warn($"Slot:{Slot} - Login to meter failed");
}
CommunicationConfig.RequestRetries = backupRetries;
return IsLoggedOn;
}
///
/// 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!
///
/// most significant byte
/// last significant byte
/// converted "msb.lsb" as string e.g. "12.34" or "2.0C"
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}";
}
///
/// List of all present meter applications
///
public List MeterAppListVersion = new List();
///
/// Core revision of boot code.
///
public String CoreRevision;
///
/// This is the FLEXNET version which describes the entire packet.
///
public String FwVersion;
///
/// This is the Metrology Lookup Table CRC.
///
public String LutCrc;
///
/// This is the meter size.
///
public String MeterSize;
///
/// Region (EMEA or NA) and Radio frequency (433 or 868 or null).
///
public String RegionRadio;
///
/// Metrology upgrade permission.
///
public Byte MetrologyUpgradePermission;
///
/// 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.
///
///
/// - Removed "Cordonel " from CoreRevision to have the e.g. "1.64" remaining
///
///
/// - Read radio frequency
///
///
/// - Read lockup table CRC,
/// - Read meter size.
///
///
/// - Read lockup table CRC bugfix with try, catch for legacy versions, where this register does not exists.
///
private void ReadMeterFirmwareAndAssignRegisters()
{
//avoid overwriting of list if this already exits
if (MeterAppListVersion.Any())
{
_logger.Warn("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 = "";
var recount = 4;
while (recount > 0)
{
recount -= 1;
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(ReadRegister(Register.System.CheckFwCrc));
}
break;
}
}
}
}
//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,
});
}
//read metrology upgrade permission
MetrologyUpgradePermission = RegisterConverter.ConvertTo(
ReadRegister(Register.System.MetrologyUpgradePermission));
//read metrology lookup table CRC
try
{
var lutCrcRaw = ReadRegister(Register.Genesisflow.LookupFileCrc);
if (lutCrcRaw != null)
{
var lutCrc = RegisterConverter.ConvertTo(lutCrcRaw);
LutCrc = $@"0x{lutCrc:X4}";
}
}
catch (Exception)
{
// ignored
}
//read meter size
MeterSize = MeterSizeConverter.ConvertMeterSizeEnumToSizeName((MeterSize)RegisterConverter.ConvertTo(
ReadRegister(Register.Genesisflow.MeterSize)));
//read core revision
var coreRegisterRead = RegisterConverter.ConvertTo(ReadRegister(Register.System.CoreRevision, 16));
var coreRevision = coreRegisterRead?.Split('\0');
CoreRevision = coreRevision == null ? "0.00" : $"{coreRevision[0].ToLower().Replace("cordonel ", "")}";
//read radio frequency
RegionRadio = "NA";
try
{
var radioFrequencyRaw = ReadRegister(Register.Sensusradio.FrequencyIndicator);
if (radioFrequencyRaw != null)
{
var radioFrequency = RegisterConverter.ConvertTo(radioFrequencyRaw);
RegionRadio = $"EMEA_{radioFrequency}";
}
}
catch (Exception)
{
// ignored
}
//log application information to file
_logger.Info($"PCB ID: {PcbId}");
_logger.Info($"Date time (UTC): {DateTimeOffset.UtcNow}");
if (CoreRevision != null)
{
_logger.Info("System Core Revision: " + CoreRevision);
}
_logger.Info("Region and radio frequency[Mhz]: " + RegionRadio);
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($"AppId: 0x{fm.AppId:X2} - {versionString} - AppName: {fm.AppName}");
}
_logger.Info($"Meter size: {MeterSize}");
//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.Any(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, $"Not able to set minimum list of registers. check json file for {checkMinRegister} ");
}
}
}
if (IsDevelopmentUsage)
{
var registerToPotentialAdd = new List();
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);
}
//
}
}
}
///
/// Automatic login to meter enabled if logged out by lost authentication
///
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)
{
}
}
///
/// If the timeout timer elapsed to keep the session active, this routine will be called
/// awaking the .
///
///
///
private void KeepSessionTimerElapsed_SyncKeepSessionThread(Object sender, ElapsedEventArgs eventARgs)
{
if (_autoLogon)
{
//put KeepSessionThread state from WaitSleepJoin to Running
_onSyncKeepSessionThread.Set();
}
}
///
/// 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 and aborted on
///
///
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)
{
}
}
///
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;
}
///
/// Check if EMPTY_PIPE or REBOOT is set
///
///
public Boolean CheckStateAfterMeasurement()
{
return HasAlarms(Alarm.EMPTY_PIPE | Alarm.REBOOT);
}
///
/// Check if alarmToCheck is set on register
///
/// is Flags so you can use more than one alarms (like Alarm.EMPTY_PIPE | Alarm.REBOOT)
///
public Boolean HasAlarms(Alarm alarmToCheck)
{
var currentAlarms = GetAlarms();
return currentAlarms.HasFlag(alarmToCheck);
}
///
/// Read out all AlarmStatus register and combine them to one
///
///
public Alarm GetAlarms()
{
List alarms = new List()
{
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 responseAllAlarms = new List();
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()
}
///
/// Reset the empty pipe alarm
///
public void ResetAlarm(Alarm clear)
{
WriteRegister(Register.Customer.TriggerAlarmCancel, clear.GetHashCode());
}
///
/// Alarm messages
///
[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
}
///
/// Reads the PCB Identification, can be accessed at all login levels,
/// Read Privilege is always needed to BUGFIX read access to PCB ID
///
/// PCB ID
public String GetPcbId(Int32 expectedLength = 12, Int32 retriesLeft = 2)
{
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} - Get PCB identification old{PcbId}");
var listPcb = new List();
var hasZero = false;
var maxReads = 4;
while (!hasZero)
{
var chunk = ReadRegister(Register.Configexchange.PcbSerialNumber, 4);
maxReads -= 1;
if (chunk == null)
{
break;
}
listPcb.AddRange(chunk);
if (listPcb.Contains(0))
{
hasZero = true;
}
if (maxReads == 0)
{
break;
}
}
_logger.Info("First read ");
if (listPcb.Any())
{
var response = listPcb.ToArray();
PcbId = Encoding.ASCII.GetString(response).Split('\0')[0];
if (PcbId.Length < 8 && retriesLeft >= 1)
{
return GetPcbId(expectedLength, retriesLeft - 1);
}
return PcbId;
}
if (retriesLeft >= 1)
{
return GetPcbId(expectedLength, retriesLeft - 1);
}
return string.Empty;
}
///
/// Get the actual register dictionary
///
///
public ConcurrentDictionary GetRegistersDic()
{
if (_configRegister?.MeterRegisterDic == null || !_configRegister.MeterRegisterDic.Any())
{
return new ConcurrentDictionary();
}
return _configRegister.MeterRegisterDic;
}
///
/// Serial number of meter married with PcbId
///
///
public Int32 GetSerialNumber()
{
return RelatePcb.GetSerialNumber(ServiceUrls.MarriageServiceUrl(), GetPcbId());
}
///
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
///
/// Event to Sync Register on MeterSide and
/// Fired on Write or read register
///
///
/// Register to update with value to update
private void RequestProtocol_MeterRegisterUpdated(Object sender, RegisterUpdatedEventArgs e)
{
_configRegister.Set(e.Register, e.Value);
}
///
/// Write Register, optional: wait for result and validate,
/// write register will ALWAYS log the data DON'T use for password write
///
/// Data type of Register
/// Register to change
/// Value to save
/// wait until result is ready
/// check the content of the register by read back
/// skip retries on this error code return
/// true on successful operation
public Boolean WriteRegister(String reg, T value, Boolean waitForResult = true,
Boolean checkRegister = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
var regDef = _configRegister.GetRegisterDefinitionByName(reg);
var data = RegisterConverter.ConvertFrom(value);
_logger.Info($"Slot:{Slot} - Write register({regDef.RegisterName}), " +
$"record({BitConverter.ToString(data.ToArray())})");
PreRegisterWrite(regDef, data);
var command = data.Length > RegisterDefinition.ChunkSize ?
Commands.MultipleWriteData : Commands.WriteData;
var responseRecord = RequestProtocol.CommandToMeter(command, regDef, data,
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;
}
///
/// Read Register and return byte array
///
///
/// expected length for response
/// skip retries on this error code return
///
public Byte[] ReadRegister(String reg, Int32? expectedLength = null,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
var regDef = _configRegister.GetRegisterDefinitionByName(reg);
_configRegister.Set(regDef, null);
_logger.Info($"Slot:{Slot} - Read register({regDef.RegisterName})");
if (!expectedLength.HasValue || expectedLength.Value <= RegisterDefinition.ChunkSize)
{
RequestProtocol?.CommandToMeter(Commands.ReadData, regDef,
skipRetryErrorCode: skipRetryErrorCode);
}
else
{
RequestProtocol?.CommandToMeter(Commands.MultipleReadData, regDef, expectedLength: expectedLength,
skipRetryErrorCode: skipRetryErrorCode);
}
RequestProtocolProcess();
var result = _configRegister.Get(reg);
PostRegisterRead(regDef, result);
return result;
}
///
/// - Removed throw because the throw always kicks in to
/// the FW-Update process causing an unpredictable stop!
///
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");
// Logout();
// ReLogin();
//removed because this stops all processing which is critical on FW-Update!
//if (!_autoLogon || !RequestProtocol.ContainsRegisterIdent("password"))
//{
// throw new AuthenticationException("Can not access meter. " +
// "Reason AuthorizationRequired");
//}
if (!string.IsNullOrEmpty(_password))
{
IsLoggedOn = false;
if (Login(_password, false))
{
}
// //removed because this stops all processing which is critical on FW-Update!
// //throw new AuthenticationException("Login failed");
//}
}
}
break;
case RequestAcknowledgeState.Ok:
break;
//try out this
case RequestAcknowledgeState.CommandError:
case RequestAcknowledgeState.MeterError:
Thread.Sleep(200);
break;
}
}
///
/// Send UI1236 command
///
///
/// - Initial
///
// 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)
{
_logger.Info($"Slot:{Slot} - Sending Ui-1236 frame: {ui1236Frame} ");
var sendFifoUi1236 = RequestProtocol.AddRecordToSendFifoUI1236(ui1236Frame);
RequestProtocolProcess();
return sendFifoUi1236.ResponsePayload.ToArray();
}
#endregion
#region Class handling
///
/// Clear Ports
///
public void Clear()
{
StreamingPort?.Clear();
RequestPort?.Clear();
}
///
public void DisposeMeter()
{
Dispose();
}
///
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
///
public void WriteLog(String text)
{
_logger.Info($"Slot:{Slot} - {text}");
//if (_enableRawDataLogging)
//{
// if (_loggerRawData == null)
// {
// _loggerRawData = NLogHelper.CreateOrGetMultiLogger(
// StreamingPort.GetPortName(), "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
// }
// _loggerRawData.Info($"{text}");
//}
}
///
public void LogRawData(Boolean on)
{
_enableRawDataLogging = on;
if (StreamingPort is LedSerialPort port)
{
port.RecordStreamingRawData = _enableRawDataLogging;
}
}
///
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());
}
///
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);
// }
// }
//}
}
///
/// Executes all StoreConfiguration and StoreCalibration for each application.
///
/// true if all configurations are stored
///
/// - Initial
///
///
/// - Corrected logic to enter write and read loop,
/// - Removed SENSUSRADIO from read check as it returns NULL on read of StoreConfiguration.
///
///
/// - After write store configuration extended sleep.
///
///
/// - Removed comparison of allConfigRegisters less than 8 because the NA version has less registers and new
/// apps will have a new register.
///
///
/// - Removed temporary NA2ALARMS readback as this has no handler in the FW.
///
public Boolean StoreAllConfigurations()
{
var allConfigRegisters = _configRegister.MeterRegisterDic.Count(a =>
a.Key.RegisterName.ToLower() == "storeconfiguration" ||
a.Key.RegisterName.ToLower() == "storecalibration");
try
{
foreach (var a in _configRegister.MeterRegisterDic.Where(a =>
a.Key.RegisterName.ToLower() == "storeconfiguration" ||
a.Key.RegisterName.ToLower() == "storecalibration"))
{
var done = false;
//retries for each register access
var retryLeft = 3;
while (retryLeft > 0 && !done)
{
Thread.Sleep(50);
var result = WriteRegister(a.Key.GetIdent(), 1);
if (!result)
{
retryLeft -= 1;
if (retryLeft == 0)
{
return false;
}
continue;
}
// Wait for storing
Thread.Sleep(400);
//TODO enable this for R1.2.x
if (a.Key.AppName == "SENSUSRADIO" || a.Key.AppName == "NA2WALARMS")
{
done = true;
allConfigRegisters -= 1;
continue;
}
var resultRead = ReadRegister(a.Key.GetIdent());
if (resultRead == null || resultRead.ToList().Any(ra => ra != 0x00))
{
retryLeft -= 1;
if (retryLeft == 0)
{
return false;
}
continue;
}
done = true;
allConfigRegisters -= 1;
}
}
}
catch (Exception)
{
// ignored
}
return allConfigRegisters == 0;
}
///
/// Upload app list to data base
///
///
///
public void PushProgress(String progressName, String version)
{
var apps = new List();
foreach (var item in MeterAppListVersion)
{
apps.Add(new CordonelAppVersion(item.AppId, item.StrVersion, item.AppId != 15));
}
apps.Add(new CordonelAppVersion(-1, CoreRevision, false));
var url = $"{ServiceUrls.MeterProcessStateUrl()}SetCordonelProgress?PcbId={PcbId}&ProgressName={progressName}&version={version}";
LocalWebRequest.PostRequestAsync(url, json: apps);
}
///
/// Backup current register dictionary to data base
///
///
public void StoreCurrentRegisterDic()
{
try
{
var url = ServiceUrls.RegisterWatchServiceUrl();
if (!string.IsNullOrEmpty(Configuration?.RegisterWatchServiceUrl))
{
url = Configuration.RegisterWatchServiceUrl;
}
var d = new List();
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(ex, "Can't store at RegisterWatchService");
}
}
///
/// Logging of register write processes to meter
/// ATTENTION: DON'T USE FOR PASSWORDS!
///
///
///
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($"HTTP status not OK"),
"Can't store at RegisterWatchService");
}
}
catch (Exception ex)
{
_logger.Error(ex, "Can't store at RegisterWatchService");
}
}
}
///
/// Storage of calibration values
///
///
///
///
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($"HTTP status not OK({response.StatusCode})"), "can not store at RegisterWatchService");
}
}
catch (Exception ex)
{
_logger.Error(ex, "can not store at RegisterWatchService");
}
}
}
#endregion
///
/// Set the process state
///
///
public void SetProcessState(DisplayCodes newState)
{
SetProcessState(newState, true);
}
}
}