tbf/GenesisCordonelInterface/API/InterfaceGCIToLaatzen.cs

1289 lines
42 KiB
C#
Raw Normal View History

using GenesisCordonelInterface.Core.Threading;
using NLog;
using System;
2026-04-23 07:09:27 +00:00
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
2026-04-23 07:09:27 +00:00
using System.Linq;
using System.Reflection;
2026-04-23 07:09:27 +00:00
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
2026-04-23 07:09:27 +00:00
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
2026-04-23 07:09:27 +00:00
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;
2026-04-23 07:09:27 +00:00
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
using static Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Register;
2026-04-23 07:09:27 +00:00
namespace GenesisCordonelInterface.API
{
public class InterfaceGCIToLaatzen
{
2026-04-27 05:57:16 +00:00
#region Fields
//private static readonly Lazy<ILogger> Logger = new Lazy<ILogger>(() => LogManager.GetLogger("GCI"));
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface");
private readonly MeterBatch _meterBatch = new MeterBatch();
private readonly ConcurrentDictionary<int, ApiWorker> _workers = new ConcurrentDictionary<int, ApiWorker>();
2026-04-27 05:57:16 +00:00
private readonly ConcurrentDictionary<int, bool> _selectedSlots = new ConcurrentDictionary<int, bool>();
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
#endregion
#region ================================== Worker ==================================
2026-04-27 05:57:16 +00:00
private ApiWorker GetWorker(int slot)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
if (slot <= 0)
throw new ArgumentOutOfRangeException(nameof(slot));
return _workers.GetOrAdd(slot, s => new ApiWorker($"GCI Worker Slot {s}"));
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
#endregion
#region ================================== Worker Debug ==================================
2026-04-23 07:09:27 +00:00
public List<PublicModels.WorkerDebugStatus> GetWorkerDebugStatuses()
2026-04-27 05:57:16 +00:00
{
return _workers
.OrderBy(x => x.Key)
.Select(x => new PublicModels.WorkerDebugStatus
2026-04-27 05:57:16 +00:00
{
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,
2026-04-27 05:57:16 +00:00
LastError = x.Value.LastError,
LastActivity = x.Value.LastActivity
})
.ToList();
}
#endregion
2026-04-23 07:09:27 +00:00
#region ================================== MeterBatch Debug ==================================
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
2026-04-23 07:09:27 +00:00
{
return _meterBatch.ListOfMeters
2026-04-27 05:57:16 +00:00
.OfType<GenesisMeter>()
.Select(m => new MeterBatchDebugStatus
{
Slot = m.Slot,
Selected = IsSlotSelected(m.Slot),
PcbId = m.PcbId,
IsConnected = m.IsConnected,
2026-04-27 05:57:16 +00:00
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,
2026-04-27 05:57:16 +00:00
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<int> GetSelectedSlots()
{
return _selectedSlots
.Where(x => x.Value)
.Select(x => x.Key)
.OrderBy(x => x)
.ToList();
2026-04-23 07:09:27 +00:00
}
#endregion
#region ================================== Helpers ==================================
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
private GenesisMeter GetMeter(int slot)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
var meter = _meterBatch.ListOfMeters
.OfType<GenesisMeter>()
.FirstOrDefault(m => m.Slot == slot);
2026-04-23 07:09:27 +00:00
//if (meter == null)
// throw new InvalidOperationException($"Meter for slot {slot} not initialized.");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
return meter;
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
private void EnsureConnected(GenesisMeter meter)
{
if (!meter.IsLoggedOn)
throw new InvalidOperationException("Meter is not connected.");
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
private string ToHex(byte[] data)
{
return data == null ? "" : BitConverter.ToString(data).Replace("-", " ");
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
private const string InterfaceName = "InterfaceGCIToLaatzen";
private void LogInfo(string operation, string message)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
Logger.Info("[{0}] {1}: {2}", InterfaceName, operation, message);
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
private void LogError(string operation, Exception ex)
{
Logger.Error(ex, "[{0}] {1} failed: {2}", InterfaceName, operation, ex.Message);
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
private string SafePort(string port)
{
return string.IsNullOrWhiteSpace(port) ? "<empty>" : port;
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
#endregion
2026-04-23 07:09:27 +00:00
#region ================================== INIT ==================================
2026-04-23 07:09:27 +00:00
public Task<PublicModels.GciInitSlotResult> 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 : "<empty>",
str.HasValue ? str.Value.PortName : "<empty>"));
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<PublicModels.GciInitSlotResult> 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 : "<empty>",
str.HasValue ? str.Value.PortName : "<empty>"));
var meter = GetMeter(slot);
if (meter == null)
{
return SlotFailed(slot, "Meter does not exist for this slot.");
}
meter.Dispose();
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.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.SetupGenesisMeter(slot, req, str, true);
//meter.ConnectMeter();
}
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<PublicModels.GciSlotInfo> 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<PublicModels.GciAllSlotsInfo> 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<GenesisMeter>()
.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<PublicModels.GciSlotInfo>()
};
}
}
public Task<PublicModels.GciCleanSlotResult> CleanSlotAsync(
int slot,
CancellationToken token = default)
{
return Task.Run(() => CleanSlot(slot), token);
}
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<PublicModels.GciCleanAllSlotsResult> 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
2026-04-23 07:09:27 +00:00
};
}
}
2026-04-27 05:57:16 +00:00
2026-04-23 07:09:27 +00:00
#endregion
#region ================================== PORT DETECTION ==================================
2026-04-27 05:57:16 +00:00
public Task<PortDetectionResult> DetectStreamingPortAsync(
int slot,
CancellationToken token = default(CancellationToken))
{
return GetWorker(slot).RunAsync(() => DetectStreamingPort(slot), token);
}
public PortDetectionResult DetectStreamingPort(int slot)
{
const string operation = nameof(DetectStreamingPort);
2026-04-23 07:09:27 +00:00
if (slot <= 0)
throw new ArgumentOutOfRangeException(nameof(slot), "Slot number must be greater than zero.");
2026-04-27 05:57:16 +00:00
try
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Start. Slot={slot}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
using (var mb = new MeterBatch())
using (var meter = new GenesisMeter())
{
mb.AddMeter(meter);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
var rawData = new ConcurrentBag<string>();
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
meter.StreamingPort.OnRawRecordReceived += (o, rawMsg) =>
{
var data = (string)rawMsg.GetData();
rawData.Add(data);
};
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
Thread.Sleep(500);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
bool success = rawData.Any();
string portName = meter.StreamingPort.GetPortName();
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
LogInfo(operation,
$"Finish. Slot={slot}, Success={success}, Port={SafePort(portName)}, RawRecords={rawData.Count}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
return new PortDetectionResult
{
Success = success,
Slot = slot,
PortName = portName,
ErrorMessage = success ? null : "No streaming data received."
};
}
2026-04-23 07:09:27 +00:00
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
throw;
2026-04-23 07:09:27 +00:00
}
}
2026-04-27 05:57:16 +00:00
public Task<PortDetectionResult> DetectRequestPortAsync(
int slot,
CancellationToken token = default(CancellationToken))
{
return GetWorker(slot).RunAsync(() => DetectRequestPort(slot), token);
}
public PortDetectionResult DetectRequestPort(int slot)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
const string operation = nameof(DetectRequestPort);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
if (slot <= 0)
throw new ArgumentOutOfRangeException(nameof(slot), "Slot number must be greater than zero.");
2026-04-23 07:09:27 +00:00
try
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Start. Slot={slot}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
using (var mb = new MeterBatch())
using (var meter = new GenesisMeter())
{
mb.AddMeter(meter);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
meter.Logout();
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
string pcbId = meter.GetPcbId();
bool success = !string.IsNullOrEmpty(pcbId);
string portName = meter.RequestPort.GetPortName();
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
LogInfo(operation,
$"Finish. Slot={slot}, Success={success}, Port={SafePort(portName)}, PcbId={pcbId ?? "<empty>"}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
return new PortDetectionResult
{
Success = success,
Slot = slot,
PortName = portName,
PcbId = pcbId,
ErrorMessage = success ? null : "PCB ID was empty."
};
}
2026-04-23 07:09:27 +00:00
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
throw;
2026-04-23 07:09:27 +00:00
}
}
2026-04-27 05:57:16 +00:00
#endregion
2026-04-23 07:09:27 +00:00
#region ================================== LOGIN ==================================
public Task<PublicModels.GciLoginResult> 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.Login2();
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 ==================================
2026-04-23 07:09:27 +00:00
public Task<PublicModels.GciConnectResult> ConnectOneSlotAsync(int slot, CancellationToken token = default)
2026-04-23 07:09:27 +00:00
{
return GetWorker(slot).RunAsync(() => ConnectOneSlot(slot), token);
2026-04-23 07:09:27 +00:00
}
public PublicModels.GciConnectResult ConnectOneSlot(int slot)
2026-04-23 07:09:27 +00:00
{
const string operation = nameof(ConnectOneSlot);
2026-04-27 05:57:16 +00:00
2026-04-23 07:09:27 +00:00
try
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Start. Slot={slot}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
var meter = GetMeter(slot);
2026-04-23 07:09:27 +00:00
if (!meter.IsConnected)
{
meter.ConnectMeter2();
2026-04-23 07:09:27 +00:00
LogInfo(operation, $"New Slot {slot} connection success.");
return new PublicModels.GciConnectResult
{
Success = true,
SlotId = slot,
IsConnected = meter.IsConnected,
Message = meter.IsConnected
? "Connected now"
: meter.LastConnectError
2026-04-23 07:09:27 +00:00
};
}
var result = new PublicModels.GciConnectResult
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
Success = true,
SlotId = slot,
IsConnected = true,
Message = "Already connected"
2026-04-23 07:09:27 +00:00
};
LogInfo(operation, $"Slot={slot} already connected.");
2026-04-27 05:57:16 +00:00
2026-04-23 07:09:27 +00:00
return result;
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
return new PublicModels.GciConnectResult
2026-04-23 07:09:27 +00:00
{
Success = false,
SlotId = slot,
IsConnected = false,
Message = ex.Message
2026-04-23 07:09:27 +00:00
};
}
}
private List<PublicModels.GciRegisterSnapshot> BuildRegisters(GenesisMeter meter)
2026-04-23 07:09:27 +00:00
{
var list = new List<GciRegisterSnapshot>();
2026-04-27 05:57:16 +00:00
foreach (var item in meter.GetRegistersDic())
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
var from = item.Key.RegisterDetail.Version.First?.ToString() ?? "-";
var to = item.Key.RegisterDetail.Version.Last?.ToString() ?? "-";
2026-04-23 07:09:27 +00:00
list.Add(new GciRegisterSnapshot
2026-04-27 05:57:16 +00:00
{
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()
});
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
return list;
}
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
#endregion
2026-04-23 07:09:27 +00:00
#region ================================== DISCONNECT ==================================
public Task<PublicModels.GciDisconnectResult> 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);
if (meter.IsLoggedOn)
meter.Logout();
meter.DisposeMeter();
//meter.SoftDispose();
LogInfo(operation, $"Success. Slot={slot}");
return new PublicModels.GciDisconnectResult
{
SlotId = slot,
Success = true,
Message = "Disconnected successfully."
};
}
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 ==================================
2026-04-23 07:09:27 +00:00
public Task<PublicModels.GciGetPcbIdResult> GetPcbIdAsync(
int slot,
CancellationToken token = default(CancellationToken))
2026-04-27 05:57:16 +00:00
{
return GetWorker(slot).RunAsync(() => GetPcbId(slot), token, "GetPcbId");
}
public PublicModels.GciGetPcbIdResult GetPcbId(int slot)
2026-04-27 05:57:16 +00:00
{
const string operation = nameof(GetPcbId);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
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 ?? "<empty>"}");
return new PublicModels.GciGetPcbIdResult
{
SlotId = slot,
Success = isValid,
PcbId = pcbId,
Message = isValid
? "PCB ID read successfully."
: "Invalid length"
};
2026-04-23 07:09:27 +00:00
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
return new PublicModels.GciGetPcbIdResult
{
SlotId = slot,
Success = false,
PcbId = null,
Message = ex.Message
};
2026-04-27 05:57:16 +00:00
}
2026-04-23 07:09:27 +00:00
}
#endregion
#region ================================== REGISTER READ ==================================
2026-04-27 05:57:16 +00:00
public Task<RegisterReadResult> ReadRegisterAsync(int slot, string name, CancellationToken token = default)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
return GetWorker(slot).RunAsync(() => ReadRegister(slot, name), token);
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
public RegisterReadResult ReadRegister(int slot, string name)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
const string operation = nameof(ReadRegister);
2026-04-23 07:09:27 +00:00
try
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Start. Slot={slot}, Register={name}");
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
var meter = GetMeter(slot);
EnsureConnected(meter);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
var raw = meter.ReadRegister(name);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
var result = new RegisterReadResult
2026-04-23 07:09:27 +00:00
{
Success = true,
2026-04-27 05:57:16 +00:00
RegisterName = name,
RawHex = ToHex(raw)
2026-04-23 07:09:27 +00:00
};
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Success. Slot={slot}, Register={name}, RawHex={result.RawHex}");
return result;
2026-04-23 07:09:27 +00:00
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
2026-04-23 07:09:27 +00:00
return new RegisterReadResult
{
Success = false,
2026-04-27 05:57:16 +00:00
RegisterName = name,
2026-04-23 07:09:27 +00:00
ErrorMessage = ex.Message
};
}
}
2026-04-27 05:57:16 +00:00
#endregion
2026-04-23 07:09:27 +00:00
#region ================================== REGISTER WRITE ==================================
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
public Task<RegisterWriteResult> WriteRegisterAsync(
int slot,
string registerName,
object value,
bool storeToDevice = false,
bool refreshSystemState = false,
CancellationToken token = default(CancellationToken))
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
return GetWorker(slot).RunAsync(
() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState),
token,
"WriteRegister");
2026-04-23 07:09:27 +00:00
}
public RegisterWriteResult WriteRegister(
2026-04-27 05:57:16 +00:00
int slot,
2026-04-23 07:09:27 +00:00
string registerName,
object value,
bool storeToDevice = false,
bool refreshSystemState = false)
{
2026-04-27 05:57:16 +00:00
const string operation = nameof(WriteRegister);
2026-04-23 07:09:27 +00:00
try
{
2026-04-27 05:57:16 +00:00
LogInfo(operation,
$"Start. Slot={slot}, Register={registerName}, Value={value}, " +
$"StoreToDevice={storeToDevice}, RefreshSystemState={refreshSystemState}");
var meter = GetMeter(slot);
EnsureConnected(meter);
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
if (string.IsNullOrWhiteSpace(registerName))
throw new ArgumentException("Register name cannot be empty.", nameof(registerName));
bool writeOk = meter.WriteRegister(registerName, value);
2026-04-23 07:09:27 +00:00
if (!writeOk)
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=Write operation failed.");
2026-04-23 07:09:27 +00:00
return new RegisterWriteResult
{
Success = false,
RegisterName = registerName,
WrittenValue = value,
ErrorMessage = "Write operation failed."
};
}
if (storeToDevice)
{
2026-04-27 05:57:16 +00:00
if (!meter.StoreAllConfigurations())
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=StoreAllConfigurations failed.");
2026-04-23 07:09:27 +00:00
return new RegisterWriteResult
{
Success = false,
RegisterName = registerName,
WrittenValue = value,
2026-04-27 05:57:16 +00:00
StoreToDevice = true,
RefreshSystemState = refreshSystemState,
2026-04-23 07:09:27 +00:00
ErrorMessage = "StoreAllConfigurations failed."
};
}
}
if (refreshSystemState)
{
2026-04-27 05:57:16 +00:00
if (!meter.WriteRegister("SENSUSRADIO_SYSTEMSTATE", 0xFF, true, false))
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Failed. Slot={slot}, Register={registerName}, Reason=System state refresh failed.");
2026-04-23 07:09:27 +00:00
return new RegisterWriteResult
{
Success = false,
RegisterName = registerName,
WrittenValue = value,
2026-04-27 05:57:16 +00:00
StoreToDevice = storeToDevice,
RefreshSystemState = true,
2026-04-23 07:09:27 +00:00
ErrorMessage = "System state refresh failed."
};
}
}
2026-04-27 05:57:16 +00:00
LogInfo(operation, $"Success. Slot={slot}, Register={registerName}");
2026-04-23 07:09:27 +00:00
return new RegisterWriteResult
{
Success = true,
RegisterName = registerName,
WrittenValue = value,
StoreToDevice = storeToDevice,
RefreshSystemState = refreshSystemState
};
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
2026-04-23 07:09:27 +00:00
return new RegisterWriteResult
{
Success = false,
RegisterName = registerName,
WrittenValue = value,
2026-04-27 05:57:16 +00:00
StoreToDevice = storeToDevice,
RefreshSystemState = refreshSystemState,
2026-04-23 07:09:27 +00:00
ErrorMessage = ex.Message
};
}
}
2026-04-27 05:57:16 +00:00
#endregion
#region ================================== PASSWORD ==================================
2026-04-23 07:09:27 +00:00
public Task<PublicModels.GciSetPasswordResult> SetMeterPasswordAsync(
int slot,
string password,
CancellationToken token = default)
2026-04-27 05:57:16 +00:00
{
return GetWorker(slot).RunAsync(() => SetMeterPassword(slot, password),
2026-04-27 05:57:16 +00:00
token,
"SetMeterPassword");
2026-04-23 07:09:27 +00:00
}
public PublicModels.GciSetPasswordResult SetMeterPassword(
int slot,
string password)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
const string operation = nameof(SetMeterPassword);
2026-04-23 07:09:27 +00:00
try
{
if (string.IsNullOrWhiteSpace(password))
throw new ArgumentException("Password cannot be empty.", nameof(password));
2026-04-23 07:09:27 +00:00
LogInfo(operation, $"Start. Slot={slot}");
2026-04-23 07:09:27 +00:00
GenesisMeter meter = GetMeter(slot);
2026-04-27 05:57:16 +00:00
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);
2026-04-23 07:09:27 +00:00
return new PublicModels.GciSetPasswordResult
{
SlotId = slot,
Success = false,
Message = ex.Message
};
}
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
#endregion
#region ================================== METER BATCH SETUP ==================================
2026-04-23 07:09:27 +00:00
2026-04-27 05:57:16 +00:00
public void ReloadSlotSetup()
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
const string operation = nameof(ReloadSlotSetup);
2026-04-23 07:09:27 +00:00
try
{
2026-04-27 05:57:16 +00:00
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.");
2026-04-23 07:09:27 +00:00
}
catch (Exception ex)
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
throw;
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
}
public void SaveSlotSetup(List<MeterBatchDebugStatus> 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)
2026-04-23 07:09:27 +00:00
{
2026-04-27 05:57:16 +00:00
LogError(operation, ex);
throw;
2026-04-23 07:09:27 +00:00
}
}
private List<string> _cachedRegisters;
public List<string> 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;
}
2026-04-27 05:57:16 +00:00
#endregion
2026-04-23 07:09:27 +00:00
}
2026-04-27 05:57:16 +00:00
}