Compare commits

...

3 Commits

36 changed files with 2841 additions and 485 deletions

View File

@ -43,6 +43,7 @@ namespace GenesisCordonelInterface.API
//Preadjustment
public PreAdjustmentSettingsContainer _settings = new PreAdjustmentSettingsContainer();
public ProcessProgress _progressProcess = new ProcessProgress();
public List<MeterStateControl> _meterControls = new List<MeterStateControl>();
public List<MeterStateControl> _tempMeterControls = new List<MeterStateControl>();
@ -174,6 +175,16 @@ namespace GenesisCordonelInterface.API
}
}
private ZeroFlowGenesisMeter GetMeterThreadSafe2(int slot)
{
lock (_meterBatchLock)
{
return _meterBatch.ListOfMeters
.OfType<ZeroFlowGenesisMeter>()
.FirstOrDefault(m => m.Slot == slot);
}
}
private bool RemoveMeterThreadSafe(GenesisMeter meter)
{
if (meter == null)
@ -653,7 +664,7 @@ namespace GenesisCordonelInterface.API
{
const string operation = nameof(CleanAllSlots);
try
try
{
LogInfo(operation, "Start.");
@ -1414,7 +1425,7 @@ namespace GenesisCordonelInterface.API
private FrmCordonelPreadjustmentUI _preadjustmentForm;
private readonly object _formLock = new object();
public event EventHandler PreadjustmentFormClosedByUser;
public event EventHandler PreAdjustmentFormClosedByUser;
/// <summary>
/// Shows singleton instance of preadjustment form.
@ -1456,7 +1467,7 @@ namespace GenesisCordonelInterface.API
/// _bridge.ShowPreadjustmentForm(this);
///
/// </summary>
public void ShowPreadjustmentForm(IWin32Window owner)
public void ShowPreAdjustmentForm(IWin32Window owner)
{
lock (_formLock)
{
@ -1476,7 +1487,7 @@ namespace GenesisCordonelInterface.API
_preadjustmentForm.Hide();
// Notify outside code that the form was closed by user
PreadjustmentFormClosedByUser?.Invoke(
PreAdjustmentFormClosedByUser?.Invoke(
this,
EventArgs.Empty);
}
@ -1524,16 +1535,69 @@ namespace GenesisCordonelInterface.API
// GCI to PreadjustmentUI - AUTOMATIC - STANDALONE - Laatzen GUI
#region ================================== PreAdjustmentUI INIT ==================================
/// <summary>
/// Initializes PreAdjustment environment.
/// </summary>
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp,
List<MeterStateControl> mc)
{
const string operation = nameof(Preadjustment_Initialization);
try
{
LogInfo(operation, "Start.");
_progressProcess = pp;
_meterControls = mc;
var initializedSlots = _meterBatch.ListOfMeters
.Select(m => m.Slot)
.OrderBy(s => s)
.ToList();
var result =
new PreAdjustmentInitializationResult
{
Success = initializedSlots.Any(),
Slots = string.Join(",", initializedSlots),
ErrorMessage = initializedSlots.Any()
? null
: "No initialized meters found."
};
LogInfo(operation, result.ToString());
return result;
}
catch (Exception ex)
{
LogError(operation, ex);
return new PreAdjustmentInitializationResult
{
Success = false,
ErrorMessage = ex.Message
};
}
}
#endregion
#region ================================== PreAdjustmentUI DETECT process ==================================
public Task<PreadjustmentDetectResult> Preadjustment_DetectAsync(
public Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token = default)
{
return Task.Run(() => Preadjustment_DetectCore(token), token);
return Task.Run(() => Preadjustment_DetectCore(selectedSlots, token), token);
}
private PreadjustmentDetectResult Preadjustment_DetectCore(
CancellationToken token)
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token)
{
const string operation = nameof(Preadjustment_DetectCore);
@ -1558,6 +1622,7 @@ namespace GenesisCordonelInterface.API
thermoMeterBatch.RemoveAllMeters();
globalMeterBatch = _meterBatch;
_meterControls = CreateMeterControls(selectedSlots);
if (!_settings.GetTempUseTempFlansh())
_tempMeterControls.Clear();
@ -1610,6 +1675,24 @@ namespace GenesisCordonelInterface.API
}
}
private List<MeterStateControl> CreateMeterControls(IEnumerable<PublicModels.MeterBatchDebugStatus> slots)
{
var controls =
new List<MeterStateControl>();
foreach (var slot in slots)
{
var ctl = new MeterStateControl(slot.Slot);
ctl.SetChecked(true);
ctl.IsEnabled = true;
controls.Add(ctl);
}
return controls;
}
private void CreateZeroFlowMeters(
MeterBatch globalMeterBatch,
MeterBatch thermoMeterBatch,
@ -1629,7 +1712,7 @@ namespace GenesisCordonelInterface.API
if (meterStateCtl.Slot == -1)
continue;
ZeroFlowGenesisMeter currentMeter = new ZeroFlowGenesisMeter(meterStateCtl.Slot, 3, !(meterStateCtl is TempMeterStateControl));
/*ZeroFlowGenesisMeter currentMeter = new ZeroFlowGenesisMeter(meterStateCtl.Slot, 3, !(meterStateCtl is TempMeterStateControl));
var convertedMeter = GetMeterThreadSafe(meterStateCtl.Slot);
if (convertedMeter != null)
@ -1660,7 +1743,9 @@ namespace GenesisCordonelInterface.API
globalMeterBatch.AddMeter2(currentMeter);
}
meterStateCtl.Meter = currentMeter;
meterStateCtl.Meter = currentMeter;*/
meterStateCtl.Meter = GetMeterThreadSafe2(meterStateCtl.Slot);
}
}
@ -1724,24 +1809,22 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== PreAdjustmentUI PREPARATION process ==================================
public Task<PreadjustmentProcessResult> Preadjustment_PreparationAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_PreparationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => Preadjustment_Preparation(slot, pp),
() => PreAdjustment_Preparation(slot),
token);
}
/// <summary>
/// Executes standalone preparation process.
/// </summary>
public PreadjustmentProcessResult Preadjustment_Preparation(
int slot,
ProcessProgress pp)
public PreAdjustmentProcessResult PreAdjustment_Preparation(
int slot)
{
const string operation = nameof(Preadjustment_Preparation);
const string operation = nameof(PreAdjustment_Preparation);
try
{
@ -1750,14 +1833,13 @@ namespace GenesisCordonelInterface.API
var meter = GetMeterThreadSafe(slot);
EnsureConnected(meter);
BaseProcess process =
CreatePreparationProcess(pp);
BaseProcess process = CreatePreparationProcess(_progressProcess);
bool success =
ExecuteProcess(process, pp);
ExecuteProcess(process, _progressProcess);
var result =
new PreadjustmentProcessResult
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@ -1775,7 +1857,7 @@ namespace GenesisCordonelInterface.API
{
LogError(operation, ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1813,21 +1895,19 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== PreAdjustmentUI AMPLITUDE TEST process ==================================
public Task<PreadjustmentProcessResult> Preadjustment_AmplitudeTestAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_AmplitudeTestAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => Preadjustment_AmplitudeTest(slot, pp),
() => PreAdjustment_AmplitudeTest(slot),
token);
}
public PreadjustmentProcessResult Preadjustment_AmplitudeTest(
int slot,
ProcessProgress pp)
public PreAdjustmentProcessResult PreAdjustment_AmplitudeTest(
int slot)
{
const string operation = nameof(Preadjustment_AmplitudeTest);
const string operation = nameof(PreAdjustment_AmplitudeTest);
try
{
@ -1836,9 +1916,9 @@ namespace GenesisCordonelInterface.API
var meter = GetMeterThreadSafe(slot);
EnsureConnected(meter);
if (pp.Setting.TempOnly)
if (_progressProcess.Setting.TempOnly)
{
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = true,
Slot = slot,
@ -1846,13 +1926,13 @@ namespace GenesisCordonelInterface.API
};
}
BaseProcess process = CreateAmplitudeTestProcess(pp);
BaseProcess process = CreateAmplitudeTestProcess(_progressProcess);
bool success = ExecuteProcess(process, pp);
bool success = ExecuteProcess(process, _progressProcess);
LogInfo(operation, $"Finished. Slot={slot}, Success={success}");
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@ -1864,7 +1944,7 @@ namespace GenesisCordonelInterface.API
{
LogError(operation, ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1903,22 +1983,19 @@ namespace GenesisCordonelInterface.API
#region ================================== PreAdjustmentUI TEMPERATURE CALIBRATION process ==================================
public Task<PreadjustmentProcessResult> Preadjustment_TemperatureCalibrationAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => Preadjustment_TemperatureCalibration(slot, pp),
() => PreAdjustment_TemperatureCalibration(slot),
token);
}
public PreadjustmentProcessResult Preadjustment_TemperatureCalibration(
int slot,
ProcessProgress pp)
public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibration(
int slot)
{
const string operation =
nameof(Preadjustment_TemperatureCalibration);
const string operation = nameof(PreAdjustment_TemperatureCalibration);
try
{
@ -1929,14 +2006,12 @@ namespace GenesisCordonelInterface.API
EnsureConnected(meter);
BaseProcess process =
CreateTemperatureCalibrationProcess(pp);
BaseProcess process = CreateTemperatureCalibrationProcess(_progressProcess);
bool success =
ExecuteProcess(process, pp);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new PreadjustmentProcessResult
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@ -1954,7 +2029,7 @@ namespace GenesisCordonelInterface.API
{
LogError(operation, ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1993,35 +2068,31 @@ namespace GenesisCordonelInterface.API
#region ================================== PreAdjustmentUI OFFSET TEST process ==================================
public Task<PreadjustmentProcessResult> Preadjustment_OffsetTestAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
public Task<PreAdjustmentProcessResult> PreAdjustment_OffsetTestAsync(
int slot,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => Preadjustment_OffsetTest(slot, pp),
() => PreAdjustment_OffsetTest(slot),
token);
}
public PreadjustmentProcessResult Preadjustment_OffsetTest(
int slot,
ProcessProgress pp)
public PreAdjustmentProcessResult PreAdjustment_OffsetTest(
int slot)
{
const string operation =
nameof(Preadjustment_OffsetTest);
const string operation = nameof(PreAdjustment_OffsetTest);
try
{
LogInfo(operation, $"Start. Slot={slot}");
var meter =
GetMeterThreadSafe(slot);
var meter = GetMeterThreadSafe(slot);
EnsureConnected(meter);
if (pp.Setting.TempOnly)
if (_progressProcess.Setting.TempOnly)
{
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = true,
Slot = slot,
@ -2029,14 +2100,12 @@ namespace GenesisCordonelInterface.API
};
}
BaseProcess process =
CreateOffsetTestProcess(pp);
BaseProcess process = CreateOffsetTestProcess(_progressProcess);
bool success =
ExecuteProcess(process, pp);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new PreadjustmentProcessResult
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@ -2054,7 +2123,7 @@ namespace GenesisCordonelInterface.API
{
LogError(operation, ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -2093,22 +2162,19 @@ namespace GenesisCordonelInterface.API
#region ================================== PreAdjustmentUI COMPLETION process ==================================
public Task<PreadjustmentProcessResult> Preadjustment_CompletionAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_CompletionAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return GetWorker(slot).RunAsync(
() => Preadjustment_Completion(slot, pp),
() => PreAdjustment_Completion(slot),
token);
}
public PreadjustmentProcessResult Preadjustment_Completion(
int slot,
ProcessProgress pp)
public PreAdjustmentProcessResult PreAdjustment_Completion(
int slot)
{
const string operation =
nameof(Preadjustment_Completion);
const string operation = nameof(PreAdjustment_Completion);
try
{
@ -2119,14 +2185,12 @@ namespace GenesisCordonelInterface.API
EnsureConnected(meter);
BaseProcess process =
CreateCompletionProcess(pp);
BaseProcess process = CreateCompletionProcess(_progressProcess);
bool success =
ExecuteProcess(process, pp);
bool success = ExecuteProcess(process, _progressProcess);
var result =
new PreadjustmentProcessResult
new PreAdjustmentProcessResult
{
Success = success,
Slot = slot,
@ -2144,7 +2208,7 @@ namespace GenesisCordonelInterface.API
{
LogError(operation, ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,

View File

@ -12,21 +12,39 @@ using static GenesisCordonelInterface.API.PublicModels;
namespace GenesisCordonelInterface.API
{
/// <summary>
/// Outside-facing facade for Genesis Cordonel Interface.
/// Exposes only selected operations intended for external callers.
/// Public-facing facade for external applications integrating with
/// Genesis Cordonel Interface.
/// </summary>
/// <remarks>
/// This class exposes a simplified and controlled API for external callers.
/// It validates public input, maps public request models to internal models,
/// forwards operations to the internal GCI implementation and exposes status
/// notifications for meter batch changes.
///
/// This layer should stay thin. Business logic and meter communication are
/// handled by <see cref="InterfaceGCIToLaatzen"/>.
/// </remarks>
public class InterfaceOutsideToGCI
{
/// <summary>
/// Internal GCI implementation used by this public facade.
/// </summary>
public readonly InterfaceGCIToLaatzen _innerMeterAPI;
/// <summary>
/// Occurs when meter batch status information changes.
/// </summary>
public event Action<List<MeterBatchDebugStatus>> MeterBatchStatusChanged;
/// <summary>
/// Initializes a new instance of the public GCI facade.
/// </summary>
public InterfaceOutsideToGCI()
{
_innerMeterAPI = new InterfaceGCIToLaatzen();
}
//
// Laatzen ToolBox actions
#region ================================== PORT DETECTION ==================================
@ -66,6 +84,20 @@ namespace GenesisCordonelInterface.API
#region ================================== INIT/UPDATE/GET slot ==================================
/// <summary>
/// Initializes a meter slot using the provided slot configuration.
/// </summary>
/// <param name="request">
/// Slot initialization request containing slot id, configuration source,
/// password source, request port and streaming port.
/// </param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Result describing whether the slot was created, updated, already existed or failed.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="request"/> is null.
/// </exception>
public async Task<GciInitSlotResult> InitSlotAsync(
GciInitSlotRequest request,
CancellationToken token = default)
@ -86,6 +118,20 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Updates configuration of an existing meter slot.
/// </summary>
/// <param name="request">
/// Slot configuration request containing updated configuration source,
/// password source and port settings.
/// </param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Result describing whether the slot update succeeded or failed.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="request"/> is null.
/// </exception>
public async Task<GciInitSlotResult> UpdateSlotAsync(
GciInitSlotRequest request,
CancellationToken token = default)
@ -106,6 +152,18 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Gets information about one meter slot.
/// </summary>
/// <param name="slotId">Slot id to query. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Slot information including existence, connection state, login state,
/// PCB id and configured communication ports.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slotId"/> is invalid.
/// </exception>
public async Task<GciSlotInfo> GetSlotAsync(
int slotId,
CancellationToken token = default)
@ -118,6 +176,13 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Gets information about all currently initialized meter slots.
/// </summary>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Collection of slot information records for all known meters.
/// </returns>
public async Task<GciAllSlotsInfo> GetAllSlotsAsync(
CancellationToken token = default)
{
@ -126,6 +191,17 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Cleans one meter slot and releases its runtime resources.
/// </summary>
/// <param name="slot">Slot id to clean. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Result describing whether the slot cleanup succeeded or failed.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slot"/> is invalid.
/// </exception>
public async Task<GciCleanSlotResult> CleanSlotAsync(
int slot,
CancellationToken token = default)
@ -140,6 +216,13 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Cleans all initialized meter slots and releases related runtime resources.
/// </summary>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Result describing whether cleanup of all slots succeeded or failed.
/// </returns>
public async Task<GciCleanAllSlotsResult> CleanAllSlotsAsync(
CancellationToken token = default)
{
@ -152,6 +235,17 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== PASSWORD ==================================
/// <summary>
/// Sets runtime password for the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="password">Password to assign to the meter.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>Result containing password update status.</returns>
/// <exception cref="ArgumentException">
/// Thrown when slot id is invalid or password is empty.
/// </exception>
public async Task<GciSetPasswordResult> SetPasswordAsync(
int slot,
string password,
@ -173,6 +267,16 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== LOGIN ==================================
/// <summary>
/// Logs in to the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>Login result containing login state and status message.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slot"/> is invalid.
/// </exception>
public async Task<PublicModels.GciLoginResult> LoginOneSlotAsync(
int slot,
CancellationToken token = default)
@ -189,6 +293,15 @@ namespace GenesisCordonelInterface.API
#region ================================== CONNECTION ==================================
/// <summary>
/// Connects the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>Connection result containing connection state and status message.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slot"/> is invalid.
/// </exception>
public async Task<GciConnectResult> ConnectOneSlotAsync(
int slot,
CancellationToken token = default)
@ -203,6 +316,15 @@ namespace GenesisCordonelInterface.API
return result;
}
/// <summary>
/// Disconnects the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>Disconnect result containing final connection state and status message.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slot"/> is invalid.
/// </exception>
public async Task<GciDisconnectResult> DisconnectAsync(
int slot,
CancellationToken token = default)
@ -219,6 +341,16 @@ namespace GenesisCordonelInterface.API
#endregion
#region ================================== PCB ==================================
/// <summary>
/// Reads PCB identifier from the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>Result containing PCB id and read status.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="slot"/> is invalid.
/// </exception>
public async Task<GciGetPcbIdResult> GetPcbIdAsync(
int slot,
CancellationToken token = default)
@ -234,6 +366,18 @@ namespace GenesisCordonelInterface.API
#region ================================== READ ==================================
/// <summary>
/// Reads a firmware register value from the meter assigned to the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="registerName">Register identifier to read.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Register read result containing raw register value and operation status.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when slot id or register name is invalid.
/// </exception>
public async Task<RegisterReadResult> ReadRegisterAsync(
int slot,
string registerName,
@ -258,6 +402,25 @@ namespace GenesisCordonelInterface.API
#region ================================== WRITE ==================================
/// <summary>
/// Writes a value to a firmware register.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="registerName">Register identifier to write.</param>
/// <param name="value">Value to write.</param>
/// <param name="storeToDevice">
/// Indicates whether configuration should be permanently stored.
/// </param>
/// <param name="refreshSystemState">
/// Indicates whether firmware system state should be refreshed after write.
/// </param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Register write result describing write status.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when slot id or register name is invalid.
/// </exception>
public async Task<RegisterWriteResult> WriteRegisterAsync(
int slot,
string registerName,
@ -290,6 +453,18 @@ namespace GenesisCordonelInterface.API
#region ================================== Password ==================================
/// <summary>
/// Updates meter password for the specified slot.
/// </summary>
/// <param name="slot">Slot id. Must be greater than zero.</param>
/// <param name="password">New password.</param>
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
/// <returns>
/// Password update result.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when slot id or password is invalid.
/// </exception>
public async Task<GciSetPasswordResult> SetMeterPasswordAsync(
int slot,
string password,
@ -313,16 +488,36 @@ namespace GenesisCordonelInterface.API
#region ================================== DEBUG STATUS ==================================
/// <summary>
/// Gets runtime diagnostic information for all active workers.
/// </summary>
/// <returns>
/// Collection containing worker state, queue information,
/// current operation and activity timestamps.
/// </returns>
public List<WorkerDebugStatus> GetWorkerDebugStatuses()
{
return _innerMeterAPI.GetWorkerDebugStatuses();
}
/// <summary>
/// Gets runtime diagnostic information for all meter slots.
/// </summary>
/// <returns>
/// Collection containing slot state, connection state,
/// selected state and communication configuration.
/// </returns>
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
{
return _innerMeterAPI.GetMeterBatchDebugStatuses();
}
/// <summary>
/// Raises meter batch status change notification.
/// </summary>
/// <remarks>
/// Intended to notify external consumers after changes in meter state.
/// </remarks>
public void RaiseMeterBatchStatusChanged()
{
var statuses = GetMeterBatchDebugStatuses();
@ -332,121 +527,54 @@ namespace GenesisCordonelInterface.API
handler(statuses);
}
// ----------------------------------------------------
#endregion
#region ================================== SLOT SELECTION ==================================
// ----------------------------------------------------
/// <summary>
/// Sets selection state for a slot.
/// </summary>
/// <param name="slot">Slot id.</param>
/// <param name="selected">Selection state.</param>
public void SetSlotSelected(int slot, bool selected)
{
_innerMeterAPI.SetSlotSelected(slot, selected);
RaiseMeterBatchStatusChanged();
}
/// <summary>
/// Determines whether the specified slot is selected.
/// </summary>
/// <param name="slot">Slot id.</param>
/// <returns>
/// True if slot is selected; otherwise false.
/// </returns>
public bool IsSlotSelected(int slot)
{
return _innerMeterAPI.IsSlotSelected(slot);
}
/// <summary>
/// Gets all selected slot identifiers.
/// </summary>
/// <returns>
/// Ordered collection of selected slot ids.
/// </returns>
public List<int> GetSelectedSlots()
{
return _innerMeterAPI.GetSelectedSlots();
}
// ----------------------------------------------------
#endregion
#region ================================== SLOT PORT CONFIG ==================================
// ----------------------------------------------------
/*public void SetSlotRequestPort(int slot, string portName)
{
lock (_portLock)
{
if (string.IsNullOrWhiteSpace(portName))
{
_requestPorts.Remove(slot);
}
else
{
_requestPorts[slot] = new GciPortConfig
{
PortName = portName,
Type = "Serial"
};
}
}
RaiseMeterBatchStatusChanged();
}
public void SetSlotStreamingPort(int slot, string portName)
{
lock (_portLock)
{
if (string.IsNullOrWhiteSpace(portName))
{
_streamingPorts.Remove(slot);
}
else
{
_streamingPorts[slot] = new GciPortConfig
{
PortName = portName,
Type = "Serial"
};
}
}
RaiseMeterBatchStatusChanged();
}
public GciPortConfig? GetSlotRequestPort(int slot)
{
lock (_portLock)
{
GciPortConfig port;
if (_requestPorts.TryGetValue(slot, out port))
return port;
return null;
}
}
public GciPortConfig? GetSlotStreamingPort(int slot)
{
lock (_portLock)
{
GciPortConfig port;
if (_streamingPorts.TryGetValue(slot, out port))
return port;
return null;
}
}*/
// ----------------------------------------------------
#endregion
#region ================================== METER BATCH SETUP ==================================
public void ReloadSlotSetup()
{
_innerMeterAPI.ReloadSlotSetup();
RaiseMeterBatchStatusChanged();
}
public void SaveSlotSetup(List<MeterBatchDebugStatus> data)
{
_innerMeterAPI.SaveSlotSetup(data);
RaiseMeterBatchStatusChanged();
}
#endregion
#region ================================== Register names ==================================
/// <summary>
/// Gets all available firmware register identifiers.
/// </summary>
/// <returns>
/// Ordered collection of register names.
/// </returns>
public List<string> GetAllRegisterNames()
{
return _innerMeterAPI.GetAllRegisterNames();
@ -454,52 +582,58 @@ namespace GenesisCordonelInterface.API
#endregion
// Preadjustment
// Laatzen Preadjustment processes
public Task<PreadjustmentDetectResult> Preadjustment_DetectAsync(
CancellationToken token = default)
{
return _innerMeterAPI.Preadjustment_DetectAsync(token);
}
public Task<PreadjustmentProcessResult> Preadjustment_PreparationAsync(
int slot,
#region ================================== PreAdjustment ==================================
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp,
CancellationToken token = default)
List<MeterStateControl> mc)
{
return _innerMeterAPI.Preadjustment_PreparationAsync(slot, pp, token);
return _innerMeterAPI.Preadjustment_Initialization(pp, mc);
}
public Task<PreadjustmentProcessResult> Preadjustment_AmplitudeTestAsync(
int slot,
ProcessProgress pp,
public Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token = default)
{
return _innerMeterAPI.Preadjustment_AmplitudeTestAsync(slot, pp, token);
return _innerMeterAPI.PreAdjustment_DetectAsync(selectedSlots, token);
}
public Task<PreadjustmentProcessResult> Preadjustment_TemperatureCalibrationAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_PreparationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return _innerMeterAPI.Preadjustment_TemperatureCalibrationAsync(slot, pp, token);
return _innerMeterAPI.PreAdjustment_PreparationAsync(slot, token);
}
public Task<PreadjustmentProcessResult> Preadjustment_OffsetTestAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_AmplitudeTestAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return _innerMeterAPI.Preadjustment_OffsetTestAsync(slot, pp, token);
return _innerMeterAPI.PreAdjustment_AmplitudeTestAsync(slot, token);
}
public Task<PreadjustmentProcessResult> Preadjustment_CompletionAsync(
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
return _innerMeterAPI.Preadjustment_CompletionAsync(slot, pp, token);
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationAsync(slot, token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_OffsetTestAsync(
int slot,
CancellationToken token = default)
{
return _innerMeterAPI.PreAdjustment_OffsetTestAsync(slot, token);
}
public Task<PreAdjustmentProcessResult> PreAdjustment_CompletionAsync(
int slot,
CancellationToken token = default)
{
return _innerMeterAPI.PreAdjustment_CompletionAsync(slot, token);
}
#endregion
}
}

View File

@ -448,7 +448,7 @@ namespace GenesisCordonelInterface.API
}
}
public class PreadjustmentProcessResult
public class PreAdjustmentProcessResult
{
public bool Success { get; set; }
public int Slot { get; set; }
@ -483,6 +483,22 @@ namespace GenesisCordonelInterface.API
$"Error={ErrorMessage ?? "None"}";
}
}
public class PreAdjustmentInitializationResult
{
public bool Success { get; set; }
public string Slots { get; set; }
public string ErrorMessage { get; set; }
/// <inheritdoc />
public override string ToString()
{
return
$"Success={Success}, " +
$"Slots={Slots}, " +
$"Error={ErrorMessage ?? "None"}";
}
}
#endregion
}
}

View File

@ -0,0 +1,35 @@
{
"_Comment": "GCI DataStorage configuration",
"DataStorage": {
"MeterLoginPasswords": {
"___Documentation___": {
"Description": "Reads login passwords for meters by PCB ID",
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
"DataSource": "Database connection string",
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [PcbId]=QUERYPARAM -> PCB ID provided during GetPasswordAsync()."
},
"Name": "MeterLoginPasswords",
"Type": "LocalDatabase",
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"QueryTemplate": "SELECT [Password] FROM [dbo].[SkeletonKeys] WHERE [PcbId] = QUERYPARAM"
},
"PreAdjustmentCalibrationParams": {
"___Documentation___": {
"Description": "Reads pre-adjustment values from CSV",
"DataSource": "Relative path resolved from application directory",
"QueryTemplate": "CSV/JSON syntax: SELECT [ReturnColumn] WHERE [MatchColumn]=QUERYPARAM or SELECT COLUMN(1) WHERE COLUMN(0)=QUERYPARAM"
},
"Name": "PreAdjustmentCalibrationParams",
"Type": "LocalCsv",
"DataSource": "Data\\preadjustment_params.csv",
"QueryTemplate": "SELECT [Offset] WHERE [PcbId] = QUERYPARAM"
}
}
}

View File

@ -0,0 +1,32 @@
namespace GenesisCordonelInterface.Core.DataStorage.Config
{
/// <summary>
/// Root GCI configuration object loaded from gci_config.json.
///
/// This class represents the top-level configuration structure
/// and serves as an entry point for all configurable GCI features.
///
/// Example:
///
/// {
/// "DataStorage":
/// {
/// ...
/// }
/// }
/// </summary>
public class GciConfig
{
/// <summary>
/// Data storage configuration section.
///
/// Contains definitions for all configured readers and writers,
/// such as:
///
/// - MeterLoginPasswords
/// - PreAdjustmentCalibrationParams
/// - future storage providers
/// </summary>
public GciDataStorageConfig DataStorage { get; set; }
}
}

View File

@ -0,0 +1,177 @@
using System;
using System.IO;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using Newtonsoft.Json;
namespace GenesisCordonelInterface.Core.DataStorage.Config
{
/// <summary>
/// Loads GCI configuration from gci_config.json.
///
/// Responsibilities:
/// - Locate configuration file
/// - Deserialize JSON into strongly typed objects
/// - Normalize relative file paths
/// - Preserve database connection strings and REST URLs
///
/// Example:
///
/// Config/
/// gci_config.json
///
/// Data/
/// meter_passwords.csv
///
/// Relative paths:
/// Data\file.csv
///
/// become:
///
/// C:\App\bin\Debug\Data\file.csv
///
/// Database sources remain unchanged:
///
/// Server=(localdb)\MojaDB;Database=...
/// </summary>
public static class GciConfigLoader
{
/// <summary>
/// Loads default GCI configuration from:
///
/// Config\gci_config.json
/// </summary>
/// <returns>
/// Loaded GCI configuration.
/// </returns>
public static GciConfig LoadDefault()
{
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
string configPath = Path.Combine(
baseDirectory,
"Config",
"gci_config.json");
return Load(configPath, baseDirectory);
}
/// <summary>
/// Loads GCI configuration from specified file.
/// </summary>
/// <param name="configPath">
/// Path to configuration json file.
/// </param>
/// <param name="baseDirectory">
/// Base directory used for resolving relative paths.
/// </param>
/// <returns>
/// Loaded configuration object.
/// </returns>
public static GciConfig Load(
string configPath,
string baseDirectory = null)
{
if (string.IsNullOrWhiteSpace(configPath))
throw new ArgumentException(
"Config path must not be empty.",
nameof(configPath));
if (!File.Exists(configPath))
throw new FileNotFoundException(
"GCI config file was not found.",
configPath);
string json = File.ReadAllText(configPath);
GciConfig config =
JsonConvert.DeserializeObject<GciConfig>(json);
if (config == null)
throw new InvalidOperationException(
"GCI config could not be loaded.");
NormalizeDataStoragePaths(
config,
baseDirectory ?? Path.GetDirectoryName(configPath));
return config;
}
/// <summary>
/// Normalizes paths for all configured data storage entries.
///
/// Converts relative file paths into absolute paths.
/// Database connection strings remain untouched.
/// </summary>
/// <param name="config">
/// Loaded configuration object.
/// </param>
/// <param name="baseDirectory">
/// Base path used for relative resolution.
/// </param>
private static void NormalizeDataStoragePaths(
GciConfig config,
string baseDirectory)
{
if (config.DataStorage == null)
return;
NormalizePath(
config.DataStorage.MeterLoginPasswords,
baseDirectory);
NormalizePath(
config.DataStorage.PreAdjustmentCalibrationParams,
baseDirectory);
}
/// <summary>
/// Converts relative file paths into absolute paths.
///
/// Applies only to:
/// - LocalCsv
/// - RemoteCsv
/// - LocalJson
/// - RemoteJson
///
/// Does not modify:
/// - Database connection strings
/// - REST URLs
/// </summary>
/// <param name="storageConfig">
/// Storage configuration.
/// </param>
/// <param name="baseDirectory">
/// Base path for resolution.
/// </param>
private static void NormalizePath(
DataStorageConfig storageConfig,
string baseDirectory)
{
if (storageConfig == null)
return;
if (string.IsNullOrWhiteSpace(storageConfig.DataSource))
return;
// Database connection strings and URLs
// must never be treated as file paths.
if (storageConfig.Type == DataStorageType.LocalDatabase ||
storageConfig.Type == DataStorageType.RemoteDatabase ||
storageConfig.Type == DataStorageType.RestApi)
{
return;
}
if (Path.IsPathRooted(storageConfig.DataSource))
return;
storageConfig.DataSource =
Path.GetFullPath(
Path.Combine(
baseDirectory,
storageConfig.DataSource));
}
}
}

View File

@ -0,0 +1,30 @@
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
namespace GenesisCordonelInterface.Core.DataStorage.Config
{
/// <summary>
/// Contains all configured GCI data storage sources.
///
/// Each property represents one logical use-case
/// and points to its storage configuration.
///
/// Example:
/// MeterLoginPasswords
/// -> SQL database
///
/// PreAdjustmentCalibrationParams
/// -> CSV file
/// </summary>
public class GciDataStorageConfig
{
/// <summary>
/// Configuration for meter login password lookup.
/// </summary>
public DataStorageConfig MeterLoginPasswords { get; set; }
/// <summary>
/// Configuration for pre-adjustment calibration data lookup.
/// </summary>
public DataStorageConfig PreAdjustmentCalibrationParams { get; set; }
}
}

View File

@ -0,0 +1,49 @@
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts
{
/// <summary>
/// Defines common contract for all GCI data storage readers.
///
/// Implementations can read data from different storage types:
/// - SQL database
/// - CSV file
/// - JSON file
/// - REST API
///
/// Consumers should use this interface instead of concrete readers.
/// </summary>
public interface IDataStorageReader
{
/// <summary>
/// Reads data from configured storage using provided query.
/// </summary>
/// <param name="query">
/// Query object containing lookup parameters.
/// </param>
/// <returns>
/// Storage-specific result object.
/// </returns>
object GetData(Models.DataQuery query);
/// <summary>
/// Tests whether configured data source is accessible.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result of source test.
/// </returns>
Models.ReaderDiagnosticResult TestSource(bool enableDiagnostics);
/// <summary>
/// Tests whether configured query is valid for the data source.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result of query test.
/// </returns>
Models.ReaderDiagnosticResult TestQuery(bool enableDiagnostics);
}
}

View File

@ -0,0 +1,94 @@
using System;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common
{
/// <summary>
/// Factory responsible for creating appropriate
/// data storage reader implementations.
///
/// The reader type is selected according to:
///
/// DataStorageConfig.Type
///
/// Example:
///
/// LocalDatabase
/// -> DatabaseDataStorageReader
///
/// LocalCsv
/// -> CsvDataStorageReader
///
/// LocalJson
/// -> JsonDataStorageReader
///
/// RestApi
/// -> RestApiDataStorageReader
///
/// This factory hides implementation details from
/// higher layers and keeps consumers independent
/// of storage technology.
///
/// Trace:
///
/// MeterLoginPasswordReader
/// -> DataStorageReaderFactory.Create()
/// -> DatabaseDataStorageReader
/// -> CsvDataStorageReader
/// -> JsonDataStorageReader
/// -> RestApiDataStorageReader
/// </summary>
public static class DataStorageReaderFactory
{
/// <summary>
/// Creates appropriate reader implementation
/// according to storage configuration.
/// </summary>
/// <param name="config">
/// Data storage configuration loaded from gci_config.json.
/// </param>
/// <returns>
/// Configured storage reader implementation.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Configuration is null.
/// </exception>
/// <exception cref="NotSupportedException">
/// Unsupported storage type.
/// </exception>
public static IDataStorageReader Create(
DataStorageConfig config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
switch (config.Type)
{
case DataStorageType.RemoteDatabase:
case DataStorageType.LocalDatabase:
return new Providers.DatabaseDataStorageReader(config);
case DataStorageType.RemoteCsv:
case DataStorageType.LocalCsv:
return new Providers.CsvDataStorageReader(config);
case DataStorageType.RemoteJson:
case DataStorageType.LocalJson:
return new Providers.JsonDataStorageReader(config);
case DataStorageType.RestApi:
return new Providers.RestApiDataStorageReader(config);
default:
throw new NotSupportedException(
$"Unsupported DataStorageType: '{config.Type}'");
}
}
}
}

View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
/// <summary>
/// Represents query input for data storage readers.
///
/// Query parameters are used as lookup values
/// during storage search operations.
///
/// A single parameter:
///
/// 231630279
///
/// executes one lookup.
///
/// Multiple parameters:
///
/// 231630279
/// 231630243
/// 231630148
///
/// can execute batch operations.
///
/// Example:
///
/// QueryTemplate:
///
/// SELECT [Password]
/// WHERE [PcbId]=QUERYPARAM
///
/// Query:
///
/// QueryParams:
/// 231630279
///
/// Result:
///
/// Password belonging to 231630279.
/// </summary>
public class DataQuery
{
/// <summary>
/// Collection of query values.
///
/// Single item:
///
/// QueryParams[0]
///
/// represents one lookup.
///
/// Multiple items may be processed
/// as batch requests.
/// </summary>
public List<string> QueryParams { get; }
= new List<string>();
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
/// <summary>
/// Configuration for one storage source.
/// Loaded from gci_config.json
/// </summary>
public class DataStorageConfig
{
/// <summary>
/// Logical storage name.
/// Example:
/// MeterLoginPasswords
/// CalibrationParameters
/// </summary>
public string Name { get; set; }
/// <summary>
/// Storage implementation type.
/// </summary>
public DataStorageType Type { get; set; }
/// <summary>
/// Connection string,
/// file path,
/// URL etc.
/// </summary>
public string DataSource { get; set; }
/// <summary>
/// Query template.
/// Example:
///
/// SQL:
/// SELECT Password
/// FROM Passwords
/// WHERE PcbId=QUERYPARAM
///
/// CSV:
/// SELECT [Password]
/// WHERE [PcbId]=QUERYPARAM
/// </summary>
public string QueryTemplate { get; set; }
}
}

View File

@ -0,0 +1,79 @@
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
/// <summary>
/// Identifies supported data storage implementations.
///
/// The type determines which reader implementation
/// will be created by DataStorageReaderFactory.
///
/// Example:
///
/// LocalDatabase
/// -> DatabaseDataStorageReader
///
/// LocalCsv
/// -> CsvDataStorageReader
///
/// RestApi
/// -> RestApiDataStorageReader
///
/// Source:
///
/// gci_config.json
///
/// Example:
///
/// "Type":"LocalDatabase"
/// </summary>
public enum DataStorageType
{
/// <summary>
/// REST API endpoint.
///
/// Example:
///
/// https://api.company.com/passwords
/// </summary>
RestApi,
/// <summary>
/// Database located on remote server.
///
/// Example:
///
/// SQL server on network machine.
/// </summary>
RemoteDatabase,
/// <summary>
/// Database located on local machine.
///
/// Example:
///
/// SQL LocalDB
/// SQLite
/// local SQL Server instance
/// </summary>
LocalDatabase,
/// <summary>
/// JSON source stored remotely.
/// </summary>
RemoteJson,
/// <summary>
/// JSON file stored locally.
/// </summary>
LocalJson,
/// <summary>
/// CSV source stored remotely.
/// </summary>
RemoteCsv,
/// <summary>
/// CSV file stored locally.
/// </summary>
LocalCsv
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
public class DatabaseSearchResult
{
public bool Found { get; set; }
public string Query { get; set; }
public Dictionary<string, object> Values { get; set; }
public DatabaseSearchResult()
{
Values = new Dictionary<string, object>();
}
}
}

View File

@ -0,0 +1,141 @@
using System.Collections.Generic;
using System.Text;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
/// <summary>
/// Represents diagnostic result of data storage operations.
///
/// Used for:
/// - source validation
/// - query validation
/// - connection tests
/// - detailed diagnostic logging
///
/// Example:
///
/// TestSource()
/// -> connection successful
///
/// TestQuery()
/// -> column validation
///
/// Can also carry additional payload data.
/// </summary>
public class ReaderDiagnosticResult
{
/// <summary>
/// Indicates whether operation completed successfully.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Human readable summary message.
///
/// Example:
///
/// "Connection successful."
///
/// or:
///
/// "Column not found."
/// </summary>
public string Message { get; set; }
/// <summary>
/// Detailed diagnostic output lines.
///
/// Used for debugging and troubleshooting.
/// </summary>
public List<string> Diagnostics { get; set; }
/// <summary>
/// Optional operation payload.
///
/// Example:
///
/// loaded CSV lines
/// SQL test value
/// parsed content
/// </summary>
public object Data { get; set; }
/// <summary>
/// Creates empty diagnostic result.
/// </summary>
public ReaderDiagnosticResult()
{
Diagnostics = new List<string>();
}
/// <summary>
/// Creates successful diagnostic result.
/// </summary>
/// <param name="message">
/// Success message.
/// </param>
/// <returns>
/// Successful result object.
/// </returns>
public static ReaderDiagnosticResult SuccessResult(
string message = "OK")
{
return new ReaderDiagnosticResult
{
Success = true,
Message = message
};
}
/// <summary>
/// Creates failed diagnostic result.
/// </summary>
/// <param name="message">
/// Failure message.
/// </param>
/// <returns>
/// Failed result object.
/// </returns>
public static ReaderDiagnosticResult Failure(
string message)
{
return new ReaderDiagnosticResult
{
Success = false,
Message = message
};
}
/// <summary>
/// Converts diagnostic information into display text.
///
/// Output contains:
///
/// diagnostic lines
/// +
/// summary message
/// </summary>
/// <returns>
/// Formatted text representation.
/// </returns>
public string ToDisplayDiag()
{
StringBuilder sb = new StringBuilder();
if (Diagnostics != null &&
Diagnostics.Count > 0)
{
foreach (string line in Diagnostics)
sb.AppendLine(line);
if (!string.IsNullOrWhiteSpace(Message))
sb.AppendLine();
}
if (!string.IsNullOrWhiteSpace(Message))
sb.AppendLine(Message);
return sb.ToString();
}
}
}

View File

@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
using System.IO;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Searching;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
{
/// <summary>
/// Reads and searches CSV based data sources.
///
/// Supported query syntax:
/// SELECT [ReturnColumn] WHERE [MatchColumn] = QUERYPARAM
///
/// Example:
/// SELECT [Password] WHERE [PcbId] = QUERYPARAM
///
/// Supports:
/// - column names: [ColumnName]
/// - column indexes: COLUMN(n)
///
/// Trace:
/// MeterLoginPasswordReader
/// -> CsvDataStorageReader.GetData()
/// -> ConnectToSource()
/// -> ExecuteQuery()
/// </summary>
public class CsvDataStorageReader : IDataStorageReader
{
private readonly DataStorageConfig config;
private string resolvedPath;
private string[] loadedLines;
private string[] loadedHeaders;
/// <summary>
/// Creates CSV data storage reader using provided configuration.
/// </summary>
/// <param name="config">
/// Data storage configuration loaded from gci_config.json.
/// </param>
public CsvDataStorageReader(DataStorageConfig config)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
}
/// <summary>
/// Reads values from CSV using configured QueryTemplate.
/// </summary>
/// <param name="query">
/// Query parameters used for lookup.
/// </param>
/// <returns>
/// ReaderDiagnosticResult containing lookup result.
/// </returns>
public object GetData(DataQuery query)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
ReaderDiagnosticResult sourceResult = ConnectToSource(true);
if (!sourceResult.Success)
throw new InvalidOperationException(sourceResult.Message);
ReaderDiagnosticResult queryResult = ExecuteQuery(query, true);
if (!queryResult.Success)
throw new InvalidOperationException(queryResult.Message);
return queryResult;
}
/// <summary>
/// Validates CSV source accessibility and content.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result.
/// </returns>
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
{
return ConnectToSource(enableDiagnostics);
}
/// <summary>
/// Validates configured QueryTemplate against CSV header.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result.
/// </returns>
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
{
ReaderDiagnosticResult sourceResult = ConnectToSource(enableDiagnostics);
if (!sourceResult.Success)
return sourceResult;
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
try
{
Log(result, enableDiagnostics, "Starting QueryTemplate validation.");
SearchOrderDefinition definition =
SearchOrderParser.Parse(config.QueryTemplate);
int selectIndex = ResolveColumnIndex(
loadedHeaders,
definition.SelectColumn);
int whereIndex = ResolveColumnIndex(
loadedHeaders,
definition.WhereColumn);
Log(result, enableDiagnostics, "QueryTemplate parsed successfully.");
Log(result, enableDiagnostics, "Select column index: " + selectIndex);
Log(result, enableDiagnostics, "Where column index: " + whereIndex);
result.Success = true;
result.Message = "QueryTemplate validation finished successfully.";
}
catch (Exception ex)
{
result.Success = false;
result.Message = ex.Message;
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
}
return result;
}
/// <summary>
/// Opens CSV file and loads its content.
/// Validates file existence and header.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Source validation result.
/// </returns>
private ReaderDiagnosticResult ConnectToSource(bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
try
{
Log(result, enableDiagnostics, "Starting CSV source connection test.");
if (string.IsNullOrWhiteSpace(config.DataSource))
throw new InvalidOperationException("CSV data source is empty.");
resolvedPath = Path.GetFullPath(config.DataSource);
Log(result, enableDiagnostics, "Resolved full path: " + resolvedPath);
if (!File.Exists(resolvedPath))
throw new FileNotFoundException("CSV file was not found.", resolvedPath);
loadedLines = File.ReadAllLines(resolvedPath);
if (loadedLines == null || loadedLines.Length == 0)
throw new InvalidOperationException("CSV file is empty.");
loadedHeaders = SplitCsvLine(loadedLines[0]);
if (loadedHeaders == null || loadedHeaders.Length == 0)
throw new InvalidOperationException("CSV header is empty.");
Log(result, enableDiagnostics, "CSV line count: " + loadedLines.Length);
Log(result, enableDiagnostics, "CSV header column count: " + loadedHeaders.Length);
result.Success = true;
result.Message = "CSV source connection finished successfully.";
result.Data = loadedLines;
}
catch (Exception ex)
{
result.Success = false;
result.Message = ex.Message;
result.Data = null;
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
}
return result;
}
/// <summary>
/// Executes configured QueryTemplate against loaded CSV data.
/// </summary>
/// <param name="query">
/// Query values used for matching.
/// </param>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Query result.
/// </returns>
private ReaderDiagnosticResult ExecuteQuery(
DataQuery query,
bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
try
{
if (query.QueryParams == null || query.QueryParams.Count == 0)
throw new InvalidOperationException("DataQuery.QueryParams is empty.");
SearchOrderDefinition definition =
SearchOrderParser.Parse(config.QueryTemplate);
int selectIndex = ResolveColumnIndex(
loadedHeaders,
definition.SelectColumn);
int whereIndex = ResolveColumnIndex(
loadedHeaders,
definition.WhereColumn);
List<string> matchedValues = new List<string>();
foreach (string param in query.QueryParams)
{
string queryValue = (param ?? string.Empty).Trim();
bool found = false;
for (int lineIndex = 1; lineIndex < loadedLines.Length; lineIndex++)
{
if (string.IsNullOrWhiteSpace(loadedLines[lineIndex]))
continue;
string[] values = SplitCsvLine(loadedLines[lineIndex]);
if (whereIndex >= values.Length)
continue;
string currentValue = (values[whereIndex] ?? string.Empty).Trim();
if (!string.Equals(
currentValue,
queryValue,
StringComparison.OrdinalIgnoreCase))
{
continue;
}
string returnValue =
selectIndex < values.Length
? values[selectIndex]
: string.Empty;
matchedValues.Add(returnValue);
found = true;
break;
}
if (!found)
Log(result, enableDiagnostics, "No match found for: " + queryValue);
}
result.Success = true;
if (query.QueryParams.Count == 1)
{
result.Data = matchedValues.Count > 0 ? matchedValues[0] : null;
result.Message = matchedValues.Count > 0
? "Value found."
: "No match found.";
}
else
{
result.Data = matchedValues;
result.Message =
"Batch query finished. Matches found: " +
matchedValues.Count +
" of " +
query.QueryParams.Count +
".";
}
}
catch (Exception ex)
{
result.Success = false;
result.Message = ex.Message;
result.Data = null;
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
}
return result;
}
/// <summary>
/// Resolves column index either by name or by explicit index.
/// </summary>
/// <param name="headers">
/// CSV header columns.
/// </param>
/// <param name="columnReference">
/// Parsed QueryTemplate column definition.
/// </param>
/// <returns>
/// Zero-based column index.
/// </returns>
private int ResolveColumnIndex(
string[] headers,
ColumnReference columnReference)
{
if (columnReference == null)
throw new InvalidOperationException("Column reference is null.");
if (columnReference.HasIndex)
{
int index = columnReference.Index.Value;
if (index < 0 || index >= headers.Length)
throw new InvalidOperationException("Column index is out of range.");
return index;
}
if (columnReference.HasName)
{
for (int i = 0; i < headers.Length; i++)
{
if (string.Equals(
headers[i]?.Trim(),
columnReference.Name?.Trim(),
StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
throw new InvalidOperationException(
"Column '" + columnReference.Name + "' was not found in CSV header.");
}
throw new InvalidOperationException("Column reference is not defined.");
}
/// <summary>
/// Splits CSV line using common separators.
/// Supports:
/// ';'
/// ','
/// '\t'
/// </summary>
/// <param name="line">
/// Input CSV line.
/// </param>
/// <returns>
/// Parsed columns.
/// </returns>
private string[] SplitCsvLine(string line)
{
if (string.IsNullOrEmpty(line))
return new string[0];
if (line.Contains(";"))
return line.Split(';');
if (line.Contains(","))
return line.Split(',');
if (line.Contains("\t"))
return line.Split('\t');
return new[] { line };
}
/// <summary>
/// Adds diagnostic line when diagnostics are enabled.
/// </summary>
private void Log(
ReaderDiagnosticResult result,
bool enableDiagnostics,
string message)
{
if (!enableDiagnostics || result == null)
return;
result.Diagnostics.Add(message);
}
}
}

View File

@ -0,0 +1,273 @@
using System;
using System.Data;
using System.Data.SqlClient;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
{
/// <summary>
/// Reads data from SQL Server based data storage.
///
/// The reader uses configuration loaded from gci_config.json.
///
/// QueryTemplate must contain QUERYPARAM placeholder.
///
/// Example:
/// SELECT [Password] FROM [dbo].[SkeletonKeys] WHERE [PcbId] = QUERYPARAM
///
/// The placeholder is internally converted to SQL parameter @value.
/// </summary>
public class DatabaseDataStorageReader : IDataStorageReader
{
/// <summary>
/// Data storage configuration containing connection string and query template.
/// </summary>
private readonly DataStorageConfig config;
/// <summary>
/// Creates SQL Server data storage reader using provided configuration.
/// </summary>
/// <param name="config">
/// Data storage configuration loaded from gci_config.json.
/// </param>
public DatabaseDataStorageReader(DataStorageConfig config)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
}
/// <summary>
/// Executes configured SQL query and returns first matching row.
/// </summary>
/// <param name="query">
/// Query object containing lookup parameter.
/// </param>
/// <returns>
/// DatabaseSearchResult containing returned SQL columns and values.
/// </returns>
public object GetData(DataQuery query)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
ReaderDiagnosticResult sourceResult = TestSource(true);
if (!sourceResult.Success)
throw new InvalidOperationException(sourceResult.Message);
string sqlText = PrepareSqlText(config.QueryTemplate);
object queryValue = ExtractQueryValue(query);
using (SqlConnection connection = new SqlConnection(config.DataSource))
using (SqlCommand command = new SqlCommand(sqlText, connection))
{
AddQueryParameter(command, queryValue);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
{
DatabaseSearchResult result = new DatabaseSearchResult
{
Query = sqlText
};
if (!reader.Read())
{
result.Found = false;
return result;
}
result.Found = true;
for (int i = 0; i < reader.FieldCount; i++)
{
object value = reader.GetValue(i);
result.Values[reader.GetName(i)] =
value == DBNull.Value ? null : value;
}
return result;
}
}
}
/// <summary>
/// Tests whether SQL Server connection can be opened.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result of SQL connection test.
/// </returns>
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
try
{
if (string.IsNullOrWhiteSpace(config.DataSource))
throw new InvalidOperationException("Data source is empty.");
Log(result, enableDiagnostics, "Opening SQL connection.");
using (SqlConnection connection = new SqlConnection(config.DataSource))
{
connection.Open();
Log(result, enableDiagnostics, "Connection opened successfully.");
using (SqlCommand command = new SqlCommand("SELECT 1", connection))
{
object value = command.ExecuteScalar();
Log(result, enableDiagnostics, "Test query result: " + value);
}
}
result.Success = true;
result.Message = "Connection to SQL Server OK.";
}
catch (Exception ex)
{
result.Success = false;
result.Message = "Failed to connect to SQL Server. " + ex.Message;
Log(result, enableDiagnostics, ex.ToString());
}
return result;
}
/// <summary>
/// Tests whether configured SQL query can be prepared and executed.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
/// </param>
/// <returns>
/// Diagnostic result of query execution test.
/// </returns>
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
try
{
if (string.IsNullOrWhiteSpace(config.QueryTemplate))
throw new InvalidOperationException("Query template is empty.");
string sqlText = PrepareSqlText(config.QueryTemplate);
Log(result, enableDiagnostics, "Original template:");
Log(result, enableDiagnostics, config.QueryTemplate);
Log(result, enableDiagnostics, "Prepared SQL:");
Log(result, enableDiagnostics, sqlText);
using (SqlConnection connection = new SqlConnection(config.DataSource))
using (SqlCommand command = new SqlCommand(sqlText, connection))
{
AddQueryParameter(command, "TEST");
connection.Open();
object value = command.ExecuteScalar();
result.Data = value;
Log(result, enableDiagnostics, "Query executed successfully.");
}
result.Success = true;
result.Message = "Query executed successfully.";
}
catch (Exception ex)
{
result.Success = false;
result.Message = "Query execution failed. " + ex.Message;
Log(result, enableDiagnostics, ex.ToString());
}
return result;
}
/// <summary>
/// Converts configured QueryTemplate to executable SQL text.
/// </summary>
/// <param name="queryTemplate">
/// SQL query template containing QUERYPARAM placeholder.
/// </param>
/// <returns>
/// SQL text with QUERYPARAM replaced by @value parameter.
/// </returns>
private static string PrepareSqlText(string queryTemplate)
{
if (string.IsNullOrWhiteSpace(queryTemplate))
throw new ArgumentException(
"Query template must not be empty.",
nameof(queryTemplate));
if (!queryTemplate.Contains("QUERYPARAM"))
throw new InvalidOperationException(
"Query template must contain QUERYPARAM placeholder.");
return queryTemplate.Replace("QUERYPARAM", "@value");
}
/// <summary>
/// Extracts first query parameter value.
/// </summary>
/// <param name="query">
/// Data query containing query parameters.
/// </param>
/// <returns>
/// First query parameter value.
/// </returns>
private static object ExtractQueryValue(DataQuery query)
{
if (query.QueryParams == null || query.QueryParams.Count == 0)
throw new InvalidOperationException(
"DataQuery does not contain any query parameter.");
return query.QueryParams[0];
}
/// <summary>
/// Adds lookup parameter to SQL command.
/// </summary>
/// <param name="command">
/// SQL command.
/// </param>
/// <param name="value">
/// Query parameter value.
/// </param>
private static void AddQueryParameter(SqlCommand command, object value)
{
command.Parameters.Clear();
SqlParameter parameter = command.Parameters.Add("@value", SqlDbType.Variant);
parameter.Value = value ?? DBNull.Value;
}
/// <summary>
/// Adds diagnostic line when diagnostics are enabled.
/// </summary>
/// <param name="result">
/// Diagnostic result object.
/// </param>
/// <param name="enableDiagnostics">
/// Indicates whether diagnostics are enabled.
/// </param>
/// <param name="message">
/// Diagnostic message.
/// </param>
private static void Log(
ReaderDiagnosticResult result,
bool enableDiagnostics,
string message)
{
if (!enableDiagnostics || result == null)
return;
result.Diagnostics.Add(message);
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
{
public class JsonDataStorageReader : IDataStorageReader
{
private readonly DataStorageConfig config;
public JsonDataStorageReader(DataStorageConfig config)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
}
public object GetData(DataQuery query)
{
throw new NotImplementedException();
}
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
{
throw new NotImplementedException();
}
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
{
public class RestApiDataStorageReader : IDataStorageReader
{
private readonly DataStorageConfig config;
public RestApiDataStorageReader(DataStorageConfig config)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
}
public object GetData(DataQuery query)
{
throw new NotImplementedException();
}
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
{
throw new NotImplementedException();
}
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,125 @@
using System;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Searching
{
/// <summary>
/// Parsed representation of QueryTemplate expression.
///
/// Example:
///
/// SELECT [Password] WHERE [PcbId] = QUERYPARAM
///
/// becomes:
///
/// SelectColumn:
/// [Password]
///
/// WhereColumn:
/// [PcbId]
/// </summary>
public class SearchOrderDefinition
{
/// <summary>
/// Column to be returned from lookup result.
///
/// Example:
///
/// SELECT [Password]
///
/// returns:
///
/// [Password]
/// </summary>
public ColumnReference SelectColumn { get; set; }
/// <summary>
/// Column used for row matching.
///
/// Example:
///
/// WHERE [PcbId] = QUERYPARAM
///
/// uses:
///
/// [PcbId]
/// </summary>
public ColumnReference WhereColumn { get; set; }
}
/// <summary>
/// Represents one column definition inside QueryTemplate.
///
/// Supports two forms:
///
/// [ColumnName]
///
/// or:
///
/// COLUMN(number)
///
/// Examples:
///
/// [Password]
///
/// COLUMN(2)
/// </summary>
public class ColumnReference
{
/// <summary>
/// Column name used in named lookup mode.
///
/// Example:
///
/// [Password]
/// </summary>
public string Name { get; set; }
/// <summary>
/// Zero-based column index used in indexed lookup mode.
///
/// Example:
///
/// COLUMN(2)
/// </summary>
public int? Index { get; set; }
/// <summary>
/// Indicates whether column uses name-based lookup.
/// </summary>
public bool HasName
{
get
{
return !string.IsNullOrWhiteSpace(Name);
}
}
/// <summary>
/// Indicates whether column uses index-based lookup.
/// </summary>
public bool HasIndex
{
get
{
return Index.HasValue;
}
}
/// <summary>
/// Returns human readable representation.
/// </summary>
/// <returns>
/// Column expression text.
/// </returns>
public override string ToString()
{
if (HasName)
return "[" + Name + "]";
if (HasIndex)
return "COLUMN(" + Index.Value + ")";
return "<undefined column reference>";
}
}
}

View File

@ -0,0 +1,167 @@
using System;
using System.Text.RegularExpressions;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Searching
{
/// <summary>
/// Parses SQL-like QueryTemplate expressions used by GCI data storage readers.
///
/// Supported syntax:
///
/// SELECT [ReturnColumn] WHERE [MatchColumn] = QUERYPARAM
///
/// Examples:
///
/// SELECT [Password] WHERE [PcbId] = QUERYPARAM
///
/// SELECT COLUMN(1) WHERE COLUMN(0) = QUERYPARAM
///
/// Mixed forms are also supported:
///
/// SELECT [Password] WHERE COLUMN(0)=QUERYPARAM
///
/// The parser converts text expressions into structured
/// SearchOrderDefinition objects which are later consumed
/// by CSV and other readers.
/// </summary>
public static class SearchOrderParser
{
/// <summary>
/// Full QueryTemplate validation pattern.
///
/// Expected syntax:
///
/// SELECT [Column] WHERE [Column] = QUERYPARAM
/// </summary>
private static readonly Regex FullPattern = new Regex(
@"^\s*SELECT\s+(?<select>\[[^\]]+\]|COLUMN\(\d+\))\s+WHERE\s+(?<where>\[[^\]]+\]|COLUMN\(\d+\))\s*=\s*QUERYPARAM\s*$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Matches:
///
/// [ColumnName]
/// </summary>
private static readonly Regex NamedColumnPattern = new Regex(
@"^\[(?<name>[^\]]+)\]$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Matches:
///
/// COLUMN(number)
/// </summary>
private static readonly Regex IndexedColumnPattern = new Regex(
@"^COLUMN\((?<index>\d+)\)$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Parses QueryTemplate text into structured definition.
///
/// Example:
///
/// SELECT [Password] WHERE [PcbId] = QUERYPARAM
///
/// Result:
///
/// SelectColumn:
/// Password
///
/// WhereColumn:
/// PcbId
/// </summary>
/// <param name="searchOrder">
/// QueryTemplate expression.
/// </param>
/// <returns>
/// Parsed query definition.
/// </returns>
public static SearchOrderDefinition Parse(string searchOrder)
{
if (string.IsNullOrWhiteSpace(searchOrder))
throw new InvalidOperationException(
"QueryTemplate is empty.");
Match match = FullPattern.Match(searchOrder);
if (!match.Success)
{
throw new InvalidOperationException(
"Invalid QueryTemplate syntax. Expected: SELECT [ReturnColumn] WHERE [MatchColumn] = QUERYPARAM");
}
return new SearchOrderDefinition
{
SelectColumn =
ParseColumnReference(
match.Groups["select"].Value),
WhereColumn =
ParseColumnReference(
match.Groups["where"].Value)
};
}
/// <summary>
/// Parses single column definition.
///
/// Supported forms:
///
/// [ColumnName]
///
/// COLUMN(number)
/// </summary>
/// <param name="token">
/// Raw token extracted from QueryTemplate.
/// </param>
/// <returns>
/// Parsed column reference.
/// </returns>
private static ColumnReference ParseColumnReference(
string token)
{
if (string.IsNullOrWhiteSpace(token))
throw new InvalidOperationException(
"Column reference token is empty.");
Match nameMatch =
NamedColumnPattern.Match(token);
if (nameMatch.Success)
{
return new ColumnReference
{
Name =
nameMatch
.Groups["name"]
.Value
.Trim(),
Index = null
};
}
Match indexMatch =
IndexedColumnPattern.Match(token);
if (indexMatch.Success)
{
return new ColumnReference
{
Name = null,
Index =
int.Parse(
indexMatch
.Groups["index"]
.Value)
};
}
throw new InvalidOperationException(
"Invalid column reference '" +
token +
"'. Use [ColumnName] or COLUMN(number).");
}
}
}

View File

@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords
{
/// <summary>
/// Defines contract for reading meter login passwords
/// from configured GCI data storage.
///
/// Implementations hide storage details and provide
/// a unified API for password lookup.
///
/// Supported storage types:
/// - SQL database
/// - CSV
/// - JSON
/// - REST API
/// </summary>
public interface IMeterLoginPasswordReader
{
/// <summary>
/// Reads meter login password synchronously.
/// </summary>
/// <param name="queryParam">
/// PCB ID or another configured lookup value.
/// </param>
/// <returns>
/// Password if found; otherwise null.
/// </returns>
string GetPassword(string queryParam);
/// <summary>
/// Reads meter login password asynchronously.
/// Thread-safe implementation may serialize
/// access to the underlying storage.
/// </summary>
/// <param name="queryParam">
/// PCB ID or another configured lookup value.
/// </param>
/// <param name="token">
/// Cancellation token.
/// </param>
/// <returns>
/// Password if found; otherwise null.
/// </returns>
Task<string> GetPasswordAsync(
string queryParam,
CancellationToken token = default);
}
}

View File

@ -0,0 +1,91 @@
using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
using GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Reads meter login password from configured GCI data storage.
/// </summary>
public class MeterLoginPasswordReader : IMeterLoginPasswordReader
{
/// <summary>
/// Universal data storage reader selected by configuration.
/// Can represent database, CSV, JSON or REST reader.
/// </summary>
private readonly IDataStorageReader reader;
/// <summary>
/// Ensures that only one password lookup is executed at a time.
/// </summary>
private readonly SemaphoreSlim readLock = new SemaphoreSlim(1, 1);
/// <summary>
/// Creates password reader using provided data storage configuration.
/// </summary>
/// <param name="config">Data storage configuration loaded from gci_config.json.</param>
public MeterLoginPasswordReader(DataStorageConfig config)
{
reader = DataStorageReaderFactory.Create(config);
}
/// <summary>
/// Asynchronously reads meter login password by query parameter.
/// </summary>
/// <param name="queryParam">PCB ID or another configured lookup value.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>Password if found; otherwise null.</returns>
public async Task<string> GetPasswordAsync(
string queryParam,
CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(queryParam))
throw new ArgumentException("Query parameter must not be empty.", nameof(queryParam));
await readLock.WaitAsync(token);
try
{
return await Task.Run(
() => GetPassword(queryParam),
token);
}
finally
{
readLock.Release();
}
}
/// <summary>
/// Reads meter login password by query parameter.
/// </summary>
/// <param name="queryParam">PCB ID or another configured lookup value.</param>
/// <returns>Password if found; otherwise null.</returns>
public string GetPassword(string queryParam)
{
if (string.IsNullOrWhiteSpace(queryParam))
throw new ArgumentException("Query parameter must not be empty.", nameof(queryParam));
DataQuery query = new DataQuery();
query.QueryParams.Add(queryParam);
object result = reader.GetData(query);
if (result is ReaderDiagnosticResult csvResult)
return csvResult.Data?.ToString();
if (result is DatabaseSearchResult dbResult)
{
if (!dbResult.Found || dbResult.Values == null || dbResult.Values.Count == 0)
return null;
foreach (object value in dbResult.Values.Values)
return value?.ToString();
}
return result?.ToString();
}
}

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams
{
/// <summary>
/// Provides access to pre-adjustment calibration parameters
/// stored in the configured data source.
///
/// Expected structure:
/// Dn_InternalId | ParameterName | ParameterValue
///
/// Example:
/// 3 | GENESISFLOW_MinValidToF | 21990232
/// 3 | GENESISFLOW_Timeout | 1
///
/// Parameters are grouped by Dn_InternalId and returned
/// as key/value pairs where:
///
/// Key = ParameterName
/// Value = ParameterValue
/// </summary>
internal interface IPreAdjustmentCalibrationParamsReader
{
/// <summary>
/// Reads all calibration parameters assigned
/// to a specific DN identifier.
/// </summary>
/// <param name="dnInternalId">
/// Internal DN identifier (meter size).
/// </param>
/// <returns>
/// Dictionary:
///
/// Key:
/// GENESISFLOW parameter name
///
/// Value:
/// Stored parameter value
/// </returns>
Task<Dictionary<string, string>> ReadParamsAsync(int dnInternalId);
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams
{
/// <summary>
/// Reads pre-adjustment calibration parameters
/// from SQL database storage.
///
/// Expected table:
///
/// dbo.PreAdjustmentCalibrationParams
///
/// Columns:
/// Dn_InternalId
/// ParameterName
/// ParameterValue
///
/// Example:
///
/// 3 | GENESISFLOW_MinValidToF | 21990232
/// 3 | GENESISFLOW_Timeout | 1
///
/// Returns values as dictionary:
///
/// GENESISFLOW_MinValidToF -> 21990232
/// GENESISFLOW_Timeout -> 1
/// </summary>
internal class PreAdjustmentCalibrationParamsReader: IPreAdjustmentCalibrationParamsReader
{
private readonly string _connectionString;
/// <summary>
/// Initializes calibration parameter reader.
/// </summary>
/// <param name="connectionString">
/// SQL database connection string.
/// </param>
public PreAdjustmentCalibrationParamsReader(
string connectionString)
{
_connectionString = connectionString?? throw new ArgumentNullException(nameof(connectionString));
}
/// <inheritdoc/>
public async Task<Dictionary<string, string>> ReadParamsAsync(int dnInternalId)
{
var result = new Dictionary<string, string>();
const string query = @"
SELECT ParameterName, ParameterValue
FROM dbo.PreAdjustmentCalibrationParams
WHERE Dn_InternalId=@Dn_InternalId";
using (var connection = new SqlConnection(_connectionString))
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue(
"@Dn_InternalId",
dnInternalId);
await connection.OpenAsync();
using (var reader =
await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
var parameterName = reader["ParameterName"].ToString();
var parameterValue = reader["ParameterValue"].ToString();
if (!string.IsNullOrWhiteSpace(parameterName))
{
result[parameterName] = parameterValue;
}
}
}
}
return result;
}
}
}

View File

@ -60,6 +60,26 @@
<Compile Include="API\InterfaceOutsideToGCI.cs" />
<Compile Include="API\InterfaceGCIToLaatzen.cs" />
<Compile Include="API\PublicModels.cs" />
<Compile Include="Core\DataStorage\Config\GciConfig.cs" />
<Compile Include="Core\DataStorage\Config\GciConfigLoader.cs" />
<Compile Include="Core\DataStorage\Config\GciDataStorageConfig.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\CsvDataStorageReader.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\DatabaseDataStorageReader.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Models\DatabaseSearchResult.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Models\DataQuery.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Models\DataStorageConfig.cs" />
<Compile Include="Core\DataStorage\Reading\Common\DataStorageReaderFactory.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Models\DataStorageType.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Contracts\IDataStorageReader..cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\JsonDataStorageReader.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Models\ReaderDiagnosticResult.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Providers\RestApiDataStorageReader.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Searching\SearchOrderDefinition.cs" />
<Compile Include="Core\DataStorage\Reading\Common\Searching\SearchOrderParser.cs" />
<Compile Include="Core\DataStorage\Reading\Implementation\MeterLoginPasswords\IMeterLoginPasswordReader.cs" />
<Compile Include="Core\DataStorage\Reading\Implementation\MeterLoginPasswords\MeterLoginPasswordReader.cs" />
<Compile Include="Core\DataStorage\Reading\Implementation\PreAdjustmentCalibrationParams\IPreAdjustmentCalibrationParamsReader.cs" />
<Compile Include="Core\DataStorage\Reading\Implementation\PreAdjustmentCalibrationParams\PreAdjustmentCalibrationParamsReader.cs" />
<Compile Include="Core\Logging\UiLogBus.cs" />
<Compile Include="Core\Logging\UiTarget.cs" />
<Compile Include="Core\Threading\ApiWorker\ApiWorker.cs" />
@ -142,6 +162,15 @@
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\MetersActionView.Designer.cs">
<DependentUpon>MetersActionView.cs</DependentUpon>
</Compile>
<Content Include="docs\images\GciBridge_component_GUI.png" />
<Content Include="docs\images\GCI__Onboarding_Overview_drawio.svg" />
<Content Include="docs\images\GCI__Onboarding_Overview_drawio__API_architecture__Current_state.svg" />
<Content Include="docs\images\GCI__Onboarding_Overview_drawio__API_architecture__Target_state.svg" />
<Content Include="docs\images\logo\logo.png" />
<Content Include="docs\images\logo\logo.svg" />
<Content Include="docs\images\logo\sensus-logo-white-green-rgb.png" />
<Content Include="docs\images\logo\sensus-logo-white-green-rgb.svg" />
<Content Include="docs\styles\main.css" />
<Content Include="RuntimePackage\Build\Copy.targets.xml" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
@ -169,6 +198,31 @@
<EmbeddedResource Include="UI\StaraTuraAPI_GenesisCordonelInterface\FrmGCIAPI.resx">
<DependentUpon>FrmGCIAPI.cs</DependentUpon>
</EmbeddedResource>
<Content Include="Config\gci_config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="docs\articles\API\index.md" />
<None Include="docs\articles\API\PublicModels.md" />
<None Include="docs\articles\API\InterfaceGCIToLaatzen.md" />
<None Include="docs\articles\API\InterfaceOutsideToGCI.md" />
<None Include="docs\docfx.json" />
<None Include="docs\index.md" />
<None Include="docs\pages\development\page_dev__current_state.md" />
<None Include="docs\pages\development\page_dev__home.md" />
<None Include="docs\pages\development\page_dev__target_state.md" />
<None Include="docs\pages\development\page_dev__migration.md" />
<None Include="docs\pages\development\page_dev__refactoring.md" />
<None Include="docs\pages\development\page_dev__target_architecture.md" />
<None Include="docs\pages\gci\page_gci__app_environment.md" />
<None Include="docs\pages\gci\page_gci__internal_architecture.md" />
<None Include="docs\pages\gci\page_gci__datastorage.md" />
<None Include="docs\pages\gci\page_gci__home.md" />
<None Include="docs\pages\gci\page_gci__interfaces.md" />
<None Include="docs\pages\gci\page_gci__runtime.md" />
<None Include="docs\pages\gci\page_gci__implementation_to_tbf.md" />
<None Include="docs\pages\gci\page_gci__implementation_universal.md" />
<None Include="docs\pages\gci\page_gci__workers.md" />
<None Include="docs\toc.yml" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@ -184,6 +238,7 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Core\DataStorage\Writing\NewFolder1\" />
<Folder Include="RuntimePackage\Package\" />
</ItemGroup>
<ItemGroup>

View File

@ -188,7 +188,6 @@
this.btnMeterInit.Size = new System.Drawing.Size(150, 30);
this.btnMeterInit.Text = "Meter Init";
this.btnMeterInit.UseVisualStyleBackColor = true;
this.btnMeterInit.Click += new System.EventHandler(this.btnMeterInit_Click);
// btnMetersAction
this.btnMetersAction.Location = new System.Drawing.Point(6, 58);

View File

@ -192,13 +192,6 @@ namespace GenesisCordonelInterface.UI
Logger.Trace($"FORM: GCI VIEW -> {name} LOADED");
}
private void btnMeterInit_Click(object sender, EventArgs e)
{
SwitchGciView(
"MeterInit",
new MeterInitView(_api, AddSlotRow, SaveSlots));
}
private void btnMetersAction_Click(object sender, EventArgs e)
{
SwitchGciView(
@ -329,11 +322,5 @@ namespace GenesisCordonelInterface.UI
view.Dock = DockStyle.Fill;
pnlGciViewHost.Controls.Add(view);
}
public void SaveSlots()
{
var data = _batchPanel.GetGridData();
_api.SaveSlotSetup(data);
}
}
}

View File

@ -26,7 +26,6 @@
this.btnReloadSetup.SetBounds(20, 20, 140, 32);
this.btnReloadSetup.Text = "Reload Setup";
this.btnReloadSetup.Click += new System.EventHandler(this.btnReloadSetup_Click);
this.btnSaveSetup.SetBounds(170, 20, 140, 32);
this.btnSaveSetup.Text = "Save Setup";

View File

@ -22,10 +22,6 @@ namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
#region BUTTONS
// ----------------------------------------------------
private void btnReloadSetup_Click(object sender, EventArgs e)
{
ExecuteApiAction(() => _api.ReloadSlotSetup());
}
private void btnSaveSetup_Click(object sender, EventArgs e)
{

View File

@ -348,7 +348,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
timeoutMs: 30000);
}
/// <summary>
/// Updates an already initialized GCI slot.
///
@ -1273,6 +1273,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
}
}
/// <summary>
/// Reads password from UniDataStorageReader using PCB ID with retry support.
///
@ -1424,12 +1426,58 @@ namespace TBF.Rig.BridgeComponents.GciBridge
// Preadjustment API:
#region ================================== Preadjustment INIT bridge ==================================
public PreAdjustmentInitializationResult Preadjustment_Initialization(
ProcessProgress pp, List<MeterStateControl> mc)
{
const string operation = nameof(Preadjustment_Initialization);
try
{
EnsureExternalInterface();
log.InfoFormat(
"{0}: {1} Start.",
Name,
operation);
PreAdjustmentInitializationResult result = gciExternalInterface.Preadjustment_Initialization(pp, mc);
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 PreAdjustmentInitializationResult
{
Success = false,
ErrorMessage = ex.Message
};
}
}
#endregion
#region ================================== Preadjustment DETECT bridge ==================================
public async Task<PreadjustmentDetectResult> Preadjustment_DetectAsync(
public async Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
IEnumerable<GciPublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_DetectAsync);
const string operation = nameof(PreAdjustment_DetectAsync);
try
{
@ -1439,7 +1487,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
PreadjustmentDetectResult result =
await gciExternalInterface
.Preadjustment_DetectAsync(token)
.PreAdjustment_DetectAsync(selectedSlots, token)
.ConfigureAwait(false);
log.InfoFormat(
@ -1466,34 +1514,28 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#endregion
#region ================================== PreAdjustment PREPARATION bridge ==================================
public async Task<PreadjustmentProcessResult> Preadjustment_PreparationAsync(
public async Task<PreAdjustmentProcessResult> PreAdjustment_PreparationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_PreparationAsync);
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 =
PreAdjustmentProcessResult result =
await gciExternalInterface
.Preadjustment_PreparationAsync(
.PreAdjustment_PreparationAsync(
slot,
pp,
token)
.ConfigureAwait(false);
@ -1511,7 +1553,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string.Format("{0}: {1} failed.", Name, operation),
ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1523,34 +1565,28 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#endregion
#region ================================== PreAdjustment AMPLITUDE TEST bridge ==================================
public async Task<PreadjustmentProcessResult> Preadjustment_AmplitudeTestAsync(
public async Task<PreAdjustmentProcessResult> PreAdjustment_AmplitudeTestAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_AmplitudeTestAsync);
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 =
PreAdjustmentProcessResult result =
await gciExternalInterface
.Preadjustment_AmplitudeTestAsync(
.PreAdjustment_AmplitudeTestAsync(
slot,
pp,
token)
.ConfigureAwait(false);
@ -1568,7 +1604,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string.Format("{0}: {1} failed.", Name, operation),
ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1582,31 +1618,26 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#region ================================== PreAdjustment TEMPERATURE CALIBRATION bridge ==================================
public async Task<PreadjustmentProcessResult> Preadjustment_TemperatureCalibrationAsync(
public async Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_TemperatureCalibrationAsync);
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 =
PreAdjustmentProcessResult result =
await gciExternalInterface
.Preadjustment_TemperatureCalibrationAsync(
.PreAdjustment_TemperatureCalibrationAsync(
slot,
pp,
token)
.ConfigureAwait(false);
@ -1624,7 +1655,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string.Format("{0}: {1} failed.", Name, operation),
ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1636,34 +1667,28 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#endregion
#region ================================== PreAdjustment OFFSET TEST bridge ==================================
public async Task<PreadjustmentProcessResult> Preadjustment_OffsetTestAsync(
public async Task<PreAdjustmentProcessResult> PreAdjustment_OffsetTestAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_OffsetTestAsync);
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 =
PreAdjustmentProcessResult result =
await gciExternalInterface
.Preadjustment_OffsetTestAsync(
.PreAdjustment_OffsetTestAsync(
slot,
pp,
token)
.ConfigureAwait(false);
@ -1681,7 +1706,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string.Format("{0}: {1} failed.", Name, operation),
ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,
@ -1693,34 +1718,28 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#endregion
#region ================================== PreAdjustment COMPLETION bridge ==================================
public async Task<PreadjustmentProcessResult> Preadjustment_CompletionAsync(
public async Task<PreAdjustmentProcessResult> PreAdjustment_CompletionAsync(
int slot,
ProcessProgress pp,
CancellationToken token = default)
{
const string operation = nameof(Preadjustment_CompletionAsync);
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 =
PreAdjustmentProcessResult result =
await gciExternalInterface
.Preadjustment_CompletionAsync(
.PreAdjustment_CompletionAsync(
slot,
pp,
token)
.ConfigureAwait(false);
@ -1738,7 +1757,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string.Format("{0}: {1} failed.", Name, operation),
ex);
return new PreadjustmentProcessResult
return new PreAdjustmentProcessResult
{
Success = false,
Slot = slot,

View File

@ -41,9 +41,8 @@
this.externalTypeNameLabel = new System.Windows.Forms.Label();
this.externalTypeNameTextBox = new System.Windows.Forms.TextBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.connectExternalButton = new System.Windows.Forms.Button();
this.showGciGuiButton = new System.Windows.Forms.Button();
this.showGciBridgeGUIButton = new System.Windows.Forms.Button();
this.showGciGuiButton = new System.Windows.Forms.Button();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
@ -162,38 +161,17 @@
// groupBox1
//
this.groupBox1.Controls.Add(this.showGciBridgeGUIButton);
this.groupBox1.Controls.Add(this.connectExternalButton);
this.groupBox1.Controls.Add(this.showGciGuiButton);
this.groupBox1.Location = new System.Drawing.Point(31, 241);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(573, 60);
this.groupBox1.TabIndex = 12;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "GCI bridge";
//
// connectExternalButton
//
this.connectExternalButton.Location = new System.Drawing.Point(14, 19);
this.connectExternalButton.Name = "connectExternalButton";
this.connectExternalButton.Size = new System.Drawing.Size(107, 28);
this.connectExternalButton.TabIndex = 0;
this.connectExternalButton.Text = "Connect";
this.connectExternalButton.UseVisualStyleBackColor = true;
this.connectExternalButton.Click += new System.EventHandler(this.connectExternalButton_Click);
//
// showGciGuiButton
//
this.showGciGuiButton.Location = new System.Drawing.Point(308, 19);
this.showGciGuiButton.Name = "showGciGuiButton";
this.showGciGuiButton.Size = new System.Drawing.Size(107, 28);
this.showGciGuiButton.TabIndex = 1;
this.showGciGuiButton.Text = "Show GCI GUI";
this.showGciGuiButton.UseVisualStyleBackColor = true;
this.showGciGuiButton.Click += new System.EventHandler(this.showGuiButton_Click);
this.groupBox1.Text = "Diagnostic GUI";
//
// showGciBridgeGUIButton
//
this.showGciBridgeGUIButton.Location = new System.Drawing.Point(421, 19);
this.showGciBridgeGUIButton.Location = new System.Drawing.Point(311, 19);
this.showGciBridgeGUIButton.Name = "showGciBridgeGUIButton";
this.showGciBridgeGUIButton.Size = new System.Drawing.Size(134, 28);
this.showGciBridgeGUIButton.TabIndex = 2;
@ -201,6 +179,16 @@
this.showGciBridgeGUIButton.UseVisualStyleBackColor = true;
this.showGciBridgeGUIButton.Click += new System.EventHandler(this.showGciBridgeGUIButton_Click);
//
// showGciGuiButton
//
this.showGciGuiButton.Location = new System.Drawing.Point(451, 19);
this.showGciGuiButton.Name = "showGciGuiButton";
this.showGciGuiButton.Size = new System.Drawing.Size(107, 28);
this.showGciGuiButton.TabIndex = 1;
this.showGciGuiButton.Text = "Show GCI GUI";
this.showGciGuiButton.UseVisualStyleBackColor = true;
this.showGciGuiButton.Click += new System.EventHandler(this.showGuiButton_Click);
//
// GciBridgeCfgCtrl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -242,7 +230,6 @@
private System.Windows.Forms.Label externalTypeNameLabel;
private System.Windows.Forms.TextBox externalTypeNameTextBox;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button connectExternalButton;
private System.Windows.Forms.Button showGciGuiButton;
private System.Windows.Forms.Button showGciBridgeGUIButton;
}

View File

@ -170,26 +170,6 @@ namespace TBF.Rig.BridgeComponents.GciBridge
return flags;
}
private void connectExternalButton_Click(object sender, EventArgs e)
{
try
{
GciBridge bridge = TbfComponents.FindComponent(config.Name) as GciBridge;
if (bridge == null)
{
MessageBox.Show("GciBridge component was not found.", "GCI Bridge");
return;
}
//bridge.ConnectExternal();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "GCI Bridge error");
}
}
private void showGuiButton_Click(object sender, EventArgs e)
{
try

View File

@ -28,7 +28,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
private static readonly Regex LogLevelRegex = new Regex(@"\|\s*(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s*\|", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public InterfaceOutsideToGCI _gciApi;
public InterfaceGCIToLaatzen _laatzenApi;
public MainForm _mainform;
public MainForm _mainForm;
public GciBridge _bridge;
public Debug.MeterBatchConfigPanel _batchPanel;
public event Action<List<PublicModels.MeterBatchDebugStatus>> MeterBatchStatusChanged;// object status from place of his location
@ -52,7 +52,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
public MainView(GciBridge bridge, MainForm mainform)
{
_mainform = mainform;
_mainForm = mainform;
_bridge = bridge;
_gciApi = bridge.gciExternalInterface;
_laatzenApi = _bridge.gciExternalInterface._innerMeterAPI;
@ -162,14 +162,14 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
/// </summary>
private void InitializeDebugPanels()
{
_batchPanel = new Debug.MeterBatchConfigPanel(_mainform, this)
_batchPanel = new Debug.MeterBatchConfigPanel(_mainForm, this)
{
Dock = DockStyle.Fill
};
pnlSlotConfig.Controls.Add(_batchPanel);
pnlWorkerDebug.Controls.Add(new Debug.WorkerDebugPanel(_mainform, this)
pnlWorkerDebug.Controls.Add(new Debug.WorkerDebugPanel(_mainForm, this)
{
Dock = DockStyle.Fill
});
@ -423,7 +423,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Preadjustment open.");
_laatzenApi.ShowPreadjustmentForm(DialogOwner);
_laatzenApi.ShowPreAdjustmentForm(DialogOwner);
Logger.Trace("FORM: Preadjustment shown.");
}

View File

@ -24,6 +24,14 @@
private void InitializeComponent()
{
this.grpSlots = new System.Windows.Forms.GroupBox();
this.label7 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.preAdjustmentInitializationButton = new System.Windows.Forms.Button();
this.completionActionButton = new System.Windows.Forms.Button();
this.offsetTestActionButton = new System.Windows.Forms.Button();
this.preparationActionButton = new System.Windows.Forms.Button();
@ -32,19 +40,15 @@
this.amplitudeTestActionButton = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.txtLog = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.cb_Metersize = new System.Windows.Forms.ComboBox();
this.l_SettingsPreparationMetersize = new System.Windows.Forms.Label();
this.grpSlots.SuspendLayout();
this.SuspendLayout();
//
// grpSlots
//
this.grpSlots.Controls.Add(this.cb_Metersize);
this.grpSlots.Controls.Add(this.l_SettingsPreparationMetersize);
this.grpSlots.Controls.Add(this.label7);
this.grpSlots.Controls.Add(this.label6);
this.grpSlots.Controls.Add(this.label5);
@ -52,7 +56,7 @@
this.grpSlots.Controls.Add(this.label3);
this.grpSlots.Controls.Add(this.label2);
this.grpSlots.Controls.Add(this.label1);
this.grpSlots.Controls.Add(this.button1);
this.grpSlots.Controls.Add(this.preAdjustmentInitializationButton);
this.grpSlots.Controls.Add(this.completionActionButton);
this.grpSlots.Controls.Add(this.offsetTestActionButton);
this.grpSlots.Controls.Add(this.preparationActionButton);
@ -67,6 +71,85 @@
this.grpSlots.Text = "Slots by selection in the table";
this.grpSlots.Enter += new System.EventHandler(this.grpSlots_Enter);
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(144, 87);
this.label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(22, 13);
this.label7.TabIndex = 115;
this.label7.Text = "--->";
//
// label6
//
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(14, 87);
this.label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(117, 13);
this.label6.TabIndex = 114;
this.label6.Text = "Preadjustment process:";
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(363, 257);
this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(22, 13);
this.label5.TabIndex = 113;
this.label5.Text = "--->";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(307, 223);
this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(22, 13);
this.label4.TabIndex = 112;
this.label4.Text = "--->";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(258, 189);
this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(22, 13);
this.label3.TabIndex = 111;
this.label3.Text = "--->";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(219, 155);
this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(22, 13);
this.label2.TabIndex = 110;
this.label2.Text = "--->";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(176, 121);
this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(22, 13);
this.label1.TabIndex = 109;
this.label1.Text = "--->";
//
// preAdjustmentInitializationButton
//
this.preAdjustmentInitializationButton.Location = new System.Drawing.Point(171, 28);
this.preAdjustmentInitializationButton.Name = "preAdjustmentInitializationButton";
this.preAdjustmentInitializationButton.Size = new System.Drawing.Size(150, 28);
this.preAdjustmentInitializationButton.TabIndex = 108;
this.preAdjustmentInitializationButton.Text = "Preadjustment init";
this.preAdjustmentInitializationButton.Click += new System.EventHandler(this.preAdjustmentInitializationButton_Click);
//
// completionActionButton
//
this.completionActionButton.Location = new System.Drawing.Point(390, 249);
@ -142,83 +225,23 @@
this.txtLog.TabIndex = 5;
this.txtLog.WordWrap = false;
//
// button1
// cb_Metersize
//
this.button1.Location = new System.Drawing.Point(17, 28);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(150, 28);
this.button1.TabIndex = 108;
this.button1.Text = "Preadjustment init";
this.cb_Metersize.FormattingEnabled = true;
this.cb_Metersize.Location = new System.Drawing.Point(74, 30);
this.cb_Metersize.Name = "cb_Metersize";
this.cb_Metersize.Size = new System.Drawing.Size(73, 21);
this.cb_Metersize.TabIndex = 117;
//
// label1
// l_SettingsPreparationMetersize
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(176, 121);
this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(22, 13);
this.label1.TabIndex = 109;
this.label1.Text = "--->";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(219, 155);
this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(22, 13);
this.label2.TabIndex = 110;
this.label2.Text = "--->";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(258, 189);
this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(22, 13);
this.label3.TabIndex = 111;
this.label3.Text = "--->";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(307, 223);
this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(22, 13);
this.label4.TabIndex = 112;
this.label4.Text = "--->";
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(363, 257);
this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(22, 13);
this.label5.TabIndex = 113;
this.label5.Text = "--->";
//
// label6
//
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(14, 87);
this.label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(117, 13);
this.label6.TabIndex = 114;
this.label6.Text = "Preadjustment process:";
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(144, 87);
this.label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(22, 13);
this.label7.TabIndex = 115;
this.label7.Text = "--->";
this.l_SettingsPreparationMetersize.AutoSize = true;
this.l_SettingsPreparationMetersize.Location = new System.Drawing.Point(14, 36);
this.l_SettingsPreparationMetersize.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.l_SettingsPreparationMetersize.Name = "l_SettingsPreparationMetersize";
this.l_SettingsPreparationMetersize.Size = new System.Drawing.Size(55, 13);
this.l_SettingsPreparationMetersize.TabIndex = 116;
this.l_SettingsPreparationMetersize.Text = "Metersize:";
//
// PreadjustmentActionsView
//
@ -238,7 +261,7 @@
private System.Windows.Forms.Button completionActionButton;
private System.Windows.Forms.Button offsetTestActionButton;
private System.Windows.Forms.Button amplitudeTestActionButton;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button preAdjustmentInitializationButton;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label3;
@ -246,5 +269,7 @@
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.ComboBox cb_Metersize;
private System.Windows.Forms.Label l_SettingsPreparationMetersize;
}
}

View File

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Logic.ProductionOrderCore.OrderData;
using Xylem.Common.Ui.CordonelPreadjustmentUi;
using static GenesisCordonelInterface.API.PublicModels;
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.PreadjustmentMeter;
@ -39,6 +40,18 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
InitializeComponent();
LoadRegisterComboBoxes();
cb_Metersize.Items.Clear();
foreach (MeterSize size in (MeterSize[])Enum.GetValues(typeof(MeterSize)))
{
cb_Metersize.Items.Add(size);
}
var setM = MeterSize.DN50;
if (_mainView._gciApi._innerMeterAPI._settings.MeterSize != null)
{
setM = _mainView._gciApi._innerMeterAPI._settings.MeterSize;
}
cb_Metersize.SelectedItem = setM;
}
private void AddSlot()
@ -159,74 +172,43 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
}
private void preparationActionButton_Click(
private void preAdjustmentInitializationButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
try
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.Preadjustment_PreparationAsync,
"Preparation",
token);
});
}
var selectedSlots = _mainView._batchPanel.GetSelectedGridData();
private void amplitudeTestActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.Preadjustment_AmplitudeTestAsync,
"Amplitude Test",
token);
});
}
if (!selectedSlots.Any())
{
Log("Detect: no selected slots.");
return;
}
private void temperatureCalibrationActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.Preadjustment_TemperatureCalibrationAsync,
"Temperature Calibration",
token);
});
}
ProcessProgress pp = CreatePreparationProgress();
List<MeterStateControl> mc = CreateMeterControls(selectedSlots);
private void offsetTestActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.Preadjustment_OffsetTestAsync,
"Offset Test",
token);
});
}
var result = _bridge.Preadjustment_Initialization(pp, mc);
private void completionActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
if (!result.Success)
{
Log(result.ErrorMessage);
return;
}
Log(
$"Initialization successful. Slots: {result.Slots}");
}
catch (Exception ex)
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.Preadjustment_CompletionAsync,
"Completion",
token);
});
Log(ex.Message);
}
}
private void detectActionButton_Click(
object sender,
EventArgs e)
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
@ -239,14 +221,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
return;
}
_mainView._laatzenApi._settings =
CreatePreAdjustmentSettings(selectedSlots);
_mainView._laatzenApi._meterControls =
CreateMeterControls(selectedSlots);
PreadjustmentDetectResult result =
await _bridge.Preadjustment_DetectAsync(token);
await _bridge.PreAdjustment_DetectAsync(selectedSlots, token);
Log(
result.Success
@ -257,19 +233,84 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
});
}
private void preparationActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.PreAdjustment_PreparationAsync,
"Preparation",
token);
});
}
private void amplitudeTestActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.PreAdjustment_AmplitudeTestAsync,
"Amplitude Test",
token);
});
}
private void temperatureCalibrationActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.PreAdjustment_TemperatureCalibrationAsync,
"Temperature Calibration",
token);
});
}
private void offsetTestActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.PreAdjustment_OffsetTestAsync,
"Offset Test",
token);
});
}
private void completionActionButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
await ExecutePreadjustmentForSelectedSlotsAsync(
_bridge.PreAdjustment_CompletionAsync,
"Completion",
token);
});
}
private ProcessProgress CreatePreparationProgress()
{
return new ProcessProgress
{
Setting = new PreAdjustmentSettingsContainer
{
TempOnly = false,
Culture =
Thread.CurrentThread.CurrentCulture
Culture = Thread.CurrentThread.CurrentCulture,
MeterSize = (MeterSize)cb_Metersize.SelectedItem
},
IsAutomaticMode = true
IsAutomaticMode = false
};
}
@ -310,7 +351,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
return settings;
}
private void writeRegisterButton_Click(object sender, EventArgs e)
{
@ -367,7 +408,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
}
private async Task ExecutePreadjustmentForSelectedSlotsAsync(
Func<int, ProcessProgress, CancellationToken, Task<PreadjustmentProcessResult>> action,
Func<int, CancellationToken, Task<PreAdjustmentProcessResult>> action,
string processName,
CancellationToken token)
{
@ -385,10 +426,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
foreach (var selectedSlot in selectedSlots)
{
PreadjustmentProcessResult result =
PreAdjustmentProcessResult result =
await action(
selectedSlot.Slot,
pp,
token);
Log(result.Success

View File

@ -105,7 +105,7 @@ namespace TBF.Rig.Input.DataStorage.UniDataStorageReader.Readers
if (enableDiagnostics)
result.Diagnostics.Add("Query executed successfully.");
result.Data = val; // môže byť null → OK
result.Data = val; // can be null → OK
}
result.Success = true;