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); } } }