using GenesisCordonelInterface.Core.Threading; using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters; using static GenesisCordonelInterface.API.PublicModels; using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter; using static Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Register; namespace GenesisCordonelInterface.API { public class InterfaceGCIToLaatzen { #region Fields //private static readonly Lazy Logger = new Lazy(() => LogManager.GetLogger("GCI")); private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface"); private readonly MeterBatch _meterBatch = new MeterBatch(); private readonly ConcurrentDictionary _workers = new ConcurrentDictionary(); private readonly ConcurrentDictionary _selectedSlots = new ConcurrentDictionary(); #endregion #region ================================== Worker ================================== private ApiWorker GetWorker(int slot) { if (slot <= 0) throw new ArgumentOutOfRangeException(nameof(slot)); return _workers.GetOrAdd(slot, s => new ApiWorker($"GCI Worker Slot {s}")); } #endregion #region ================================== Worker Debug ================================== public List GetWorkerDebugStatuses() { return _workers .OrderBy(x => x.Key) .Select(x => new PublicModels.WorkerDebugStatus { Slot = x.Key, Name = x.Value.Name, QueueLength = x.Value.IsDisposed ? 0 : x.Value.QueueLength, IsBusy = !x.Value.IsDisposed && x.Value.IsBusy, CurrentOperation = x.Value.IsDisposed ? null : x.Value.CurrentOperation, LastError = x.Value.LastError, LastActivity = x.Value.LastActivity }) .ToList(); } #endregion #region ================================== MeterBatch Debug ================================== public List GetMeterBatchDebugStatuses() { return _meterBatch.ListOfMeters .OfType() .Select(m => new MeterBatchDebugStatus { Slot = m.Slot, Selected = IsSlotSelected(m.Slot), PcbId = m.PcbId, IsConnected = m.IsConnected, IsLoggedOn = m.IsLoggedOn, RequestPort = m.RequestPort?.GetPortName(), StreamingPort = m.StreamingPort?.GetPortName(), RequestPortType = m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.RfidSerialPort ? "RFID" : m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort ? "UART" : m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort ? "IRDA" : null, FwVersion = m.FwVersion, InterfaceVersion = m.InterfaceInfo?.InterfaceVersion }) .OrderBy(x => x.Slot) .ToList(); } public void SetSlotSelected(int slot, bool selected) { if (slot <= 0) throw new ArgumentOutOfRangeException(nameof(slot)); Logger.Debug( "[{0}] SetSlotSelected: slot={1}, selected={2}", InterfaceName, slot, selected); _selectedSlots[slot] = selected; } public bool IsSlotSelected(int slot) { return _selectedSlots.TryGetValue(slot, out bool selected) && selected; } public List GetSelectedSlots() { return _selectedSlots .Where(x => x.Value) .Select(x => x.Key) .OrderBy(x => x) .ToList(); } #endregion #region ================================== Helpers ================================== private GenesisMeter GetMeter(int slot) { var meter = _meterBatch.ListOfMeters .OfType() .FirstOrDefault(m => m.Slot == slot); //if (meter == null) // throw new InvalidOperationException($"Meter for slot {slot} not initialized."); return meter; } private void EnsureConnected(GenesisMeter meter) { if (!meter.IsLoggedOn) throw new InvalidOperationException("Meter is not connected."); } private string ToHex(byte[] data) { return data == null ? "" : BitConverter.ToString(data).Replace("-", " "); } private const string InterfaceName = "InterfaceGCIToLaatzen"; private void LogInfo(string operation, string message) { Logger.Info("[{0}] {1}: {2}", InterfaceName, operation, message); } private void LogError(string operation, Exception ex) { Logger.Error(ex, "[{0}] {1} failed: {2}", InterfaceName, operation, ex.Message); } private string SafePort(string port) { return string.IsNullOrWhiteSpace(port) ? "" : port; } #endregion #region ================================== INIT/UPDATE ================================== public Task InitSlotAsync( int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => InitSlot(slot, cfg, pwd, req, str), token); } public PublicModels.GciInitSlotResult InitSlot( int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str) { const string operation = nameof(InitSlot); try { LogInfo(operation, string.Format( "Start. Slot={0}, ConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}", slot, cfg, pwd, req.HasValue ? req.Value.PortName : "NA", str.HasValue ? str.Value.PortName : "NA")); var existingMeter = GetMeter(slot); if (existingMeter != null) { return SlotAlreadyExists(slot, existingMeter); } var meter = CreateMeterForSlot(slot, cfg, pwd, req, str); //AddMeter(slot, meter); return SlotCreated(slot, meter); } catch (Exception ex) { LogError(operation, ex); return SlotFailed(slot, ex.Message); } } public Task UpdateSlotAsync( int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => UpdateSlot(slot, cfg, pwd, req, str), token); } public PublicModels.GciInitSlotResult UpdateSlot( int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str) { const string operation = nameof(UpdateSlot); try { LogInfo(operation, string.Format( "Start. Slot={0}, ConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}", slot, cfg, pwd, req.HasValue ? req.Value.PortName : "NA", str.HasValue ? str.Value.PortName : "NA")); var meter = GetMeter(slot); if (meter == null) { return SlotFailed(slot, "Meter does not exist for this slot."); } if (meter.IsConnected) { return SlotFailed(slot, "Slot update is not allowed while meter is connected. Disconnect the meter first."); } UpdateMeterForSlot(meter, slot, cfg, pwd, req, str); return SlotUpdated(slot, meter); } catch (Exception ex) { LogError(operation, ex); return SlotFailed(slot, ex.Message); } } private GenesisMeter CreateMeterForSlot( int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str) { GenesisMeter meter = new GenesisMeter(); meter.Slot = slot; meter.useConfigSource = cfg; meter.usePasswordSource = pwd; meter.requestPortConfig = req; meter.streamingPortConfig = str; //meter.SetupGenesisMeter(slot, req, str, true); _meterBatch.AddMeter2(meter); return meter; } private void UpdateMeterForSlot( GenesisMeter meter, int slot, ConfigSource cfg, PasswordSource pwd, PortConfig? req, PortConfig? str) { if (meter == null) throw new ArgumentNullException(nameof(meter)); meter.Slot = slot; meter.useConfigSource = cfg; meter.usePasswordSource = pwd; meter.requestPortConfig = req; meter.streamingPortConfig = str; //meter.SetupGenesisMeter(slot, req, str, true); } private PublicModels.GciInitSlotResult SlotCreated(int slot, GenesisMeter meter) { return new PublicModels.GciInitSlotResult { SlotId = slot, Success = true, Message = "Meter initialized.", PcbId = meter != null ? meter.SerialNumber : null, Created = true, Updated = false, AlreadyExists = false }; } private PublicModels.GciInitSlotResult SlotUpdated(int slot, GenesisMeter meter) { return new PublicModels.GciInitSlotResult { SlotId = slot, Success = true, Message = "Meter updated.", PcbId = meter != null ? meter.SerialNumber : null, Created = false, Updated = true, AlreadyExists = false }; } private PublicModels.GciInitSlotResult SlotAlreadyExists(int slot, GenesisMeter meter) { return new PublicModels.GciInitSlotResult { SlotId = slot, Success = true, Message = "Meter already exists.", PcbId = meter != null ? meter.SerialNumber : null, Created = false, Updated = false, AlreadyExists = true }; } private PublicModels.GciInitSlotResult SlotFailed(int slot, string message) { return new PublicModels.GciInitSlotResult { SlotId = slot, Success = false, Message = message, PcbId = null, Created = false, Updated = false, AlreadyExists = false }; } #endregion #region ================================== Slot INFO ================================== public Task GetOneMeterInfo( int slot, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => GetOneMeterInfo(slot), token); } public PublicModels.GciSlotInfo GetOneMeterInfo(int slot) { const string operation = nameof(GetOneMeterInfo); try { LogInfo(operation, $"Start. Slot={slot}"); var meter = GetMeter(slot); if (meter == null) { return new PublicModels.GciSlotInfo { SlotId = slot, Success = true, Exists = false, IsConnected = false, IsLoggedOn = false, Message = "Slot is empty." }; } return new PublicModels.GciSlotInfo { SlotId = slot, Success = true, Exists = true, IsConnected = meter.IsConnected, IsLoggedOn = meter.IsLoggedOn, Message = "Slot found.", PcbId = meter.PcbId, Password = meter.Password, ConfigSource = ModelsMapping.MapConfigSourceBack(meter.useConfigSource), PasswordSource = ModelsMapping.MapPasswordSourceBack(meter.usePasswordSource), RequestPort = ModelsMapping.MapPortBack(meter.RequestPort), StreamingPort = ModelsMapping.MapPortBack(meter.StreamingPort) }; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciSlotInfo { SlotId = slot, Success = false, Exists = false, IsConnected = false, IsLoggedOn = false, Message = ex.Message }; } } public Task GetAllMetersInfo( CancellationToken token = default) { return Task.Run(() => GetAllMetersInfo(), token); } public PublicModels.GciAllSlotsInfo GetAllMetersInfo() { const string operation = nameof(GetAllMetersInfo); try { LogInfo(operation, "Start."); var result = new PublicModels.GciAllSlotsInfo { Success = true, Message = "Slots read.", Slots = _meterBatch.ListOfMeters .OfType() .Select(meter => new PublicModels.GciSlotInfo { SlotId = meter.Slot, Success = true, Exists = true, Message = "Slot found.", PcbId = meter.PcbId, ConfigSource = ModelsMapping.MapConfigSourceBack(meter.useConfigSource), PasswordSource = ModelsMapping.MapPasswordSourceBack(meter.usePasswordSource), RequestPort = ModelsMapping.MapPortBack(meter.RequestPort), StreamingPort = ModelsMapping.MapPortBack(meter.StreamingPort) }) .ToList() }; return result; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciAllSlotsInfo { Success = false, Message = ex.Message, Slots = new List() }; } } public Task CleanSlotAsync( int slot, CancellationToken token = default) { return Task.Run(() => CleanSlot(slot), token); } #endregion #region ================================== CLEAN ================================== public PublicModels.GciCleanSlotResult CleanSlot(int slot) { const string operation = nameof(CleanSlot); try { LogInfo(operation, string.Format("Start. Slot={0}", slot)); var meter = GetMeter(slot); if (meter != null) { meter.DisposeMeter(); _meterBatch.ListOfMeters.Remove(meter); } ApiWorker worker; if (_workers.TryRemove(slot, out worker)) { worker.Dispose(); } return new PublicModels.GciCleanSlotResult { SlotId = slot, Success = true, Message = string.Format("Slot {0} cleaned.", slot) }; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciCleanSlotResult { SlotId = slot, Success = false, Message = ex.Message }; } } public Task CleanAllSlotsAsync( CancellationToken token = default) { return Task.Run(() => CleanAllSlots(), token); } public PublicModels.GciCleanAllSlotsResult CleanAllSlots() { const string operation = nameof(CleanAllSlots); try { LogInfo(operation, "Start."); foreach (var meter in _meterBatch.ListOfMeters) { meter.DisposeMeter(); } _meterBatch.ListOfMeters.Clear(); foreach (var pair in _workers.ToList()) { ApiWorker worker; if (_workers.TryRemove(pair.Key, out worker)) { worker.Dispose(); } } return new PublicModels.GciCleanAllSlotsResult { Success = true, Message = "Slots cleaned." }; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciCleanAllSlotsResult { Success = false, Message = ex.Message }; } } #endregion #region ================================== PORT DETECTION ================================== public Task DetectStreamingPortAsync( int slot, CancellationToken token = default(CancellationToken)) { return GetWorker(slot).RunAsync(() => DetectStreamingPort(slot), token); } public PortDetectionResult DetectStreamingPort(int slot) { const string operation = nameof(DetectStreamingPort); if (slot <= 0) throw new ArgumentOutOfRangeException(nameof(slot), "Slot number must be greater than zero."); try { LogInfo(operation, $"Start. Slot={slot}"); using (var mb = new MeterBatch()) using (var meter = new GenesisMeter()) { mb.AddMeter(meter); var rawData = new ConcurrentBag(); meter.StreamingPort.OnRawRecordReceived += (o, rawMsg) => { var data = (string)rawMsg.GetData(); rawData.Add(data); }; Thread.Sleep(500); bool success = rawData.Any(); string portName = meter.StreamingPort.GetPortName(); LogInfo(operation, $"Finish. Slot={slot}, Success={success}, Port={SafePort(portName)}, RawRecords={rawData.Count}"); return new PortDetectionResult { Success = success, Slot = slot, PortName = portName, ErrorMessage = success ? null : "No streaming data received." }; } } catch (Exception ex) { LogError(operation, ex); throw; } } public Task DetectRequestPortAsync( int slot, CancellationToken token = default(CancellationToken)) { return GetWorker(slot).RunAsync(() => DetectRequestPort(slot), token); } public PortDetectionResult DetectRequestPort(int slot) { const string operation = nameof(DetectRequestPort); if (slot <= 0) throw new ArgumentOutOfRangeException(nameof(slot), "Slot number must be greater than zero."); try { LogInfo(operation, $"Start. Slot={slot}"); using (var mb = new MeterBatch()) using (var meter = new GenesisMeter()) { mb.AddMeter(meter); meter.Logout(); string pcbId = meter.GetPcbId(); bool success = !string.IsNullOrEmpty(pcbId); string portName = meter.RequestPort.GetPortName(); LogInfo(operation, $"Finish. Slot={slot}, Success={success}, Port={SafePort(portName)}, PcbId={pcbId ?? ""}"); return new PortDetectionResult { Success = success, Slot = slot, PortName = portName, PcbId = pcbId, ErrorMessage = success ? null : "PCB ID was empty." }; } } catch (Exception ex) { LogError(operation, ex); throw; } } #endregion #region ================================== LOGIN ================================== public Task LoginOneSlotAsync(int slot, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => LoginOneSlot(slot), token); } public PublicModels.GciLoginResult LoginOneSlot(int slot) { const string operation = nameof(LoginOneSlot); try { LogInfo(operation, $"Start. Slot={slot}"); var meter = GetMeter(slot); if (!meter.IsLoggedOn) { meter.Login(); if (meter.IsLoggedOn) { LogInfo(operation, $"New Slot {slot} login success."); return new PublicModels.GciLoginResult { Success = true, SlotId = slot, IsLoggedOn = true, Message = "Logged on now" }; } LogInfo(operation, $"Failed Slot {slot} login."); return new PublicModels.GciLoginResult { Success = false, SlotId = slot, IsLoggedOn = false, Message = string.IsNullOrWhiteSpace(meter.LastLoginError) ? "Login failed" : meter.LastLoginError }; } var result = new PublicModels.GciLoginResult { Success = true, SlotId = slot, IsLoggedOn = true, Message = "Already logged on" }; LogInfo(operation, $"Slot={slot} already logged on."); return result; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciLoginResult { Success = false, SlotId = slot, IsLoggedOn = false, Message = ex.Message }; } } #endregion #region ================================== CONNECT ================================== public Task ConnectOneSlotAsync(int slot, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => ConnectOneSlot(slot), token); } public PublicModels.GciConnectResult ConnectOneSlot(int slot) { const string operation = nameof(ConnectOneSlot); try { LogInfo(operation, $"Start. Slot={slot}"); var meter = GetMeter(slot); if (!meter.IsConnected) { meter.SetupGenesisMeter(slot, meter.requestPortConfig, meter.streamingPortConfig, true); meter.ConnectMeter(); LogInfo(operation, $"New Slot {slot} connection success."); return new PublicModels.GciConnectResult { Success = true, SlotId = slot, IsConnected = meter.IsConnected, Message = meter.IsConnected ? "Connected now" : meter.LastConnectStatus }; } var result = new PublicModels.GciConnectResult { Success = true, SlotId = slot, IsConnected = true, Message = "Already connected" }; LogInfo(operation, $"Slot={slot} already connected."); return result; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciConnectResult { Success = false, SlotId = slot, IsConnected = false, Message = ex.Message }; } } private List BuildRegisters(GenesisMeter meter) { var list = new List(); foreach (var item in meter.GetRegistersDic()) { var from = item.Key.RegisterDetail.Version.First?.ToString() ?? "-"; var to = item.Key.RegisterDetail.Version.Last?.ToString() ?? "-"; list.Add(new GciRegisterSnapshot { Name = item.Key.GetIdent(), Type = item.Key.DataType.Name, RawValue = BitConverter.ToString(item.Value).Replace("-", " "), Min = item.Key.Minimum?.ToString(), Max = item.Key.Maximum?.ToString(), Description = item.Key.RegisterDetail.Description, Version = $"from {from} to {to}", IsAvailable = item.Key.IsAvailable.ToString(), Privilege = item.Key.RegisterDetail.Privilege.Lvl8.ToString() }); } return list; } #endregion #region ================================== DISCONNECT ================================== public Task DisconnectAsync( int slot, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => Disconnect(slot), token); } public PublicModels.GciDisconnectResult Disconnect(int slot) { const string operation = nameof(Disconnect); try { LogInfo(operation, $"Start. Slot={slot}"); var meter = GetMeter(slot); meter.Logout(); meter.Disconnect(); //meter.Dispose(); //meter.SoftDispose(); LogInfo(operation, $"Success. Slot={slot}"); return new PublicModels.GciDisconnectResult { SlotId = slot, Success = true, Message = string.Join( " | ", new[] { meter.LastLogoutStatus, meter.LastDisposeStatus } .Where(x => !string.IsNullOrWhiteSpace(x))) }; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciDisconnectResult { SlotId = slot, Success = false, Message = ex.Message }; } finally { if (_workers.TryRemove(slot, out var worker)) { worker.Dispose(); LogInfo(operation, $"Worker disposed. Slot={slot}"); } } } #endregion #region ================================== PCB ================================== public Task GetPcbIdAsync( int slot, CancellationToken token = default(CancellationToken)) { return GetWorker(slot).RunAsync(() => GetPcbId(slot), token, "GetPcbId"); } public PublicModels.GciGetPcbIdResult GetPcbId(int slot) { const string operation = nameof(GetPcbId); try { LogInfo(operation, $"Start. Slot={slot}"); var meter = GetMeter(slot); meter.Logout(); string pcbId = meter.GetPcbId(); bool isValid = !string.IsNullOrWhiteSpace(pcbId) && pcbId.Length == 9; LogInfo(operation, $"Finish. Slot={slot}, PcbId={pcbId ?? ""}"); return new PublicModels.GciGetPcbIdResult { SlotId = slot, Success = isValid, PcbId = pcbId, Message = isValid ? "PCB ID read successfully." : "Invalid length" }; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciGetPcbIdResult { SlotId = slot, Success = false, PcbId = null, Message = ex.Message }; } } #endregion #region ================================== REGISTER READ ================================== public Task ReadRegisterAsync(int slot, string name, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => ReadRegister(slot, name), token); } public RegisterReadResult ReadRegister(int slot, string name) { const string operation = nameof(ReadRegister); try { LogInfo(operation, $"Start. Slot={slot}, Register={name}"); var meter = GetMeter(slot); EnsureConnected(meter); var raw = meter.ReadRegister(name); var result = new RegisterReadResult { Success = true, RegisterName = name, RawHex = ToHex(raw) }; LogInfo(operation, $"Success. Slot={slot}, Register={name}, RawHex={result.RawHex}"); return result; } catch (Exception ex) { LogError(operation, ex); return new RegisterReadResult { Success = false, RegisterName = name, ErrorMessage = ex.Message }; } } #endregion #region ================================== REGISTER WRITE ================================== public Task WriteRegisterAsync( int slot, string registerName, object value, bool storeToDevice = false, bool refreshSystemState = false, CancellationToken token = default(CancellationToken)) { return GetWorker(slot).RunAsync( () => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState), token, "WriteRegister"); } public RegisterWriteResult WriteRegister( int slot, string registerName, object value, bool storeToDevice = false, bool refreshSystemState = false) { const string operation = nameof(WriteRegister); try { LogInfo(operation, $"Start. Slot={slot}, Register={registerName}, Value={value}, " + $"StoreToDevice={storeToDevice}, RefreshSystemState={refreshSystemState}"); var meter = GetMeter(slot); EnsureConnected(meter); if (string.IsNullOrWhiteSpace(registerName)) throw new ArgumentException("Register name cannot be empty.", nameof(registerName)); bool writeOk = meter.WriteRegister(registerName, value); if (!writeOk) { LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=Write operation failed."); return new RegisterWriteResult { Success = false, RegisterName = registerName, WrittenValue = value, ErrorMessage = "Write operation failed." }; } if (storeToDevice) { if (!meter.StoreAllConfigurations()) { LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=StoreAllConfigurations failed."); return new RegisterWriteResult { Success = false, RegisterName = registerName, WrittenValue = value, StoreToDevice = true, RefreshSystemState = refreshSystemState, ErrorMessage = "StoreAllConfigurations failed." }; } } if (refreshSystemState) { if (!meter.WriteRegister("SENSUSRADIO_SYSTEMSTATE", 0xFF, true, false)) { LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=System state refresh failed."); return new RegisterWriteResult { Success = false, RegisterName = registerName, WrittenValue = value, StoreToDevice = storeToDevice, RefreshSystemState = true, ErrorMessage = "System state refresh failed." }; } } LogInfo(operation, $"Success. Slot={slot}, Register={registerName}"); return new RegisterWriteResult { Success = true, RegisterName = registerName, WrittenValue = value, StoreToDevice = storeToDevice, RefreshSystemState = refreshSystemState }; } catch (Exception ex) { LogError(operation, ex); return new RegisterWriteResult { Success = false, RegisterName = registerName, WrittenValue = value, StoreToDevice = storeToDevice, RefreshSystemState = refreshSystemState, ErrorMessage = ex.Message }; } } #endregion #region ================================== PASSWORD ================================== public Task SetMeterPasswordAsync( int slot, string password, CancellationToken token = default) { return GetWorker(slot).RunAsync(() => SetMeterPassword(slot, password), token, "SetMeterPassword"); } public PublicModels.GciSetPasswordResult SetMeterPassword( int slot, string password) { const string operation = nameof(SetMeterPassword); try { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password cannot be empty.", nameof(password)); LogInfo(operation, $"Start. Slot={slot}"); GenesisMeter meter = GetMeter(slot); meter?.SetPassword(password);//SetupFromExternConfig(); var result = new PublicModels.GciSetPasswordResult { SlotId = slot, Success = true, Password = meter.GetPassword_out(), Message = "Password set successfully." }; LogInfo(operation, $"Finish. Slot={slot}, Success={result.Success}"); return result; } catch (Exception ex) { LogError(operation, ex); return new PublicModels.GciSetPasswordResult { SlotId = slot, Success = false, Message = ex.Message }; } } #endregion #region ================================== METER BATCH SETUP ================================== public void ReloadSlotSetup() { const string operation = nameof(ReloadSlotSetup); try { LogInfo(operation, "Start."); _meterBatch.ListOfMeters.Clear(); _selectedSlots.Clear(); // TODO: // Load meter batch setup from persistent storage. // Example: // _meterBatch.SetupFromConfigFile(); LogInfo(operation, "Success. Meter batch and selected slots cleared."); } catch (Exception ex) { LogError(operation, ex); throw; } } public void SaveSlotSetup(List data) { const string operation = nameof(SaveSlotSetup); try { LogInfo(operation, $"Start. Rows={data?.Count ?? 0}"); if (data == null) throw new ArgumentNullException(nameof(data)); _meterBatch.ListOfMeters.Clear(); _selectedSlots.Clear(); foreach (var item in data) { var meter = new GenesisMeter(); PortConfig? req = string.IsNullOrWhiteSpace(item.RequestPort) ? (PortConfig?)null : new PortConfig { PortName = item.RequestPort, Type = "Serial" }; PortConfig? str = string.IsNullOrWhiteSpace(item.StreamingPort) ? (PortConfig?)null : new PortConfig { PortName = item.StreamingPort, Type = "Serial" }; meter.useConfigSource = ConfigSource.InterfaceInputConfig; meter.SetupFromExternConfig(item.Slot, req, str, true); _meterBatch.AddMeter(meter); _selectedSlots[item.Slot] = item.Selected; LogInfo( operation, $"Saved row. Slot={item.Slot}, Selected={item.Selected}, " + $"RequestPort={SafePort(item.RequestPort)}, StreamingPort={SafePort(item.StreamingPort)}"); } LogInfo(operation, $"Success. MeterBatchCount={_meterBatch.ListOfMeters.Count}, SelectedSlots={_selectedSlots.Count}"); } catch (Exception ex) { LogError(operation, ex); throw; } } private List _cachedRegisters; public List GetAllRegisterNames() { if (_cachedRegisters != null) return _cachedRegisters; _cachedRegisters = typeof(Register) .GetNestedTypes(BindingFlags.Public) .SelectMany(t => t.GetFields(BindingFlags.Public | BindingFlags.Static)) .Where(f => f.FieldType == typeof(string)) .Select(f => f.GetValue(null)?.ToString()) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct() .OrderBy(x => x) .ToList(); return _cachedRegisters; } #endregion } }