1303 lines
43 KiB
C#
1303 lines
43 KiB
C#
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<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>();
|
|
|
|
private readonly ConcurrentDictionary<int, bool> _selectedSlots = new ConcurrentDictionary<int, bool>();
|
|
|
|
#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<PublicModels.WorkerDebugStatus> 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<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
|
|
{
|
|
return _meterBatch.ListOfMeters
|
|
.OfType<GenesisMeter>()
|
|
.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<int> 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<GenesisMeter>()
|
|
.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) ? "<empty>" : port;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== INIT/UPDATE ==================================
|
|
|
|
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 : "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<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 : "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<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);
|
|
}
|
|
#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<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
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PORT DETECTION ==================================
|
|
|
|
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);
|
|
|
|
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<string>();
|
|
|
|
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<PortDetectionResult> 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 ?? "<empty>"}");
|
|
|
|
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<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.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<PublicModels.GciConnectResult> 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<PublicModels.GciRegisterSnapshot> BuildRegisters(GenesisMeter meter)
|
|
{
|
|
var list = new List<GciRegisterSnapshot>();
|
|
|
|
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<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);
|
|
|
|
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<PublicModels.GciGetPcbIdResult> 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 ?? "<empty>"}");
|
|
|
|
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<RegisterReadResult> 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<RegisterWriteResult> 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<PublicModels.GciSetPasswordResult> 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<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)
|
|
{
|
|
LogError(operation, ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |