tbf/TBF/Rig/BridgeComponents/GciBridge/GciBridge.cs

1725 lines
62 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 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<bool> Preadjustment_DetectAsync(
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_DetectAsync);
try
{
EnsureExternalInterface();
log.InfoFormat("{0}: {1} Start.", Name, operation);
bool result =
await gciExternalInterface.Preadjustment_DetectAsync(
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#endregion
#region ================================== PreAdjustment PREPARATION bridge ==================================
public async Task<bool> Preadjustment_PreparationAsync(
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.", Name, operation);
bool result =
await gciExternalInterface
.Preadjustment_PreparationAsync(
pp,
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#endregion
#region ================================== PreAdjustment AMPLITUDE TEST bridge ==================================
public async Task<bool> Preadjustment_AmplitudeTestAsync(
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.", Name, operation);
bool result =
await gciExternalInterface
.Preadjustment_AmplitudeTestAsync(
pp,
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#endregion
#region ================================== PreAdjustment TEMPERATURE CALIBRATION bridge ==================================
public async Task<bool> Preadjustment_TemperatureCalibrationAsync(
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.", Name, operation);
bool result =
await gciExternalInterface
.Preadjustment_TemperatureCalibrationAsync(
pp,
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#endregion
#region ================================== PreAdjustment OFFSET TEST bridge ==================================
public async Task<bool> Preadjustment_OffsetTestAsync(
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.", Name, operation);
bool result =
await gciExternalInterface
.Preadjustment_OffsetTestAsync(
pp,
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#endregion
#region ================================== PreAdjustment COMPLETION bridge ==================================
public async Task<bool> Preadjustment_CompletionAsync(
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.", Name, operation);
bool result =
await gciExternalInterface
.Preadjustment_CompletionAsync(
pp,
token)
.ConfigureAwait(false);
log.InfoFormat(
"{0}: {1} Finish. Success={2}",
Name,
operation,
result);
return result;
}
catch (Exception ex)
{
log.Error(
string.Format("{0}: {1} failed.", Name, operation),
ex);
return false;
}
}
#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();
}
}
}