1794 lines
64 KiB
C#
1794 lines
64 KiB
C#
using CordonelPreadjustmentUi;
|
|
using CordonelPreadjustmentUi.Processes.Itinerary;
|
|
using GenesisCordonelInterface.API;
|
|
using GenesisCordonelInterface.Core.Threading;
|
|
using log4net;
|
|
///
|
|
/// Copyright (c) 2015-2021 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using TBF.Rig.BridgeComponents.GciBridge.Interfaces;
|
|
using TBF.Rig.BridgeComponents.GciBridge.UI;
|
|
using TBF.Rig.Generic;
|
|
using TBF.Rig.Input.DataStorage.UniDataStorageReader;
|
|
using TBF.Rig.Input.DataStorage.UniDataStorageReader.Searching.Database;
|
|
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
|
using Xylem.Common.Ui.CordonelPreadjustmentUi;
|
|
using static GenesisCordonelInterface.API.PublicModels;
|
|
using static TBF.Rig.BridgeComponents.GciBridge.Interfaces.PublicModels;
|
|
using GciGUIType = GenesisCordonelInterface.UI.MainView;
|
|
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
|
|
using GciType = GenesisCordonelInterface.API.InterfaceOutsideToGCI;
|
|
using UdsReaderType = TBF.Rig.Input.DataStorage.UniDataStorageReader.Reader;
|
|
using UDSRPublicModels = TBF.Rig.Input.DataStorage.UniDataStorageReader.Interfaces.PublicModels;
|
|
using UdsWriterType = TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
|
|
|
|
namespace TBF.Rig.BridgeComponents.GciBridge
|
|
{
|
|
/// <summary>
|
|
/// TBF bridge component for integration with the sibling GCI project.
|
|
/// The component can be linked to UniDataStorage reader and writer components.
|
|
/// </summary>
|
|
public class GciBridge : ComponentBase
|
|
{
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(GciBridge));
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("{0}({1})", ClassName, Cfg.ToString(-1));
|
|
}
|
|
|
|
readonly GciBridgeCfg gciBridgeCfg;
|
|
|
|
readonly UdsReaderType reader;
|
|
readonly UdsWriterType writer;
|
|
|
|
//diag GUI for GCI
|
|
GciGUIType gciGUI;
|
|
Form gciGuiHostForm;
|
|
|
|
//diag GUI for GciBridge
|
|
public UserControl gciBridgeGUIUserControl;
|
|
public UI.MainForm gciBridgeGuiForm;
|
|
|
|
public GciType gciExternalInterface;
|
|
|
|
public bool HasReader { get { return reader != null; } }
|
|
public bool HasWriter { get { return writer != null; } }
|
|
public bool IsGuiInitialized { get { return gciGUI != null; } }
|
|
public bool IsExternalInitialized { get { return gciExternalInterface != null; } }
|
|
public GciType GciExternalInterface { get { return gciExternalInterface; } }
|
|
public GciBridgeCfg GciBridgeCfg { get { return gciBridgeCfg; } }
|
|
|
|
public UdsReaderType GetReader()
|
|
{
|
|
return reader;
|
|
}
|
|
|
|
public UdsWriterType GetWriter()
|
|
{
|
|
return writer;
|
|
}
|
|
|
|
public GciBridge() { }
|
|
|
|
public GciBridge(IComponentCfg cfg, IList<IComponent> components)
|
|
: base(cfg)
|
|
{
|
|
gciBridgeCfg = cfg as GciBridgeCfg;
|
|
if (gciBridgeCfg == null) throw new Exception("Invalid GciBridgeCfg.");
|
|
|
|
if (!string.IsNullOrEmpty(gciBridgeCfg.ReaderName))
|
|
{
|
|
reader = TbfComponents.FindComponent(gciBridgeCfg.ReaderName, components) as UdsReaderType;
|
|
if (reader == null) throw new Exception("Cannot find reader component '" + gciBridgeCfg.ReaderName + "'");
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(gciBridgeCfg.WriterName))
|
|
{
|
|
writer = TbfComponents.FindComponent(gciBridgeCfg.WriterName, components) as UdsWriterType;
|
|
if (writer == null) throw new Exception("Cannot find writer component '" + gciBridgeCfg.WriterName + "'");
|
|
}
|
|
|
|
Initialize();
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
if (gciBridgeCfg.EnableExternalAccess)
|
|
{
|
|
TryInitializeExternalInterface();
|
|
}
|
|
|
|
if (gciBridgeCfg.EnableGuiAccess)
|
|
{
|
|
TryInitializeGciGui();
|
|
|
|
if (gciBridgeCfg.ShowGuiOnInitialize)
|
|
{
|
|
ShowGciGui();
|
|
}
|
|
}
|
|
|
|
log.FatalFormat("{0} initialized: {1}", Name, this);
|
|
}
|
|
|
|
void TryInitializeGciBridgeGui()
|
|
{
|
|
try
|
|
{
|
|
if (gciBridgeGuiForm != null && !gciBridgeGuiForm.IsDisposed)
|
|
return;
|
|
|
|
gciBridgeGuiForm = new UI.MainForm(this);
|
|
gciBridgeGuiForm.Text = "Gci Bridge GUI";
|
|
gciBridgeGuiForm.Width = 1300;
|
|
gciBridgeGuiForm.Height = 600;
|
|
gciBridgeGuiForm.StartPosition = FormStartPosition.CenterScreen;
|
|
|
|
/*gciBridgeGUI = new MainView(this, gciBridgeGuiForm);
|
|
gciBridgeGUI.Dock = DockStyle.Fill;
|
|
|
|
gciBridgeGuiForm.Controls.Add(gciBridgeGUI);
|
|
|
|
gciBridgeGuiForm.FormClosed += (s, e) =>
|
|
{
|
|
gciBridgeGUI = null;
|
|
gciBridgeGuiForm = null;
|
|
};*/
|
|
|
|
log.InfoFormat("{0}: GciBridge GUI view initialized.", Name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("Failed to initialize GciBridge GUI view.", ex);
|
|
}
|
|
}
|
|
|
|
void TryInitializeGciGui()
|
|
{
|
|
try
|
|
{
|
|
if (gciGuiHostForm != null && !gciGuiHostForm.IsDisposed)
|
|
return;
|
|
|
|
gciGUI = new GciGUIType();
|
|
gciGUI.Dock = DockStyle.Fill;
|
|
|
|
gciGuiHostForm = new Form();
|
|
gciGuiHostForm.Text = "Genesis Cordonel Interface";
|
|
gciGuiHostForm.Width = 1300;
|
|
gciGuiHostForm.Height = 600;
|
|
gciGuiHostForm.StartPosition = FormStartPosition.CenterScreen;
|
|
|
|
gciGuiHostForm.Controls.Add(gciGUI);
|
|
|
|
log.InfoFormat("{0}: GCI GUI view initialized.", Name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("Failed to initialize GCI GUI view.", ex);
|
|
}
|
|
}
|
|
|
|
void TryInitializeExternalInterface()
|
|
{
|
|
try
|
|
{
|
|
if (gciExternalInterface != null)
|
|
return;
|
|
|
|
gciExternalInterface = new GciType();
|
|
|
|
log.InfoFormat("{0}: GCI external interface initialized.", Name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("Failed to initialize GCI external interface.", ex);
|
|
}
|
|
}
|
|
|
|
public void ShowGciGui()
|
|
{
|
|
if (!gciBridgeCfg.EnableGuiAccess) return;
|
|
|
|
if (gciGuiHostForm == null || gciGuiHostForm.IsDisposed)
|
|
{
|
|
TryInitializeGciGui();
|
|
}
|
|
|
|
if (gciGuiHostForm == null) return;
|
|
|
|
gciGuiHostForm.Show();
|
|
gciGuiHostForm.BringToFront();
|
|
|
|
log.InfoFormat("{0}: ShowGciGui invoked.", Name);
|
|
}
|
|
|
|
public void HideGciGui()
|
|
{
|
|
if (!gciBridgeCfg.EnableGuiAccess) return;
|
|
if (gciGuiHostForm == null || gciGuiHostForm.IsDisposed) return;
|
|
|
|
gciGuiHostForm.Hide();
|
|
|
|
log.InfoFormat("{0}: HideGciGui invoked.", Name);
|
|
}
|
|
|
|
public void ShowGui()
|
|
{
|
|
if (!gciBridgeCfg.EnableGuiAccess) return;
|
|
|
|
if (gciBridgeGuiForm == null || gciBridgeGuiForm.IsDisposed)
|
|
{
|
|
TryInitializeGciBridgeGui();
|
|
}
|
|
|
|
if (gciBridgeGuiForm == null || gciBridgeGuiForm.IsDisposed)
|
|
return;
|
|
|
|
gciBridgeGuiForm.Show();
|
|
gciBridgeGuiForm.BringToFront();
|
|
|
|
log.InfoFormat("{0}: ShowGui invoked.", Name);
|
|
}
|
|
|
|
public void HideGui()
|
|
{
|
|
if (!gciBridgeCfg.EnableGuiAccess) return;
|
|
if (gciBridgeGuiForm == null || gciBridgeGuiForm.IsDisposed) return;
|
|
|
|
gciBridgeGuiForm.Hide();
|
|
|
|
log.InfoFormat("{0}: HideGciGui invoked.", Name);
|
|
}
|
|
|
|
void EnsureExternalInterface()
|
|
{
|
|
if (!gciBridgeCfg.EnableExternalAccess)
|
|
throw new Exception("GCI external access is disabled.");
|
|
|
|
if (!IsExternalInitialized)
|
|
TryInitializeExternalInterface();
|
|
|
|
if (gciExternalInterface == null)
|
|
throw new Exception("GCI external interface is not initialized.");
|
|
}
|
|
|
|
void EnsureReader()
|
|
{
|
|
if (reader == null)
|
|
throw new Exception("UniDataStorageReader is not linked to GciBridge.");
|
|
}
|
|
|
|
// API:
|
|
#region ======================================= GCI Public Interface =======================================
|
|
|
|
/// <summary>
|
|
/// Initializes one GCI slot through the external GCI interface.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.InitSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.InitSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.InitSlotAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.SetupFromExternConfig()
|
|
/// </summary>
|
|
/// <param name="request">Public GCI slot initialization request.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Initialization result for the requested slot.</returns>
|
|
public async Task<GciPublicModels.GciInitSlotResult> InitSlotAsync(
|
|
GciPublicModels.GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
if (request.SlotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(request));
|
|
|
|
var result = await gciExternalInterface
|
|
.InitSlotAsync(request, token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat("{0}: InitSlotAsync invoked. {1}, Result={2}", Name, request, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes one GCI slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed slot initialization attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// InitSlotWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> InitSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.InitSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.InitSlotAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.SetupFromExternConfig()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciInitSlotResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="request">Public GCI slot initialization request.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Initialization result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciInitSlotResult>> InitSlotWithRetryAsync(
|
|
GciPublicModels.GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => InitSlotAsync(request, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"InitSlotAsync slot {request?.SlotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 500,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Updates an already initialized GCI slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.UpdateSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.UpdateSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.UpdateSlotAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.SetupFromExternConfig()
|
|
/// </summary>
|
|
/// <param name="request">Public GCI slot update request.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Update result for the requested slot.</returns>
|
|
public async Task<GciPublicModels.GciInitSlotResult> UpdateSlotAsync(
|
|
GciPublicModels.GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
if (request.SlotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(request));
|
|
|
|
var result = await gciExternalInterface
|
|
.UpdateSlotAsync(request, token)
|
|
.ConfigureAwait(false);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an already initialized GCI slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed slot update attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// UpdateSlotWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> UpdateSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.UpdateSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.UpdateSlotAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.SetupFromExternConfig()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciInitSlotResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="request">Public GCI slot update request.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Update result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciInitSlotResult>> UpdateSlotWithRetryAsync(
|
|
GciPublicModels.GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => UpdateSlotAsync(request, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"UpdateSlotAsync slot {request?.SlotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 500,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads current information about one initialized GCI slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.GetSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.GetSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.GetOneMeterInfo()
|
|
/// -> per-slot GCI worker
|
|
/// -> MeterBatch / GenesisMeter snapshot
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Current public slot information.</returns>
|
|
public async Task<GciPublicModels.GciSlotInfo> GetSlotAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
GciPublicModels.GciSlotInfo result =
|
|
await gciExternalInterface.GetSlotAsync(slotId, token);
|
|
|
|
log.InfoFormat("{0}: GetSlotAsync({1}) invoked. Result={2}", Name, slotId, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads current information about one initialized GCI slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed slot info read attempts
|
|
/// - validates that result is not null
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// GetSlotWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> GetSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.GetSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.GetOneMeterInfo()
|
|
/// -> per-slot GCI worker
|
|
/// -> MeterBatch / GenesisMeter snapshot
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciSlotInfo
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Current public slot information wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciSlotInfo>> GetSlotWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => GetSlotAsync(slotId, token),
|
|
r => r != null,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"GetSlotAsync slot {slotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 500,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears one initialized GCI slot and related GCI worker state.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.CleanSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.CleanSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.CleanSlotAsync()
|
|
/// -> selected slot cleanup
|
|
/// -> worker cleanup
|
|
/// </summary>
|
|
/// <param name="slot">GCI slot id.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Cleanup operation result.</returns>
|
|
public async Task<GciPublicModels.GciCleanSlotResult> CleanSlotAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
|
|
|
GciPublicModels.GciCleanSlotResult result =
|
|
await gciExternalInterface.CleanSlotAsync(slot, token);
|
|
|
|
log.InfoFormat("{0}: CleanSlotAsync invoked. Slot={1}, Result={2}", Name, slot, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans one initialized slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed cleanup attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// CleanSlotWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> CleanSlotAsync()
|
|
/// -> InterfaceOutsideToGCI.CleanSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.CleanSlotAsync()
|
|
/// -> worker cleanup
|
|
/// -> meter cleanup
|
|
/// -> slot cleanup
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciCleanSlotResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Target slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Cleanup result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciCleanSlotResult>> CleanSlotWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => CleanSlotAsync(slotId),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"CleanSlotAsync slot {slotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all initialized GCI slots and related GCI worker state.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.CleanAllSlotsAsync()
|
|
/// -> InterfaceOutsideToGCI.CleanAllSlotsAsync()
|
|
/// -> InterfaceGCIToLaatzen.CleanAllSlotsAsync()
|
|
/// -> MeterBatch cleanup
|
|
/// -> selected slot cleanup
|
|
/// -> worker cleanup
|
|
/// </summary>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Cleanup operation result.</returns>
|
|
public async Task<GciPublicModels.GciCleanAllSlotsResult> CleanAllSlotsAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
GciPublicModels.GciCleanAllSlotsResult result =
|
|
await gciExternalInterface.CleanAllSlotsAsync(token);
|
|
|
|
log.InfoFormat("{0}: CleanAllSlotsAsync invoked.", Name);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans all initialized slots using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed cleanup attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// CleanAllSlotsWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> CleanAllSlotsAsync()
|
|
/// -> InterfaceOutsideToGCI.CleanAllSlotsAsync()
|
|
/// -> InterfaceGCIToLaatzen.CleanAllSlotsAsync()
|
|
/// -> worker cleanup
|
|
/// -> MeterBatch cleanup
|
|
/// -> slot cleanup
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciCleanAllSlotsResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Cleanup result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciCleanAllSlotsResult>> CleanAllSlotsWithRetryAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => CleanAllSlotsAsync(),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
"CleanAllSlotsAsync",
|
|
maxAttempts: 3,
|
|
delayMs: 5,
|
|
timeoutMs: 60000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads PCB ID from the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.GetPcbIdAsync()
|
|
/// -> InterfaceOutsideToGCI.GetPcbIdAsync()
|
|
/// -> InterfaceGCIToLaatzen.GetPcbIdAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.GetPcbId()
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>PCB ID read result.</returns>
|
|
public async Task<GciPublicModels.GciGetPcbIdResult> GetPcbIdAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(slotId));
|
|
|
|
var result = await gciExternalInterface
|
|
.GetPcbIdAsync(slotId, token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: GetPcbIdAsync({1}) invoked. Result={2}",
|
|
Name,
|
|
slotId,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads PCB ID from meter in the specified slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed PCB reads
|
|
/// - validates Success=true
|
|
/// - tracks retry statistics and duration
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// GetPcbIdWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> GetPcbIdAsync()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciGetPcbIdResult
|
|
/// - retry statistics
|
|
/// - timeout state
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>PCB ID result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciGetPcbIdResult>> GetPcbIdWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => GetPcbIdAsync(slotId, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"GetPcbIdAsync slot {slotId}",
|
|
maxAttempts: 10,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connects to meter in the specified slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed connect attempts
|
|
/// - validates Success=true
|
|
/// - tracks duration and retry count
|
|
/// - supports cancellation token
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// ConnectWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> ConnectAsync()
|
|
/// -> InterfaceOutsideToGCI.ConnectOneSlotAsync()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciConnectResult
|
|
/// - retry statistics
|
|
/// - timeout state
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Connection result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciConnectResult>> ConnectWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => ConnectAsync(slotId, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"ConnectAsync slot {slotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Connects/logs in to the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.ConnectAsync()
|
|
/// -> InterfaceOutsideToGCI.ConnectOneSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.ConnectOneMeterAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> Protocols connect to ports
|
|
/// -> GenesisMeter register snapshot
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Connection result including firmware/interface information.</returns>
|
|
public async Task<GciPublicModels.GciConnectResult> ConnectAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
|
|
|
var result = await gciExternalInterface
|
|
.ConnectOneSlotAsync(slot, token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat("{0}: ConnectAsync({1}) invoked. Result={2}", Name, slot, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs in to the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.LoginAsync()
|
|
/// -> InterfaceOutsideToGCI.LoginOneSlotAsync()
|
|
/// -> InterfaceGCIToLaatzen.LoginOneMeterAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> MeterBatch.MetersLogin()
|
|
/// -> GenesisMeter login state
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Login result for the requested slot.</returns>
|
|
public async Task<GciPublicModels.GciLoginResult> LoginAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await gciExternalInterface.LoginOneSlotAsync(slotId, token);
|
|
|
|
log.InfoFormat("{0}: LoginAsync({1}) invoked. Result={2}", Name, slotId, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs meter login using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed login attempts
|
|
/// - validates Success=true
|
|
/// - tracks retry count and execution duration
|
|
/// - supports cancellation
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// LoginWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> LoginAsync()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciLoginResult
|
|
/// - retry statistics
|
|
/// - timeout state
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Login result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciLoginResult>> LoginWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => LoginAsync(slotId, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"LoginAsync slot {slotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 5,
|
|
timeoutMs: 60000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects/logs out from the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.DisconnectAsync()
|
|
/// -> InterfaceOutsideToGCI.DisconnectAsync()
|
|
/// -> InterfaceGCIToLaatzen.DisconnectAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.Logout()
|
|
/// -> GenesisMeter.DisposeMeter()
|
|
/// -> worker disposal
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Disconnect operation result.</returns>
|
|
public async Task<GciPublicModels.GciDisconnectResult> DisconnectAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await gciExternalInterface.DisconnectAsync(slotId, token);
|
|
|
|
log.InfoFormat("{0}: DisconnectAsync({1}) invoked. Result={2}", Name, slotId, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects/logs out meter in the specified slot using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed disconnect attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// DisconnectWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> DisconnectAsync()
|
|
/// -> InterfaceOutsideToGCI.DisconnectAsync()
|
|
/// -> InterfaceGCIToLaatzen.DisconnectAsync()
|
|
/// -> GenesisMeter.Logout()
|
|
/// -> GenesisMeter.DisposeMeter()
|
|
/// -> worker cleanup
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciDisconnectResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Target slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Disconnect result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciDisconnectResult>> DisconnectWithRetryAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => DisconnectAsync(slotId, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"DisconnectAsync slot {slotId}",
|
|
maxAttempts: 3,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets password to the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.SetPasswordAsync()
|
|
/// -> InterfaceOutsideToGCI.SetPasswordAsync()
|
|
/// -> InterfaceGCIToLaatzen.SetMeterPasswordAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter.Password = password
|
|
/// -> internal login credentials update
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="password">Password assigned to the meter.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Password set operation result.</returns>
|
|
public async Task<GciPublicModels.GciSetPasswordResult> SetPasswordAsync(
|
|
int slotId,
|
|
string password,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
if (string.IsNullOrWhiteSpace(password))
|
|
throw new ArgumentException("Password is empty.");
|
|
|
|
GciPublicModels.GciSetPasswordResult result =
|
|
await gciExternalInterface.SetPasswordAsync(slotId, password, token);
|
|
|
|
log.InfoFormat("{0}: SetPasswordAsync({1}) invoked. Result={2}", Name, slotId, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends password to meter in the specified slot using retry protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed password writes
|
|
/// - validates Success=true
|
|
/// - tracks retry count and duration
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// SetPasswordWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> SetPasswordAsync()
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - GciSetPasswordResult
|
|
/// - retry statistics
|
|
/// - timeout state
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="password">Password assigned to the meter.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Password set result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.GciSetPasswordResult>> SetPasswordWithRetryAsync(
|
|
int slotId,
|
|
string password,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => SetPasswordAsync(slotId, password, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"SetPasswordAsync slot {slotId}",
|
|
maxAttempts: 5,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads one register value from the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.ReadRegisterAsync()
|
|
/// -> InterfaceOutsideToGCI.ReadRegisterAsync()
|
|
/// -> InterfaceGCIToLaatzen.ReadRegisterAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter register read
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="registerName">Register name.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Register read result.</returns>
|
|
public async Task<GciPublicModels.RegisterReadResult> ReadRegisterAsync(
|
|
int slotId,
|
|
string registerName,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
if (string.IsNullOrWhiteSpace(registerName))
|
|
throw new ArgumentException("Register name is empty.");
|
|
|
|
var result = await gciExternalInterface.ReadRegisterAsync(slotId, registerName, token);
|
|
|
|
log.InfoFormat("{0}: ReadRegisterAsync({1}, {2}) invoked. Result={3}",
|
|
Name, slotId, registerName, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads one register value from the meter using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed register read attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// ReadRegisterWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> ReadRegisterAsync()
|
|
/// -> InterfaceOutsideToGCI.ReadRegisterAsync()
|
|
/// -> InterfaceGCIToLaatzen.ReadRegisterAsync()
|
|
/// -> GenesisMeter register read
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - RegisterReadResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Target slot number.</param>
|
|
/// <param name="registerName">Register name to read.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Register read result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.RegisterReadResult>> ReadRegisterWithRetryAsync(
|
|
int slotId,
|
|
string registerName,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => ReadRegisterAsync(slotId, registerName, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"ReadRegisterAsync slot {slotId}, register {registerName}",
|
|
maxAttempts: 5,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes one register value to the meter assigned to the requested slot.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.WriteRegisterAsync()
|
|
/// -> InterfaceOutsideToGCI.WriteRegisterAsync()
|
|
/// -> InterfaceGCIToLaatzen.WriteRegisterAsync()
|
|
/// -> per-slot GCI worker
|
|
/// -> GenesisMeter register write
|
|
/// -> optional device store
|
|
/// -> optional state refresh
|
|
/// </summary>
|
|
/// <param name="slotId">Slot number.</param>
|
|
/// <param name="registerName">Register name.</param>
|
|
/// <param name="value">Value written to register.</param>
|
|
/// <param name="storeToDevice">Stores value permanently into device memory.</param>
|
|
/// <param name="refreshSystemState">Refreshes internal meter state after write.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Register write result.</returns>
|
|
public async Task<GciPublicModels.RegisterWriteResult> WriteRegisterAsync(
|
|
int slotId,
|
|
string registerName,
|
|
object value,
|
|
bool storeToDevice = false,
|
|
bool refreshSystemState = false,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
if (string.IsNullOrWhiteSpace(registerName))
|
|
throw new ArgumentException("Register name is empty.");
|
|
|
|
var result = await gciExternalInterface.WriteRegisterAsync(
|
|
slotId,
|
|
registerName,
|
|
value,
|
|
storeToDevice,
|
|
refreshSystemState,
|
|
token);
|
|
|
|
log.InfoFormat("{0}: WriteRegisterAsync({1}, {2}, {3}) invoked. Result={4}",
|
|
Name, slotId, registerName, value, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes one register value to the meter using retry and timeout protection.
|
|
///
|
|
/// Features:
|
|
/// - retries failed register write attempts
|
|
/// - validates Success=true
|
|
/// - supports cancellation
|
|
/// - tracks retry count
|
|
/// - tracks total execution duration
|
|
/// - detects timeout situations
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// WriteRegisterWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> WriteRegisterAsync()
|
|
/// -> InterfaceOutsideToGCI.WriteRegisterAsync()
|
|
/// -> InterfaceGCIToLaatzen.WriteRegisterAsync()
|
|
/// -> GenesisMeter register write
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - RegisterWriteResult
|
|
/// - retry statistics
|
|
/// - timeout information
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="slotId">Target slot number.</param>
|
|
/// <param name="registerName">Register name to write.</param>
|
|
/// <param name="value">Value to write into register.</param>
|
|
/// <param name="storeToDevice">Stores value permanently into device memory.</param>
|
|
/// <param name="refreshSystemState">Refreshes internal register cache after write.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Register write result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<GciPublicModels.RegisterWriteResult>> WriteRegisterWithRetryAsync(
|
|
int slotId,
|
|
string registerName,
|
|
object value,
|
|
bool storeToDevice = false,
|
|
bool refreshSystemState = false,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => WriteRegisterAsync(
|
|
slotId,
|
|
registerName,
|
|
value,
|
|
storeToDevice,
|
|
refreshSystemState,
|
|
token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"WriteRegisterAsync slot {slotId}, register {registerName}",
|
|
maxAttempts: 5,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ======================================= UDSR Public Interface =======================================
|
|
|
|
/// <summary>
|
|
/// Reads password data from UniDataStorageReader by PCB ID.
|
|
///
|
|
/// Trace:
|
|
/// GciBridge.GetPasswordAsync()
|
|
/// -> UniDataStorageReader.Reader.GetDataFromStorageByParameterAsync()
|
|
/// -> Reader queue/lock
|
|
/// -> Reader.GetDataFromStorageByParameter()
|
|
/// -> selected storage reader by configuration
|
|
/// -> DatabaseReader.GetData() / RestApiReader.GetData() / JsonReader.GetData() / CsvReader.GetData()
|
|
/// </summary>
|
|
/// <param name="pcbId">PCB ID used as query parameter.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Password lookup result.</returns>
|
|
public async Task<UdsPasswordResult> GetPasswordAsync(
|
|
string pcbId,
|
|
CancellationToken token = default)
|
|
{
|
|
EnsureReader();
|
|
|
|
if (string.IsNullOrWhiteSpace(pcbId))
|
|
throw new ArgumentException("PCB ID is empty.", nameof(pcbId));
|
|
|
|
try
|
|
{
|
|
UDSRPublicModels.DataQuery query = CreatePasswordQuery(pcbId);
|
|
|
|
object data = await reader.GetDataFromStorageByParameterAsync(query, token);
|
|
|
|
string password = ExtractPassword(data);
|
|
|
|
return new UdsPasswordResult
|
|
{
|
|
Success = !string.IsNullOrWhiteSpace(password),
|
|
PcbId = pcbId,
|
|
Password = password,
|
|
Message = !string.IsNullOrWhiteSpace(password)
|
|
? "Password found."
|
|
: "Password was not found."
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("GetPasswordAsync failed.", ex);
|
|
|
|
return new UdsPasswordResult
|
|
{
|
|
Success = false,
|
|
PcbId = pcbId,
|
|
Password = null,
|
|
Message = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads password from UniDataStorageReader using PCB ID with retry support.
|
|
///
|
|
/// Features:
|
|
/// - retries failed storage queries
|
|
/// - validates Success=true
|
|
/// - supports timeout handling
|
|
/// - tracks retry statistics
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// GetPasswordWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> GetPasswordAsync()
|
|
/// -> UniDataStorageReader
|
|
///
|
|
/// Returns:
|
|
/// RetryResult containing:
|
|
/// - UdsPasswordResult
|
|
/// - retry statistics
|
|
/// - timeout state
|
|
/// - execution duration
|
|
/// </summary>
|
|
/// <param name="pcbId">PCB ID used as query parameter.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Password lookup result wrapped inside RetryResult.</returns>
|
|
public Task<RetryResult<UdsPasswordResult>> GetPasswordWithRetryAsync(
|
|
string pcbId,
|
|
CancellationToken token = default)
|
|
{
|
|
return RetryWorker.RunWithRetryAsync(
|
|
() => GetPasswordAsync(pcbId, token),
|
|
r => r.Success,
|
|
msg => log.Info(msg),
|
|
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
|
|
$"GetPasswordAsync pcb {pcbId}",
|
|
maxAttempts: 5,
|
|
delayMs: 5,
|
|
timeoutMs: 30000);
|
|
}
|
|
#endregion
|
|
|
|
#region =================================== Combined Public interface =========================================
|
|
|
|
/// <summary>
|
|
/// Executes complete meter login workflow for a single slot with retry,
|
|
/// timeout handling and automatic recovery.
|
|
///
|
|
/// Workflow:
|
|
/// Connect
|
|
/// -> Read PCB ID
|
|
/// -> Read password from UDSR
|
|
/// -> Set password to meter
|
|
/// -> Login to meter
|
|
///
|
|
/// Every operation:
|
|
/// - supports retry logic
|
|
/// - supports timeout protection
|
|
/// - stores execution statistics
|
|
/// (attempt count, duration, timeout state)
|
|
/// - validates Success=true before continuing
|
|
///
|
|
/// If any step fails:
|
|
/// - workflow is immediately aborted
|
|
/// - DisconnectAsync() cleanup is attempted
|
|
/// - final result contains failed operation details
|
|
///
|
|
/// Parallelism:
|
|
/// - safe to execute for multiple slots in parallel
|
|
/// - each slot internally uses serialized GCI worker queue
|
|
///
|
|
/// Typical usage:
|
|
///
|
|
/// var result = await bridge.ConnectFullPassLoginWithRetryAsync(slot);
|
|
///
|
|
/// Result contains:
|
|
/// - per-step RetryResult<T>
|
|
/// - timing information
|
|
/// - retry statistics
|
|
/// - final workflow state
|
|
///
|
|
/// Internal flow:
|
|
///
|
|
/// GciBridge.ConnectFullPassLoginWithRetryAsync()
|
|
/// -> RetryWorker.RunWithRetryAsync()
|
|
/// -> ConnectAsync()
|
|
/// -> GetPcbIdAsync()
|
|
/// -> GetPasswordAsync()
|
|
/// -> SetPasswordAsync()
|
|
/// -> LoginAsync()
|
|
///
|
|
/// Used by:
|
|
/// - CombinedInterfaceView
|
|
/// - automated meter initialization workflows
|
|
/// - production batch login scenarios
|
|
/// </summary>
|
|
/// <param name="slotId">Target slot number.</param>
|
|
/// <param name="token">Cancellation token.</param>
|
|
/// <returns>Complete workflow result including all retry statistics and operation results.</returns>
|
|
public async Task<GciFullLoginResult> ConnectFullPassLoginWithRetryAsync(int slotId, CancellationToken token = default)
|
|
{
|
|
EnsureExternalInterface();
|
|
EnsureReader();
|
|
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(slotId));
|
|
|
|
var finalResult = new GciFullLoginResult { SlotId = slotId, Success = false };
|
|
|
|
try
|
|
{
|
|
finalResult.ConnectResult = await ConnectWithRetryAsync(slotId, token);
|
|
RetryWorker.EnsureSuccess(finalResult.ConnectResult, $"ConnectAsync slot {slotId}");
|
|
|
|
finalResult.PcbResult = await GetPcbIdWithRetryAsync(slotId, token);
|
|
RetryWorker.EnsureSuccess(finalResult.PcbResult, $"GetPcbIdAsync slot {slotId}");
|
|
|
|
finalResult.PasswordResult = await GetPasswordWithRetryAsync(finalResult.PcbResult.Result.PcbId, token);
|
|
RetryWorker.EnsureSuccess(finalResult.PasswordResult, $"GetPasswordAsync slot {slotId}");
|
|
|
|
finalResult.SetPasswordResult = await SetPasswordWithRetryAsync(slotId, finalResult.PasswordResult.Result.Password, token);
|
|
RetryWorker.EnsureSuccess(finalResult.SetPasswordResult, $"SetPasswordAsync slot {slotId}");
|
|
|
|
finalResult.LoginResult = await LoginWithRetryAsync(slotId, token);
|
|
RetryWorker.EnsureSuccess(finalResult.LoginResult, $"LoginAsync slot {slotId}");
|
|
|
|
finalResult.Success = true;
|
|
finalResult.Message = "Connect / get PCB / get password / set password / login completed.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
finalResult.Success = false;
|
|
finalResult.Message = ex.Message;
|
|
|
|
try
|
|
{
|
|
await DisconnectAsync(slotId, token);
|
|
}
|
|
catch (Exception cleanEx)
|
|
{
|
|
log.ErrorFormat("DisconnectAsync after failed ConnectFullPassLoginAsync failed. Slot={0}, Error={1}", slotId, cleanEx.Message);
|
|
}
|
|
}
|
|
|
|
return finalResult;
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Preadjustment API:
|
|
|
|
#region ================================== Preadjustment DETECT bridge ==================================
|
|
|
|
public async Task<PreadjustmentDetectResult> Preadjustment_DetectAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_DetectAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
log.InfoFormat("{0}: {1} Start.", Name, operation);
|
|
|
|
PreadjustmentDetectResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_DetectAsync(token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentDetectResult
|
|
{
|
|
Success = false,
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region ================================== PreAdjustment PREPARATION bridge ==================================
|
|
|
|
public async Task<PreadjustmentProcessResult> Preadjustment_PreparationAsync(
|
|
int slot,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_PreparationAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (pp == null)
|
|
throw new ArgumentNullException(nameof(pp));
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Start. Slot={2}",
|
|
Name,
|
|
operation,
|
|
slot);
|
|
|
|
PreadjustmentProcessResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_PreparationAsync(
|
|
slot,
|
|
pp,
|
|
token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentProcessResult
|
|
{
|
|
Success = false,
|
|
Slot = slot,
|
|
ProcessName = "Preparation",
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region ================================== PreAdjustment AMPLITUDE TEST bridge ==================================
|
|
|
|
public async Task<PreadjustmentProcessResult> Preadjustment_AmplitudeTestAsync(
|
|
int slot,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_AmplitudeTestAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (pp == null)
|
|
throw new ArgumentNullException(nameof(pp));
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Start. Slot={2}",
|
|
Name,
|
|
operation,
|
|
slot);
|
|
|
|
PreadjustmentProcessResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_AmplitudeTestAsync(
|
|
slot,
|
|
pp,
|
|
token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentProcessResult
|
|
{
|
|
Success = false,
|
|
Slot = slot,
|
|
ProcessName = "Amplitude Test",
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== PreAdjustment TEMPERATURE CALIBRATION bridge ==================================
|
|
|
|
public async Task<PreadjustmentProcessResult> Preadjustment_TemperatureCalibrationAsync(
|
|
int slot,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_TemperatureCalibrationAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (pp == null)
|
|
throw new ArgumentNullException(nameof(pp));
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Start. Slot={2}",
|
|
Name,
|
|
operation,
|
|
slot);
|
|
|
|
PreadjustmentProcessResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_TemperatureCalibrationAsync(
|
|
slot,
|
|
pp,
|
|
token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentProcessResult
|
|
{
|
|
Success = false,
|
|
Slot = slot,
|
|
ProcessName = "Temperature Calibration",
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region ================================== PreAdjustment OFFSET TEST bridge ==================================
|
|
|
|
public async Task<PreadjustmentProcessResult> Preadjustment_OffsetTestAsync(
|
|
int slot,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_OffsetTestAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (pp == null)
|
|
throw new ArgumentNullException(nameof(pp));
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Start. Slot={2}",
|
|
Name,
|
|
operation,
|
|
slot);
|
|
|
|
PreadjustmentProcessResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_OffsetTestAsync(
|
|
slot,
|
|
pp,
|
|
token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentProcessResult
|
|
{
|
|
Success = false,
|
|
Slot = slot,
|
|
ProcessName = "Offset Test",
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region ================================== PreAdjustment COMPLETION bridge ==================================
|
|
|
|
public async Task<PreadjustmentProcessResult> Preadjustment_CompletionAsync(
|
|
int slot,
|
|
ProcessProgress pp,
|
|
CancellationToken token = default)
|
|
{
|
|
const string operation = nameof(Preadjustment_CompletionAsync);
|
|
|
|
try
|
|
{
|
|
EnsureExternalInterface();
|
|
|
|
if (pp == null)
|
|
throw new ArgumentNullException(nameof(pp));
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Start. Slot={2}",
|
|
Name,
|
|
operation,
|
|
slot);
|
|
|
|
PreadjustmentProcessResult result =
|
|
await gciExternalInterface
|
|
.Preadjustment_CompletionAsync(
|
|
slot,
|
|
pp,
|
|
token)
|
|
.ConfigureAwait(false);
|
|
|
|
log.InfoFormat(
|
|
"{0}: {1} Finish. {2}",
|
|
Name,
|
|
operation,
|
|
result);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error(
|
|
string.Format("{0}: {1} failed.", Name, operation),
|
|
ex);
|
|
|
|
return new PreadjustmentProcessResult
|
|
{
|
|
Success = false,
|
|
Slot = slot,
|
|
ProcessName = "Completion",
|
|
ErrorMessage = ex.Message
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ======================================= Helpers =======================================
|
|
private UDSRPublicModels.DataQuery CreatePasswordQuery(string pcbId)
|
|
{
|
|
var query = new UDSRPublicModels.DataQuery();
|
|
query.QueryParams.Add(pcbId);
|
|
return query;
|
|
}
|
|
|
|
private string ExtractPassword(object data)
|
|
{
|
|
if (data == null)
|
|
return null;
|
|
|
|
if (data is string str)
|
|
return str;
|
|
|
|
if (data is DatabaseSearchResult result)
|
|
{
|
|
if (!result.Found)
|
|
return null;
|
|
|
|
if (result.Values != null &&
|
|
result.Values.TryGetValue("Password", out var value))
|
|
{
|
|
return value?.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// fallback
|
|
return data.ToString();
|
|
}
|
|
#endregion
|
|
|
|
public List<string> GetAllRegisterNames()
|
|
{
|
|
EnsureExternalInterface();
|
|
return gciExternalInterface.GetAllRegisterNames();
|
|
}
|
|
}
|
|
} |