1947 lines
64 KiB
C#
1947 lines
64 KiB
C#
using CordonelPreadjustmentUi;
|
|
using CordonelPreadjustmentUi.Processes;
|
|
using CordonelPreadjustmentUi.Processes.Actions;
|
|
using CordonelPreadjustmentUi.Processes.Itinerary;
|
|
using GenesisCordonelInterface.Core.Threading;
|
|
using GenesisCordonelInterface.UI.LaatzenAPI_CordonelPreadjustmentUI;
|
|
using NLog;
|
|
using NLog.Fluent;
|
|
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 System.Xml.Linq;
|
|
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 Xylem.Common.Ui.CordonelPreadjustmentUi;
|
|
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");
|
|
|
|
//GenesisToolBox
|
|
private readonly MeterBatch _meterBatch = new MeterBatch();
|
|
|
|
//Preadjustment
|
|
public PreAdjustmentSettingsContainer _settings = new PreAdjustmentSettingsContainer();
|
|
public List<MeterStateControl> _meterControls = new List<MeterStateControl>();
|
|
public List<MeterStateControl> _tempMeterControls = new List<MeterStateControl>();
|
|
|
|
// Protects all access to _meterBatch.ListOfMeters
|
|
private readonly object _meterBatchLock = new object();
|
|
private static readonly object _setupGenesisMeterLock = new object();
|
|
|
|
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 ==================================
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
|
|
{
|
|
List<GenesisMeter> meters;
|
|
|
|
//just snapshot of list under lock
|
|
lock (_meterBatchLock)
|
|
{
|
|
meters = _meterBatch.ListOfMeters
|
|
.OfType<GenesisMeter>()
|
|
.ToList();
|
|
}
|
|
|
|
return meters
|
|
.Select(m => new MeterBatchDebugStatus
|
|
{
|
|
Slot = m.Slot,
|
|
Selected = IsSlotSelected(m.Slot),
|
|
PcbId = m.PcbId,
|
|
IsConnected = m.IsConnected,
|
|
IsLoggedOn = m.IsLoggedOn,
|
|
|
|
RequestPort = m.requestPortConfig.HasValue
|
|
? m.requestPortConfig.Value.PortName
|
|
: "",
|
|
|
|
StreamingPort = m.streamingPortConfig.HasValue
|
|
? m.streamingPortConfig.Value.PortName
|
|
: "",
|
|
|
|
RequestPortType = m.requestPortConfig.HasValue
|
|
? m.requestPortConfig.Value.Type
|
|
: "",
|
|
|
|
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 GetMeterThreadSafe(int slot)
|
|
{
|
|
lock (_meterBatchLock)
|
|
{
|
|
return _meterBatch.ListOfMeters
|
|
.OfType<GenesisMeter>()
|
|
.FirstOrDefault(m => m.Slot == slot);
|
|
}
|
|
}
|
|
|
|
private bool RemoveMeterThreadSafe(GenesisMeter meter)
|
|
{
|
|
if (meter == null)
|
|
return false;
|
|
|
|
lock (_meterBatchLock)
|
|
{
|
|
return _meterBatch.ListOfMeters.Remove(meter);
|
|
}
|
|
}
|
|
|
|
private void RemoveMetersThreadSafe()
|
|
{
|
|
lock (_meterBatchLock)
|
|
{
|
|
_meterBatch.ListOfMeters.Clear();
|
|
}
|
|
}
|
|
|
|
private void RemoveWorkersThreadSafe()
|
|
{
|
|
foreach (var pair in _workers.ToList())
|
|
{
|
|
ApiWorker worker;
|
|
|
|
if (_workers.TryRemove(pair.Key, out worker))
|
|
{
|
|
worker.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DisposeMetersThreadSafe()
|
|
{
|
|
lock (_meterBatchLock)
|
|
{
|
|
foreach (var meter in _meterBatch.ListOfMeters.OfType<GenesisMeter>())
|
|
{
|
|
meter.DisposeMeter();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public ConcurrentDictionary<RegisterDefinition, Byte[]> GetRegistersDicForSlot(int slot)
|
|
{
|
|
var meter = GetMeterThreadSafe(slot);
|
|
|
|
if (meter == null)
|
|
throw new Exception($"Slot {slot} not initialized.");
|
|
|
|
return meter.GetRegistersDic();
|
|
}
|
|
|
|
#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 = GetMeterThreadSafe(slot);
|
|
|
|
if (existingMeter != null)
|
|
{
|
|
UpdateMeterForSlot(existingMeter, slot, cfg, pwd, req, str);
|
|
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 = GetMeterThreadSafe(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);
|
|
lock (_meterBatchLock)
|
|
{
|
|
_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. Meter config was only updated",
|
|
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 = GetMeterThreadSafe(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.requestPortConfig),
|
|
StreamingPort = ModelsMapping.MapPortBack(meter.streamingPortConfig)
|
|
};
|
|
}
|
|
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.");
|
|
|
|
lock (_meterBatchLock)
|
|
{
|
|
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.requestPortConfig),
|
|
StreamingPort = ModelsMapping.MapPortBack(meter.streamingPortConfig)
|
|
})
|
|
.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 = GetMeterThreadSafe(slot);
|
|
|
|
if (meter != null)
|
|
{
|
|
meter.DisposeMeter();
|
|
RemoveMeterThreadSafe(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.");
|
|
|
|
DisposeMetersThreadSafe();
|
|
RemoveMetersThreadSafe();
|
|
RemoveWorkersThreadSafe();
|
|
|
|
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 = GetMeterThreadSafe(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 = GetMeterThreadSafe(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 = GetMeterThreadSafe(slot);
|
|
|
|
bool logoutSuccess = meter.Logout();
|
|
meter.Disconnect();
|
|
|
|
if (meter.IsLoggedOn)
|
|
{
|
|
meter.MarkLoggedOut();
|
|
meter.LastLogoutStatus = logoutSuccess
|
|
? "Successfully logged out"
|
|
: "Logout failed, but connection was closed";
|
|
}
|
|
|
|
bool success = !meter.IsConnected && !meter.IsLoggedOn;
|
|
|
|
if (success)
|
|
{
|
|
LogInfo(operation, $"Success. Slot={slot}");
|
|
}
|
|
else
|
|
{
|
|
LogInfo(
|
|
operation,
|
|
$"Disconnect incomplete. Slot={slot}, IsConnected={meter.IsConnected}, IsLoggedOn={meter.IsLoggedOn}");
|
|
}
|
|
|
|
//if (!meter.IsConnected && meter.IsLoggedOn)
|
|
// meter.ConnectMeter(); // repair of this situation
|
|
|
|
return new PublicModels.GciDisconnectResult
|
|
{
|
|
SlotId = slot,
|
|
Success = success,
|
|
Message = string.Join(
|
|
" | ",
|
|
new[]
|
|
{
|
|
meter.LastLogoutStatus,
|
|
meter.LastDisposeStatus,
|
|
success
|
|
? null
|
|
: $"Disconnect state invalid. IsConnected={meter.IsConnected}, IsLoggedOn={meter.IsLoggedOn}"
|
|
}
|
|
.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 = GetMeterThreadSafe(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 = GetMeterThreadSafe(slot);
|
|
EnsureConnected(meter);
|
|
|
|
var raw = meter.ReadRegister(name);
|
|
|
|
bool success = raw != null && raw.Length > 0;
|
|
|
|
var result = new RegisterReadResult
|
|
{
|
|
Success = success,
|
|
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)
|
|
{
|
|
return GetWorker(slot).RunAsync(() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState), token);
|
|
}
|
|
|
|
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 = GetMeterThreadSafe(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 = GetMeterThreadSafe(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
|
|
|
|
// GCI to PreadjustmentUI - MANUAL - Laatzen GUI
|
|
|
|
#region ================================== PreAdjustmentUI form call ==================================
|
|
|
|
private FrmCordonelPreadjustmentUI _preadjustmentForm;
|
|
private readonly object _formLock = new object();
|
|
|
|
public event EventHandler PreadjustmentFormClosedByUser;
|
|
|
|
/// <summary>
|
|
/// Shows singleton instance of preadjustment form.
|
|
///
|
|
/// Behavior:
|
|
///
|
|
/// ShowPreadjustmentForm()
|
|
/// ↓
|
|
/// create form instance if necessary
|
|
/// ↓
|
|
/// user works with form
|
|
/// ↓
|
|
/// user clicks X
|
|
/// ↓
|
|
/// FormClosing
|
|
/// ↓
|
|
/// Cancel closing
|
|
/// ↓
|
|
/// Hide()
|
|
/// ↓
|
|
/// PreadjustmentFormClosedByUser
|
|
/// ↓
|
|
/// external workflow continues
|
|
///
|
|
/// Notes:
|
|
/// - form instance is reused
|
|
/// - form is hidden instead of disposed
|
|
/// - event notification is non-blocking
|
|
/// - repeated calls bring existing form to front
|
|
/// - actual disposal happens only during application shutdown
|
|
///
|
|
/// Typical usage:
|
|
///
|
|
/// _bridge.PreadjustmentFormClosedByUser += (s,e)=>
|
|
/// {
|
|
/// ContinueWorkflow();
|
|
/// };
|
|
///
|
|
/// _bridge.ShowPreadjustmentForm(this);
|
|
///
|
|
/// </summary>
|
|
public void ShowPreadjustmentForm(IWin32Window owner)
|
|
{
|
|
lock (_formLock)
|
|
{
|
|
// Create form only if it does not exist
|
|
// or has already been disposed
|
|
if (_preadjustmentForm == null || _preadjustmentForm.IsDisposed)
|
|
{
|
|
_preadjustmentForm = new FrmCordonelPreadjustmentUI(_meterBatch);
|
|
|
|
_preadjustmentForm.FormClosing += (s, e) =>
|
|
{
|
|
// Hide the form instead of destroying it
|
|
// when user clicks the close button
|
|
if (e.CloseReason == CloseReason.UserClosing)
|
|
{
|
|
e.Cancel = true;
|
|
_preadjustmentForm.Hide();
|
|
|
|
// Notify outside code that the form was closed by user
|
|
PreadjustmentFormClosedByUser?.Invoke(
|
|
this,
|
|
EventArgs.Empty);
|
|
}
|
|
};
|
|
}
|
|
|
|
// If already visible, bring it to front
|
|
if (_preadjustmentForm.Visible)
|
|
{
|
|
_preadjustmentForm.Activate();
|
|
_preadjustmentForm.BringToFront();
|
|
return;
|
|
}
|
|
|
|
// Show existing form instance
|
|
_preadjustmentForm.Show(owner);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== Create context for PreAdjustmentUI ==================================
|
|
|
|
/*public interface IGciToolContext
|
|
{
|
|
IReadOnlyList<int> GetEnabledSlots();
|
|
|
|
string GetPcbId(int slot);
|
|
string GetSerialNumber(int slot);
|
|
|
|
bool Login(int slot);
|
|
bool Logout(int slot);
|
|
|
|
bool ReadRegister(int slot, int address, out string value);
|
|
bool WriteRegister(int slot, int address, string value);
|
|
|
|
void LogInfo(string message);
|
|
void LogError(string message);
|
|
|
|
event EventHandler<GciMeterChangedEventArgs> MeterChanged;
|
|
}*/
|
|
|
|
|
|
#endregion
|
|
|
|
// GCI to PreadjustmentUI - AUTOMATIC - STANDALONE - Laatzen GUI
|
|
|
|
#region ================================== PreAdjustmentUI DETECT process ==================================
|
|
|
|
public Task<bool> Preadjustment_DetectAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
return Task.Run(() => Preadjustment_DetectCore(
|
|
token),
|
|
token);
|
|
}
|
|
|
|
private bool Preadjustment_DetectCore(
|
|
CancellationToken token)
|
|
{
|
|
MeterBatch globalMeterBatch = new MeterBatch();
|
|
MeterBatch thermoMeterBatch = new MeterBatch();
|
|
|
|
if (globalMeterBatch == null)
|
|
throw new ArgumentNullException(nameof(globalMeterBatch));
|
|
|
|
if (thermoMeterBatch == null)
|
|
throw new ArgumentNullException(nameof(thermoMeterBatch));
|
|
|
|
if (_settings == null)
|
|
throw new ArgumentNullException(nameof(_settings));
|
|
|
|
if (_meterControls == null)
|
|
throw new ArgumentNullException(nameof(_meterControls));
|
|
|
|
if (_tempMeterControls == null)
|
|
_tempMeterControls = new List<MeterStateControl>();
|
|
|
|
// Clear previous batch content
|
|
if (globalMeterBatch.ListOfMeters.Any())
|
|
globalMeterBatch.RemoveAllMeters();
|
|
|
|
if (thermoMeterBatch.ListOfMeters.Any())
|
|
thermoMeterBatch.RemoveAllMeters();
|
|
|
|
globalMeterBatch = _meterBatch;
|
|
|
|
// If manual temperature input is used,
|
|
// ignore temporary meter controls
|
|
if (!_settings.GetTempUseTempFlansh())
|
|
_tempMeterControls.Clear();
|
|
|
|
var allMeterControls =
|
|
new List<MeterStateControl>();
|
|
|
|
allMeterControls.AddRange(_meterControls);
|
|
allMeterControls.AddRange(_tempMeterControls);
|
|
|
|
CreateZeroFlowMeters(globalMeterBatch, thermoMeterBatch, allMeterControls, token);
|
|
|
|
SetEnableOpeningState(allMeterControls);
|
|
|
|
if (!_meterControls.Any(a => a.EnableOpening))
|
|
return false;
|
|
|
|
SetUnknownStatus(allMeterControls);
|
|
|
|
//CheckNormalMeters( meterControls, token); //open ports
|
|
|
|
CheckTemperatureMeters(_settings, _tempMeterControls, token);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void CreateZeroFlowMeters(
|
|
MeterBatch globalMeterBatch,
|
|
MeterBatch thermoMeterBatch,
|
|
List<MeterStateControl> allMeterControls,
|
|
CancellationToken token)
|
|
{
|
|
foreach (var meterStateCtl in allMeterControls)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
if (!(meterStateCtl.IsEnabled ||
|
|
meterStateCtl is TempMeterStateControl))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (meterStateCtl.Slot == -1)
|
|
continue;
|
|
|
|
ZeroFlowGenesisMeter currentMeter = new ZeroFlowGenesisMeter(meterStateCtl.Slot, 3, !(meterStateCtl is TempMeterStateControl));
|
|
|
|
var convertedMeter = GetMeterThreadSafe(meterStateCtl.Slot);
|
|
if (convertedMeter != null)
|
|
{
|
|
convertedMeter.CopySafeStateTo(currentMeter);
|
|
}
|
|
|
|
currentMeter.LogOnEnable = true;
|
|
currentMeter.LoginFailed = false;
|
|
currentMeter.PreparationFailed = false;
|
|
currentMeter.AmplitudeFailed = false;
|
|
currentMeter.ZeroFlowOffsetFailed = false;
|
|
currentMeter.CompletionFailed = false;
|
|
currentMeter.Ok = false;
|
|
currentMeter.EmptyPipeCheckEnable = false;
|
|
currentMeter.EmptyPipeCheckFailed = false;
|
|
|
|
if (meterStateCtl is TempMeterStateControl)
|
|
{
|
|
thermoMeterBatch.AddMeter(currentMeter);
|
|
meterStateCtl.IsEnabled = true;
|
|
}
|
|
else
|
|
{
|
|
if (currentMeter.useConfigSource != ConfigSource.InterfaceInputConfig)
|
|
globalMeterBatch.AddMeter(currentMeter);
|
|
else
|
|
globalMeterBatch.AddMeter2(currentMeter);
|
|
}
|
|
|
|
meterStateCtl.Meter = currentMeter;
|
|
}
|
|
}
|
|
|
|
private void SetEnableOpeningState(
|
|
List<MeterStateControl> allMeterControls)
|
|
{
|
|
foreach (var meterState in allMeterControls)
|
|
{
|
|
meterState.EnableOpening =
|
|
meterState.IsEnabled;
|
|
}
|
|
}
|
|
|
|
private void SetUnknownStatus(
|
|
List<MeterStateControl> allMeterControls)
|
|
{
|
|
foreach (var meterState in allMeterControls)
|
|
{
|
|
meterState.SetToUnknownStatus =
|
|
!meterState.EnableOpening;
|
|
}
|
|
}
|
|
|
|
private void CheckNormalMeters(
|
|
List<MeterStateControl> meterControls,
|
|
CancellationToken token)
|
|
{
|
|
foreach (var meterCtl in meterControls)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
if (meterCtl != null && meterCtl.IsEnabled)
|
|
{
|
|
meterCtl.Ok =
|
|
meterCtl.Meter.CheckRequestPort() &&
|
|
meterCtl.Meter.CheckStreamingPort();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CheckTemperatureMeters(
|
|
PreAdjustmentSettingsContainer settings,
|
|
List<MeterStateControl> tempMeterControls,
|
|
CancellationToken token)
|
|
{
|
|
if (settings.GetTempUseManualInput())
|
|
return;
|
|
|
|
foreach (var meterCtl in tempMeterControls)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
if (meterCtl != null && meterCtl.IsEnabled)
|
|
{
|
|
meterCtl.Ok =
|
|
meterCtl.Meter.CheckStreamingPort();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI PREPARATION process ==================================
|
|
|
|
/// <summary>
|
|
/// Execute standalone preparation process.
|
|
///
|
|
/// Flow:
|
|
///
|
|
/// Create ProcessProgress
|
|
/// ↓
|
|
/// Create PreparationProcess
|
|
/// ↓
|
|
/// Start process
|
|
/// ↓
|
|
/// Wait for completion
|
|
/// ↓
|
|
/// Return result
|
|
///
|
|
/// Notes:
|
|
/// - GUI independent
|
|
/// - reusable from GCI workflow
|
|
/// - supports SinglePath and MultiPath mode
|
|
/// </summary>
|
|
public Task<bool> Preadjustment_PreparationAsync(
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
IProcess process;
|
|
|
|
if (pp.Setting.NumberOfPaths == 1)
|
|
{
|
|
process =
|
|
new SPPreparationProcess(
|
|
"Preparation Single",
|
|
PreAdjustmentControl.StatusPanelItems.Prepare,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilPreparationFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.PreparationFailed(pp.Setting.Culture),
|
|
60);
|
|
}
|
|
else
|
|
{
|
|
process =
|
|
new PreparationProcess(
|
|
"Preparation",
|
|
PreAdjustmentControl.StatusPanelItems.Prepare,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilPreparationFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.PreparationFailed(pp.Setting.Culture),
|
|
60);
|
|
}
|
|
|
|
return ExecuteProcessAsync(
|
|
process,
|
|
pp,
|
|
token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI AMPLITUDE TEST process ==================================
|
|
|
|
/// <summary>
|
|
/// Execute standalone amplitude test process.
|
|
/// </summary>
|
|
public Task<bool> Preadjustment_AmplitudeTestAsync(
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
if (pp.Setting.TempOnly)
|
|
return Task.FromResult(true);
|
|
|
|
IProcess process;
|
|
|
|
if (pp.Setting.NumberOfPaths == 1)
|
|
{
|
|
process =
|
|
new SPAmplitudeTestProcess(
|
|
"Amplitude Test Single",
|
|
PreAdjustmentControl.StatusPanelItems.Amplitude,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilAmplitudeTestFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.AmplitudeFailed(pp.Setting.Culture),
|
|
4 * 60);
|
|
}
|
|
else
|
|
{
|
|
process =
|
|
new AmplitudeTestProcess(
|
|
"Amplitude Test",
|
|
PreAdjustmentControl.StatusPanelItems.Amplitude,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilAmplitudeTestFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.AmplitudeFailed(pp.Setting.Culture),
|
|
4 * 60);
|
|
}
|
|
|
|
return ExecuteProcessAsync(
|
|
process,
|
|
pp,
|
|
token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI TEMPERATURE CALIBRATION process ==================================
|
|
|
|
public Task<bool> Preadjustment_TemperatureCalibrationAsync(
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
IProcess process;
|
|
|
|
if (pp.Setting.NumberOfPaths == 1)
|
|
{
|
|
process =
|
|
new SPTemperatureCalibrationProcess(
|
|
"Temperature Calibration Single",
|
|
PreAdjustmentControl.StatusPanelItems.TempCal,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilTemperatureCalibrationFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.TempCalFailed(pp.Setting.Culture),
|
|
2 * 60);
|
|
}
|
|
else
|
|
{
|
|
process =
|
|
new TemperatureCalibrationProcess(
|
|
"Temperature Calibration",
|
|
PreAdjustmentControl.StatusPanelItems.TempCal,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilTemperatureCalibrationFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.TempCalFailed(pp.Setting.Culture),
|
|
2 * 60);
|
|
}
|
|
|
|
return ExecuteProcessAsync(
|
|
process,
|
|
pp,
|
|
token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI OFFSET TEST process ==================================
|
|
|
|
public Task<bool> Preadjustment_OffsetTestAsync(
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
if (pp.Setting.TempOnly)
|
|
return Task.FromResult(true);
|
|
|
|
IProcess process;
|
|
|
|
if (pp.Setting.NumberOfPaths == 1)
|
|
{
|
|
process =
|
|
new SPOffsetTestProcess(
|
|
"Offset Test Single",
|
|
PreAdjustmentControl.StatusPanelItems.Offset,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilZeroflowOffsetTestFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.ZeroflowOffsetTestFailed(pp.Setting.Culture),
|
|
18 * 60);
|
|
}
|
|
else
|
|
{
|
|
process =
|
|
new OffsetTestProcess(
|
|
"Offset Test",
|
|
PreAdjustmentControl.StatusPanelItems.Offset,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilZeroflowOffsetTestFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.ZeroflowOffsetTestFailed(pp.Setting.Culture),
|
|
18 * 60);
|
|
}
|
|
|
|
return ExecuteProcessAsync(
|
|
process,
|
|
pp,
|
|
token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI COMPLETION process ==================================
|
|
|
|
public Task<bool> Preadjustment_CompletionAsync(
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
IProcess process;
|
|
|
|
if (pp.Setting.NumberOfPaths == 1)
|
|
{
|
|
process =
|
|
new SPCompletionProcess(
|
|
"Completion Single",
|
|
PreAdjustmentControl.StatusPanelItems.Completion,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilCompletionFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.CompletionFailed(pp.Setting.Culture),
|
|
1 * 60);
|
|
}
|
|
else
|
|
{
|
|
process =
|
|
new CompletionProcess(
|
|
"Completion",
|
|
PreAdjustmentControl.StatusPanelItems.Completion,
|
|
PreAdjustmentControl.PredefinedMessages.WaitUntilCompletionFinished(pp.Setting.Culture),
|
|
PreAdjustmentControl.PredefinedMessages.CompletionFailed(pp.Setting.Culture),
|
|
1 * 60);
|
|
}
|
|
|
|
return ExecuteProcessAsync(
|
|
process,
|
|
pp,
|
|
token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustmentUI process threading ==================================
|
|
|
|
public async Task<bool> ExecuteProcessAsync(
|
|
IProcess process,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
pp.IsBusy = true;
|
|
|
|
process.StartProcess(
|
|
pp,
|
|
_meterControls,
|
|
_tempMeterControls);
|
|
|
|
while (pp.IsBusy && !token.IsCancellationRequested)
|
|
{
|
|
Thread.Sleep(50);
|
|
}
|
|
|
|
return !_meterControls.Any(
|
|
m => m.IsEnabled && m.Failed);
|
|
|
|
}, token);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |