Develop - GciBridge -> GCI -> create database core in GCI - SkeletoKey - CalibParams - first ok

This commit is contained in:
Marek Frniak 2026-06-09 19:55:36 +02:00
parent 061e85ca6b
commit 2ea0df8faf
19 changed files with 1122 additions and 332 deletions

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.API
{
public class Enums
{
public enum DataStorageReaderTypes : sbyte
{
LoginPasswordsReader,
CalibrationParamsReader
}
}
}

View File

@ -1,5 +1,9 @@
using CordonelPreadjustmentUi;
using CordonelPreadjustmentUi.Processes.Itinerary;
using GenesisCordonelInterface.Core.DataStorage.Config;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords;
using GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams;
using System;
using System.Collections.Generic;
using System.IO.Ports;
@ -36,12 +40,31 @@ namespace GenesisCordonelInterface.API
/// </summary>
public event Action<List<MeterBatchDebugStatus>> MeterBatchStatusChanged;
private readonly IMeterLoginPasswordReader loginPasswordsReader;
private readonly IPreAdjustmentCalibrationParamsReader calibrationParamsReader;
/// <summary>
/// Initializes a new instance of the public GCI facade.
/// </summary>
public InterfaceOutsideToGCI()
{
_innerMeterAPI = new InterfaceGCIToLaatzen();
}
/// <summary>
/// Initializes a new instance of the public GCI facade.
/// </summary>
public InterfaceOutsideToGCI(
InterfaceGCIToLaatzen innerMeterApi,
IMeterLoginPasswordReader passwordReader,
IPreAdjustmentCalibrationParamsReader calibrationReader)
{
_innerMeterAPI = innerMeterApi?? throw new ArgumentNullException(nameof(innerMeterApi));
loginPasswordsReader = passwordReader ?? throw new ArgumentNullException(nameof(passwordReader));
calibrationParamsReader = calibrationReader ?? throw new ArgumentNullException(nameof(calibrationReader));
}
// Laatzen ToolBox actions
@ -50,7 +73,7 @@ namespace GenesisCordonelInterface.API
public PortDetectionResult DetectStreamingPort(int slot)
{
var result = _innerMeterAPI.DetectStreamingPort(slot);
var result = _innerMeterAPI?.DetectStreamingPort(slot);
//RaiseMeterBatchStatusChanged();
return result;
}
@ -59,14 +82,14 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default(CancellationToken))
{
var result = await _innerMeterAPI.DetectStreamingPortAsync(slot, token);
var result = await _innerMeterAPI?.DetectStreamingPortAsync(slot, token);
//RaiseMeterBatchStatusChanged();
return result;
}
public PortDetectionResult DetectRequestPort(int slot)
{
var result = _innerMeterAPI.DetectRequestPort(slot);
var result = _innerMeterAPI?.DetectRequestPort(slot);
//RaiseMeterBatchStatusChanged();
return result;
}
@ -75,7 +98,7 @@ namespace GenesisCordonelInterface.API
int slot,
CancellationToken token = default(CancellationToken))
{
var result = await _innerMeterAPI.DetectRequestPortAsync(slot, token);
var result = await _innerMeterAPI?.DetectRequestPortAsync(slot, token);
//RaiseMeterBatchStatusChanged();
return result;
}
@ -484,6 +507,7 @@ namespace GenesisCordonelInterface.API
return result;
}
#endregion
#region ================================== DEBUG STATUS ==================================
@ -497,7 +521,7 @@ namespace GenesisCordonelInterface.API
/// </returns>
public List<WorkerDebugStatus> GetWorkerDebugStatuses()
{
return _innerMeterAPI.GetWorkerDebugStatuses();
return _innerMeterAPI?.GetWorkerDebugStatuses();
}
/// <summary>
@ -509,7 +533,7 @@ namespace GenesisCordonelInterface.API
/// </returns>
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
{
return _innerMeterAPI.GetMeterBatchDebugStatuses();
return _innerMeterAPI?.GetMeterBatchDebugStatuses();
}
/// <summary>
@ -538,7 +562,7 @@ namespace GenesisCordonelInterface.API
/// <param name="selected">Selection state.</param>
public void SetSlotSelected(int slot, bool selected)
{
_innerMeterAPI.SetSlotSelected(slot, selected);
_innerMeterAPI?.SetSlotSelected(slot, selected);
RaiseMeterBatchStatusChanged();
}
@ -551,7 +575,7 @@ namespace GenesisCordonelInterface.API
/// </returns>
public bool IsSlotSelected(int slot)
{
return _innerMeterAPI.IsSlotSelected(slot);
return (bool)(_innerMeterAPI?.IsSlotSelected(slot));
}
/// <summary>
@ -562,7 +586,7 @@ namespace GenesisCordonelInterface.API
/// </returns>
public List<int> GetSelectedSlots()
{
return _innerMeterAPI.GetSelectedSlots();
return _innerMeterAPI?.GetSelectedSlots();
}
#endregion
@ -577,7 +601,7 @@ namespace GenesisCordonelInterface.API
/// </returns>
public List<string> GetAllRegisterNames()
{
return _innerMeterAPI.GetAllRegisterNames();
return _innerMeterAPI?.GetAllRegisterNames();
}
#endregion
@ -589,7 +613,7 @@ namespace GenesisCordonelInterface.API
ProcessProgress pp,
List<MeterStateControl> mc)
{
return _innerMeterAPI.Preadjustment_Initialization(pp, mc);
return _innerMeterAPI?.Preadjustment_Initialization(pp, mc);
}
public Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
@ -641,5 +665,62 @@ namespace GenesisCordonelInterface.API
}
#endregion
#region ================================== UNI DATA STORAGE READER ==================================
//LoginPasswords reading
/// <summary>
/// Reads meter login password from configured GCI data storage.
/// </summary>
/// <param name="query">
/// Data query containing PCB ID or another configured lookup value.
/// </param>
/// <param name="token">
/// Cancellation token used to cancel the asynchronous operation.
/// </param>
/// <returns>
/// Password if found; otherwise null.
/// </returns>
public Task<string> ReadMeterLoginPasswordAsync(
DataQuery query,
CancellationToken token = default)
{
return loginPasswordsReader.ReadMeterLoginPasswordAsync(
query,
token);
}
//CalibrationParams reading
/// <summary>
/// Reads pre-adjustment calibration parameters
/// from configured GCI data storage.
/// </summary>
/// <param name="query">
/// Data query containing meter size or another configured lookup value.
/// </param>
/// <param name="token">
/// Cancellation token used to cancel the asynchronous operation.
/// </param>
/// <returns>
/// Dictionary:
///
/// Key:
/// Calibration parameter name
///
/// Value:
/// Calibration parameter value
/// </returns>
public Task<Dictionary<string, string>> ReadPreAdjustmentCalibrationParamsAsync(
DataQuery query,
CancellationToken token = default)
{
return calibrationParamsReader.ReadCalibrationParamsAsync(
query,
token);
}
#endregion
}
}

View File

@ -1,4 +1,4 @@
namespace GenesisCordonelInterface.Core.DataStorage.Config
namespace GenesisCordonelInterface.Core.Config
{
/// <summary>
/// Root GCI configuration object loaded from gci_config.json.
@ -9,7 +9,7 @@
/// Example:
///
/// {
/// "DataStorage":
/// "DataStorageSection":
/// {
/// ...
/// }
@ -27,6 +27,6 @@
/// - PreAdjustmentCalibrationParams
/// - future storage providers
/// </summary>
public GciDataStorageConfig DataStorage { get; set; }
public DataStorage.Config.GciDataStorageConfig DataStorageSection { get; set; }
}
}

View File

@ -4,7 +4,7 @@ using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using Newtonsoft.Json;
namespace GenesisCordonelInterface.Core.DataStorage.Config
namespace GenesisCordonelInterface.Core.Config
{
/// <summary>
/// Loads GCI configuration from gci_config.json.
@ -114,15 +114,15 @@ namespace GenesisCordonelInterface.Core.DataStorage.Config
GciConfig config,
string baseDirectory)
{
if (config.DataStorage == null)
if (config.DataStorageSection == null)
return;
NormalizePath(
config.DataStorage.MeterLoginPasswords,
config.DataStorageSection.MeterLoginPasswords,
baseDirectory);
NormalizePath(
config.DataStorage.PreAdjustmentCalibrationParams,
config.DataStorageSection.PreAdjustmentCalibrationParams,
baseDirectory);
}

View File

@ -1,7 +1,7 @@
{
"_Comment": "GCI DataStorage configuration",
"DataStorage": {
"DataStorageSection": {
"MeterLoginPasswords": {
@ -21,15 +21,16 @@
"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"
"Description": "Reads pre-adjustment calibration parameters by meter size",
"Type": "Supported: LocalDatabase, RemoteDatabase, LocalCsv, LocalJson, RestApi",
"DataSource": "Database connection string",
"QueryTemplate": "QUERYPARAM is placeholder for runtime value. Example: WHERE [MeterSize]=QUERYPARAM -> meter size provided during ReadCalibrationParamsAsync()."
},
"Name": "PreAdjustmentCalibrationParams",
"Type": "LocalCsv",
"DataSource": "Data\\preadjustment_params.csv",
"QueryTemplate": "SELECT [Offset] WHERE [PcbId] = QUERYPARAM"
"Type": "LocalDatabase",
"DataSource": "Server=(localdb)\\MojaDB;Database=UnionTownCalibAndSkeleton;Integrated Security=True;",
"QueryTemplate": "SELECT [ParameterName], [ParameterValue] FROM [dbo].[PreAdjustmentCalibrationParams] WHERE [MeterSize] = QUERYPARAM"
}
}
}

View File

@ -46,5 +46,17 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
/// WHERE [PcbId]=QUERYPARAM
/// </summary>
public string QueryTemplate { get; set; }
public DataStorageConfig(
string name,
DataStorageType type,
string dataSource,
string queryTemplate)
{
Name = name;
Type = type;
DataSource = dataSource;
QueryTemplate = queryTemplate;
}
}
}

View File

@ -2,17 +2,52 @@
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models
{
public class DatabaseSearchResult
/// <summary>
/// Represents result returned by database-based
/// data storage readers.
///
/// Supports both:
///
/// - Single-row lookups
/// (e.g. MeterLoginPasswords)
///
/// - Multi-row queries
/// (e.g. PreAdjustmentCalibrationParams)
/// </summary>
internal class DatabaseSearchResult
{
/// <summary>
/// Indicates whether at least one record
/// was found.
/// </summary>
public bool Found { get; set; }
/// <summary>
/// Executed SQL query text.
/// Mainly intended for diagnostics
/// and troubleshooting.
/// </summary>
public string Query { get; set; }
public Dictionary<string, object> Values { get; set; }
/// <summary>
/// First returned row represented as
/// column/value pairs.
///
/// Preserved for backward compatibility
/// with existing readers expecting
/// a single database record.
/// </summary>
public Dictionary<string, object> Values { get; }
= new Dictionary<string, object>();
public DatabaseSearchResult()
{
Values = new Dictionary<string, object>();
}
/// <summary>
/// All returned rows represented as
/// a collection of column/value dictionaries.
///
/// Intended for queries returning
/// multiple records.
/// </summary>
public List<Dictionary<string, object>> Rows { get; }
= new List<Dictionary<string, object>>();
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
@ -14,76 +15,152 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
/// QueryTemplate must contain QUERYPARAM placeholder.
///
/// Example:
/// SELECT [Password] FROM [dbo].[SkeletonKeys] WHERE [PcbId] = QUERYPARAM
///
/// The placeholder is internally converted to SQL parameter @value.
/// SELECT [Password]
/// FROM [dbo].[SkeletonKeys]
/// WHERE [PcbId] = QUERYPARAM
///
/// or
///
/// SELECT [ParameterName], [ParameterValue]
/// FROM [dbo].[PreAdjustmentCalibrationParams]
/// WHERE [Dn_InternalId] = QUERYPARAM
///
/// The placeholder is internally converted
/// to SQL parameter @value.
///
/// Supports both:
///
/// - Single-row lookups
/// - Multi-row result sets
/// </summary>
public class DatabaseDataStorageReader : IDataStorageReader
{
/// <summary>
/// Data storage configuration containing connection string and query template.
/// Data storage configuration containing
/// connection string and query template.
/// </summary>
private readonly DataStorageConfig config;
/// <summary>
/// Creates SQL Server data storage reader using provided configuration.
/// Creates SQL Server data storage reader
/// using provided configuration.
/// </summary>
/// <param name="config">
/// Data storage configuration loaded from gci_config.json.
/// Data storage configuration loaded
/// from gci_config.json.
/// </param>
public DatabaseDataStorageReader(DataStorageConfig config)
public DatabaseDataStorageReader(
DataStorageConfig config)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
this.config =
config ?? throw new ArgumentNullException(nameof(config));
}
/// <summary>
/// Executes configured SQL query and returns first matching row.
/// Executes configured SQL query and returns
/// matching database records.
///
/// Single-row queries populate:
/// DatabaseSearchResult.Values
///
/// Multi-row queries populate:
/// DatabaseSearchResult.Rows
///
/// For backward compatibility, the first row
/// is also stored in Values.
/// </summary>
/// <param name="query">
/// Query object containing lookup parameter.
/// </param>
/// <returns>
/// DatabaseSearchResult containing returned SQL columns and values.
/// DatabaseSearchResult containing returned
/// database records.
///
/// Values contains the first returned row.
///
/// Rows contains the complete result set.
/// </returns>
public object GetData(DataQuery query)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
ReaderDiagnosticResult sourceResult = TestSource(true);
ReaderDiagnosticResult sourceResult =
TestSource(true);
if (!sourceResult.Success)
throw new InvalidOperationException(sourceResult.Message);
string sqlText = PrepareSqlText(config.QueryTemplate);
object queryValue = ExtractQueryValue(query);
string sqlText =
PrepareSqlText(config.QueryTemplate);
using (SqlConnection connection = new SqlConnection(config.DataSource))
using (SqlCommand command = new SqlCommand(sqlText, connection))
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))
// Full result set is required because some
// storage definitions return multiple records
// (e.g. PreAdjustmentCalibrationParams).
using (SqlDataReader reader =
command.ExecuteReader())
{
DatabaseSearchResult result = new DatabaseSearchResult
{
Query = sqlText
};
DatabaseSearchResult result =
new DatabaseSearchResult
{
Query = sqlText
};
if (!reader.Read())
while (reader.Read())
{
result.Found = false;
return result;
result.Found = true;
// Represents one database row.
Dictionary<string, object> row =
new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
object value =
reader.GetValue(i);
object normalizedValue =
value == DBNull.Value
? null
: value;
row[reader.GetName(i)] =
normalizedValue;
}
// Preserve first row for legacy consumers
// expecting a single returned database record
// (e.g. MeterLoginPasswordReader).
if (result.Rows.Count == 0)
{
foreach (var item in row)
{
result.Values[item.Key] =
item.Value;
}
}
// Store complete database result set.
result.Rows.Add(row);
}
result.Found = true;
for (int i = 0; i < reader.FieldCount; i++)
if (result.Rows.Count == 0)
{
object value = reader.GetValue(i);
result.Values[reader.GetName(i)] =
value == DBNull.Value ? null : value;
result.Found = false;
}
return result;
@ -92,7 +169,8 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
}
/// <summary>
/// Tests whether SQL Server connection can be opened.
/// Tests whether SQL Server connection
/// can be opened.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
@ -100,45 +178,70 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
/// <returns>
/// Diagnostic result of SQL connection test.
/// </returns>
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
public ReaderDiagnosticResult TestSource(
bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
ReaderDiagnosticResult result =
new ReaderDiagnosticResult();
try
{
if (string.IsNullOrWhiteSpace(config.DataSource))
throw new InvalidOperationException("Data source is empty.");
{
throw new InvalidOperationException(
"Data source is empty.");
}
Log(result, enableDiagnostics, "Opening SQL connection.");
Log(
result,
enableDiagnostics,
"Opening SQL connection.");
using (SqlConnection connection = new SqlConnection(config.DataSource))
using (SqlConnection connection =
new SqlConnection(config.DataSource))
{
connection.Open();
Log(result, enableDiagnostics, "Connection opened successfully.");
Log(
result,
enableDiagnostics,
"Connection opened successfully.");
using (SqlCommand command = new SqlCommand("SELECT 1", connection))
using (SqlCommand command =
new SqlCommand("SELECT 1", connection))
{
object value = command.ExecuteScalar();
Log(result, enableDiagnostics, "Test query result: " + value);
object value =
command.ExecuteScalar();
Log(
result,
enableDiagnostics,
"Test query result: " + value);
}
}
result.Success = true;
result.Message = "Connection to SQL Server OK.";
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());
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.
/// Tests whether configured SQL query
/// can be prepared and executed.
/// </summary>
/// <param name="enableDiagnostics">
/// Enables detailed diagnostic output.
@ -146,16 +249,22 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
/// <returns>
/// Diagnostic result of query execution test.
/// </returns>
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
public ReaderDiagnosticResult TestQuery(
bool enableDiagnostics)
{
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
ReaderDiagnosticResult result =
new ReaderDiagnosticResult();
try
{
if (string.IsNullOrWhiteSpace(config.QueryTemplate))
throw new InvalidOperationException("Query template is empty.");
{
throw new InvalidOperationException(
"Query template is empty.");
}
string sqlText = PrepareSqlText(config.QueryTemplate);
string sqlText =
PrepareSqlText(config.QueryTemplate);
Log(result, enableDiagnostics, "Original template:");
Log(result, enableDiagnostics, config.QueryTemplate);
@ -163,69 +272,91 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
Log(result, enableDiagnostics, "Prepared SQL:");
Log(result, enableDiagnostics, sqlText);
using (SqlConnection connection = new SqlConnection(config.DataSource))
using (SqlCommand command = new SqlCommand(sqlText, connection))
using (SqlConnection connection =
new SqlConnection(config.DataSource))
using (SqlCommand command =
new SqlCommand(sqlText, connection))
{
AddQueryParameter(command, "TEST");
connection.Open();
object value = command.ExecuteScalar();
object value =
command.ExecuteScalar();
result.Data = value;
Log(result, enableDiagnostics, "Query executed successfully.");
Log(
result,
enableDiagnostics,
"Query executed successfully.");
}
result.Success = true;
result.Message = "Query executed successfully.";
result.Message =
"Query executed successfully.";
}
catch (Exception ex)
{
result.Success = false;
result.Message = "Query execution failed. " + ex.Message;
Log(result, enableDiagnostics, ex.ToString());
result.Message =
"Query execution failed. " + ex.Message;
Log(
result,
enableDiagnostics,
ex.ToString());
}
return result;
}
/// <summary>
/// Converts configured QueryTemplate to executable SQL text.
/// Converts configured QueryTemplate
/// to executable SQL text.
/// </summary>
/// <param name="queryTemplate">
/// SQL query template containing QUERYPARAM placeholder.
/// SQL query template containing
/// QUERYPARAM placeholder.
/// </param>
/// <returns>
/// SQL text with QUERYPARAM replaced by @value parameter.
/// SQL text with QUERYPARAM replaced
/// by @value parameter.
/// </returns>
private static string PrepareSqlText(string queryTemplate)
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");
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)
private static object ExtractQueryValue(
DataQuery query)
{
if (query.QueryParams == null || query.QueryParams.Count == 0)
if (query.QueryParams == null ||
query.QueryParams.Count == 0)
{
throw new InvalidOperationException(
"DataQuery does not contain any query parameter.");
}
return query.QueryParams[0];
}
@ -233,32 +364,25 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Common.Providers
/// <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)
private static void AddQueryParameter(
SqlCommand command,
object value)
{
command.Parameters.Clear();
SqlParameter parameter = command.Parameters.Add("@value", SqlDbType.Variant);
parameter.Value = value ?? DBNull.Value;
SqlParameter parameter =
command.Parameters.Add(
"@value",
SqlDbType.Variant);
parameter.Value =
value ?? DBNull.Value;
}
/// <summary>
/// Adds diagnostic line when diagnostics are enabled.
/// 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,

View File

@ -1,4 +1,5 @@
using System.Threading;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using System.Threading;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords
@ -27,7 +28,7 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.Meter
/// <returns>
/// Password if found; otherwise null.
/// </returns>
string GetPassword(string queryParam);
string GetPassword(DataQuery query);
/// <summary>
/// Reads meter login password asynchronously.
@ -43,8 +44,8 @@ namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.Meter
/// <returns>
/// Password if found; otherwise null.
/// </returns>
Task<string> GetPasswordAsync(
string queryParam,
Task<string> ReadMeterLoginPasswordAsync(
DataQuery query,
CancellationToken token = default);
}
}

View File

@ -7,85 +7,120 @@ 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
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords
{
/// <summary>
/// Universal data storage reader selected by configuration.
/// Can represent database, CSV, JSON or REST reader.
/// Reads meter login password from configured GCI data storage.
/// </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)
public class MeterLoginPasswordReader : IMeterLoginPasswordReader
{
reader = DataStorageReaderFactory.Create(config);
}
/// <summary>
/// Universal data storage reader selected by configuration.
/// Can represent database, CSV, JSON or REST reader.
/// </summary>
private readonly IDataStorageReader reader;
/// <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));
/// <summary>
/// Ensures that only one password lookup is executed at a time.
/// </summary>
private readonly SemaphoreSlim readLock = new SemaphoreSlim(1, 1);
await readLock.WaitAsync(token);
try
/// <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)
{
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();
reader = DataStorageReaderFactory.Create(config);
}
return result?.ToString();
/// <summary>
/// Asynchronously reads meter login password using
/// the provided data query.
///
/// Thread-safe implementation may serialize
/// access to the underlying storage.
/// </summary>
/// <param name="query">
/// Data query containing lookup parameter values.
/// </param>
/// <param name="token">
/// Cancellation token.
/// </param>
/// <returns>
/// Password if found; otherwise null.
/// </returns>
public async Task<string> ReadMeterLoginPasswordAsync(
DataQuery query,
CancellationToken token = default)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
if (query.QueryParams.Count == 0)
throw new ArgumentException(
"Query does not contain any parameter.",
nameof(query));
await readLock.WaitAsync(token);
try
{
return await Task.Run(
() => GetPassword(query),
token);
}
finally
{
readLock.Release();
}
}
/// <summary>
/// Reads meter login password using
/// the provided data query.
/// </summary>
/// <param name="query">
/// Data query containing lookup parameter values.
/// </param>
/// <returns>
/// Password if found; otherwise null.
/// </returns>
public string GetPassword(
DataQuery query)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
if (query.QueryParams.Count == 0)
throw new ArgumentException(
"Query does not contain any parameter.",
nameof(query));
object result =
reader.GetData(query);
if (result is ReaderDiagnosticResult diagnosticResult)
{
return diagnosticResult.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

@ -1,43 +1,61 @@
using System.Collections.Generic;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using System.Collections.Generic;
using System.Threading;
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.
/// stored in the configured GCI data source.
///
/// Expected structure:
/// Dn_InternalId | ParameterName | ParameterValue
///
/// MeterSize | ParameterName | ParameterValue
///
/// Example:
/// 3 | GENESISFLOW_MinValidToF | 21990232
/// 3 | GENESISFLOW_Timeout | 1
///
/// Parameters are grouped by Dn_InternalId and returned
/// 2 | GENESISFLOW_MinValidToF | 21990232
/// 2 | GENESISFLOW_Timeout | 1
///
/// Parameters are grouped by MeterSize and returned
/// as key/value pairs where:
///
/// Key = ParameterName
/// Value = ParameterValue
/// </summary>
internal interface IPreAdjustmentCalibrationParamsReader
public interface IPreAdjustmentCalibrationParamsReader
{
/// <summary>
/// Reads all calibration parameters assigned
/// to a specific DN identifier.
/// Reads all pre-adjustment calibration parameters
/// using the provided data query synchronously.
/// </summary>
/// <param name="dnInternalId">
/// Internal DN identifier (meter size).
/// <param name="query">
/// Data query containing meter size as lookup parameter.
/// </param>
/// <returns>
/// Dictionary:
///
/// Key:
/// GENESISFLOW parameter name
///
/// Value:
/// Stored parameter value
/// Dictionary where key is GENESISFLOW parameter name
/// and value is stored parameter value.
/// </returns>
Task<Dictionary<string, string>> ReadParamsAsync(int dnInternalId);
Dictionary<string, string> GetCalibrationParams(
DataQuery query);
/// <summary>
/// Reads all pre-adjustment calibration parameters
/// using the provided data query asynchronously.
/// </summary>
/// <param name="query">
/// Data query containing meter size as lookup parameter.
/// </param>
/// <param name="token">
/// Cancellation token.
/// </param>
/// <returns>
/// Dictionary where key is GENESISFLOW parameter name
/// and value is stored parameter value.
/// </returns>
Task<Dictionary<string, string>> ReadCalibrationParamsAsync(
DataQuery query,
CancellationToken token = default);
}
}

View File

@ -1,87 +1,250 @@
using System;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Contracts;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams
{
/// <summary>
/// Reads pre-adjustment calibration parameters
/// from SQL database storage.
/// from configured GCI data storage.
///
/// Expected table:
/// The underlying storage type is selected
/// through IDataStorageReader and may represent:
///
/// dbo.PreAdjustmentCalibrationParams
/// - SQL database
/// - CSV
/// - JSON
/// - REST API
///
/// Columns:
/// Dn_InternalId
/// ParameterName
/// ParameterValue
/// Expected storage structure:
///
/// MeterSize | ParameterName | ParameterValue
///
/// Example:
///
/// 3 | GENESISFLOW_MinValidToF | 21990232
/// 3 | GENESISFLOW_Timeout | 1
/// 2 | GENESISFLOW_MinValidToF | 21990232
/// 2 | GENESISFLOW_Timeout | 1
///
/// Returns values as dictionary:
/// Returned data are represented as:
///
/// GENESISFLOW_MinValidToF -> 21990232
/// GENESISFLOW_Timeout -> 1
/// Key = ParameterName
/// Value = ParameterValue
/// </summary>
internal class PreAdjustmentCalibrationParamsReader: IPreAdjustmentCalibrationParamsReader
public class PreAdjustmentCalibrationParamsReader
: IPreAdjustmentCalibrationParamsReader
{
private readonly string _connectionString;
/// <summary>
/// Universal data storage reader selected by configuration.
/// Can represent database, CSV, JSON or REST reader.
/// </summary>
private readonly IDataStorageReader reader;
/// <summary>
/// Initializes calibration parameter reader.
/// Ensures that only one calibration parameter lookup
/// is executed at a time.
/// </summary>
/// <param name="connectionString">
/// SQL database connection string.
private readonly SemaphoreSlim readLock =
new SemaphoreSlim(1, 1);
/// <summary>
/// Creates calibration parameter reader using
/// provided data storage configuration.
/// </summary>
/// <param name="config">
/// Data storage configuration loaded from gci_config.json.
/// </param>
public PreAdjustmentCalibrationParamsReader(
string connectionString)
DataStorageConfig config)
{
_connectionString = connectionString?? throw new ArgumentNullException(nameof(connectionString));
if (config == null)
throw new ArgumentNullException(nameof(config));
reader = DataStorageReaderFactory.Create(config);
}
/// <inheritdoc/>
public async Task<Dictionary<string, string>> ReadParamsAsync(int dnInternalId)
/// <summary>
/// Reads all pre-adjustment calibration parameters
/// using the provided data query asynchronously.
///
/// The query is expected to contain meter size
/// as the first lookup parameter.
/// </summary>
/// <param name="query">
/// Data query containing lookup parameter values.
/// </param>
/// <param name="token">
/// Cancellation token.
/// </param>
/// <returns>
/// Dictionary:
///
/// Key:
/// GENESISFLOW parameter name
///
/// Value:
/// Stored parameter value
/// </returns>
public async Task<Dictionary<string, string>> ReadCalibrationParamsAsync(
DataQuery query,
CancellationToken token = default)
{
var result = new Dictionary<string, string>();
ValidateQuery(query);
const string query = @"
SELECT ParameterName, ParameterValue
FROM dbo.PreAdjustmentCalibrationParams
WHERE Dn_InternalId=@Dn_InternalId";
await readLock.WaitAsync(token);
using (var connection = new SqlConnection(_connectionString))
using (var command = new SqlCommand(query, connection))
try
{
command.Parameters.AddWithValue(
"@Dn_InternalId",
dnInternalId);
return await Task.Run(
() => GetCalibrationParams(query),
token);
}
finally
{
readLock.Release();
}
}
await connection.OpenAsync();
/// <summary>
/// Reads all pre-adjustment calibration parameters
/// using the provided data query.
///
/// The query is expected to contain meter size
/// as the first lookup parameter.
/// </summary>
/// <param name="query">
/// Data query containing lookup parameter values.
/// </param>
/// <returns>
/// Dictionary:
///
/// Key:
/// GENESISFLOW parameter name
///
/// Value:
/// Stored parameter value
/// </returns>
public Dictionary<string, string> GetCalibrationParams(
DataQuery query)
{
ValidateQuery(query);
using (var reader =
await command.ExecuteReaderAsync())
object result =
reader.GetData(query);
return ConvertResultToDictionary(result);
}
/// <summary>
/// Validates that data query exists
/// and contains at least one lookup parameter.
/// </summary>
/// <param name="query">
/// Data query to validate.
/// </param>
private static void ValidateQuery(
DataQuery query)
{
if (query == null)
throw new ArgumentNullException(nameof(query));
if (query.QueryParams == null ||
query.QueryParams.Count == 0)
{
throw new ArgumentException(
"Query does not contain any parameter.",
nameof(query));
}
}
/// <summary>
/// Converts provider-specific result objects
/// returned by IDataStorageReader into a unified
/// dictionary representation.
/// </summary>
/// <param name="result">
/// Raw result returned by the configured storage reader.
/// </param>
/// <returns>
/// Dictionary containing calibration parameter
/// name/value pairs.
/// </returns>
private Dictionary<string, string> ConvertResultToDictionary(
object result)
{
Dictionary<string, string> values =
new Dictionary<string, string>();
if (result == null)
return values;
if (result is DatabaseSearchResult dbResult)
{
if (!dbResult.Found)
return values;
foreach (Dictionary<string, object> row in dbResult.Rows)
{
while (await reader.ReadAsync())
if (!row.TryGetValue(
"ParameterName",
out object parameterNameObject))
{
var parameterName = reader["ParameterName"].ToString();
var parameterValue = reader["ParameterValue"].ToString();
if (!string.IsNullOrWhiteSpace(parameterName))
{
result[parameterName] = parameterValue;
}
continue;
}
if (!row.TryGetValue(
"ParameterValue",
out object parameterValueObject))
{
continue;
}
string parameterName =
parameterNameObject?.ToString();
if (string.IsNullOrWhiteSpace(parameterName))
continue;
values[parameterName] =
parameterValueObject?.ToString();
}
return values;
}
if (result is ReaderDiagnosticResult diagnosticResult)
{
if (diagnosticResult.Data is Dictionary<string, string> dictionary)
return dictionary;
if (diagnosticResult.Data is Dictionary<string, object> objectDictionary)
{
foreach (var item in objectDictionary)
{
values[item.Key] =
item.Value?.ToString();
}
return values;
}
}
return result;
if (result is Dictionary<string, string> directDictionary)
return directDictionary;
if (result is Dictionary<string, object> directObjectDictionary)
{
foreach (var item in directObjectDictionary)
{
values[item.Key] =
item.Value?.ToString();
}
}
return values;
}
}
}

View File

@ -0,0 +1,73 @@
using GenesisCordonelInterface.API;
using GenesisCordonelInterface.Core.Config;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using UdsReaderType_CalibrationParams = GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams.PreAdjustmentCalibrationParamsReader;
using UdsReaderType_LoginPasswords = GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords.MeterLoginPasswordReader;
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
using GciEnums = GenesisCordonelInterface.API.Enums;
using GciType = GenesisCordonelInterface.API.InterfaceOutsideToGCI;
using GciDataStorageReadingModels = GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GciGUIType = GenesisCordonelInterface.UI.MainView;
namespace GenesisCordonelInterface.Core
{
public class Engine
{
GciConfig gciConfig;
public UdsReaderType_LoginPasswords loginPasswordsDataStorageReader;
public UdsReaderType_CalibrationParams calibrationParamsStorageReader;
//readonly UdsWriterType writer;
//diag GUI for GCI
GciGUIType gciGUI;
Form gciGuiHostForm;
//diag GUI for GciBridge
public UserControl gciBridgeGUIUserControl;
public UI.MainForm gciBridgeGuiForm;
public InterfaceOutsideToGCI gciExternalInterface;
/// <summary>
/// Initializes a new instance of the public GCI facade.
/// </summary>
public Engine()
{
gciConfig = GciConfigLoader.LoadDefault();
loginPasswordsDataStorageReader = new UdsReaderType_LoginPasswords
(
new GciDataStorageReadingModels.DataStorageConfig
(
nameof(GciEnums.DataStorageReaderTypes.LoginPasswordsReader),
GciDataStorageReadingModels.DataStorageType.LocalDatabase,
gciConfig.DataStorageSection.MeterLoginPasswords.DataSource,
gciConfig.DataStorageSection.MeterLoginPasswords.QueryTemplate
)
);
calibrationParamsStorageReader = new UdsReaderType_CalibrationParams
(
new GciDataStorageReadingModels.DataStorageConfig
(
nameof(GciEnums.DataStorageReaderTypes.CalibrationParamsReader),
GciDataStorageReadingModels.DataStorageType.LocalDatabase,
gciConfig.DataStorageSection.PreAdjustmentCalibrationParams.DataSource,
gciConfig.DataStorageSection.PreAdjustmentCalibrationParams.QueryTemplate
)
);
gciExternalInterface = new InterfaceOutsideToGCI(
new InterfaceGCIToLaatzen(),
loginPasswordsDataStorageReader,
calibrationParamsStorageReader);
}
}
}

View File

@ -60,9 +60,10 @@
<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\Config\GciConfig.cs" />
<Compile Include="Core\Config\GciConfigLoader.cs" />
<Compile Include="Core\DataStorage\Config\GciDataStorageConfig.cs" />
<Compile Include="API\Enums.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" />
@ -80,6 +81,7 @@
<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\Engine\Engine.cs" />
<Compile Include="Core\Logging\UiLogBus.cs" />
<Compile Include="Core\Logging\UiTarget.cs" />
<Compile Include="Core\Threading\ApiWorker\ApiWorker.cs" />
@ -198,7 +200,8 @@
<EmbeddedResource Include="UI\StaraTuraAPI_GenesisCordonelInterface\FrmGCIAPI.resx">
<DependentUpon>FrmGCIAPI.cs</DependentUpon>
</EmbeddedResource>
<Content Include="Config\gci_config.json">
<Content Include="Core\Config\gci_config.json">
<Link>Config\gci_config.json</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="docs\articles\API\index.md" />

View File

@ -1,8 +1,12 @@
using CordonelPreadjustmentUi;
using CordonelPreadjustmentUi.Processes.Itinerary;
using GenesisCordonelInterface.API;
using GenesisCordonelInterface.Core;
using GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using GenesisCordonelInterface.Core.Threading;
using GraphLib;
using log4net;
using NHibernate.Mapping;
///
/// Copyright (c) 2015-2021 Sensus Slovensko a.s.
///
@ -20,10 +24,14 @@ using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Ui.CordonelPreadjustmentUi;
using static GenesisCordonelInterface.API.PublicModels;
using static TBF.Rig.BridgeComponents.GciBridge.Interfaces.PublicModels;
using GciEngine = GenesisCordonelInterface.Core.Engine;
using GciEnums = GenesisCordonelInterface.API.Enums;
using GciGUIType = GenesisCordonelInterface.UI.MainView;
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
using GciType = GenesisCordonelInterface.API.InterfaceOutsideToGCI;
using UdsReaderType = TBF.Rig.Input.DataStorage.UniDataStorageReader.Reader;
using GciUDSRPublicModels = GenesisCordonelInterface.Core.DataStorage.Reading.Common.Models;
using UdsReaderType_CalibrationParams = GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.PreAdjustmentCalibrationParams.PreAdjustmentCalibrationParamsReader;
using UdsReaderType_LoginPasswords = GenesisCordonelInterface.Core.DataStorage.Reading.Implementation.MeterLoginPasswords.MeterLoginPasswordReader;//TBF.Rig.Input.DataStorageSection.UniDataStorageReader.Reader;
using UDSRPublicModels = TBF.Rig.Input.DataStorage.UniDataStorageReader.Interfaces.PublicModels;
using UdsWriterType = TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
@ -44,9 +52,13 @@ namespace TBF.Rig.BridgeComponents.GciBridge
readonly GciBridgeCfg gciBridgeCfg;
readonly UdsReaderType reader;
//readonly UdsReaderType_LoginPasswords loginPasswordsDataStorageReader;
//readonly UdsReaderType_CalibrationParams calibrationParamsStorageReader;
readonly UdsWriterType writer;
//GCI main init
GciEngine _gciEngine;
//diag GUI for GCI
GciGUIType gciGUI;
Form gciGuiHostForm;
@ -55,18 +67,18 @@ namespace TBF.Rig.BridgeComponents.GciBridge
public UserControl gciBridgeGUIUserControl;
public UI.MainForm gciBridgeGuiForm;
public GciType gciExternalInterface;
public bool HasReader { get { return reader != null; } }
public bool HasWriter { get { return writer != null; } }
public bool IsGuiInitialized { get { return gciGUI != null; } }
public bool IsExternalInitialized { get { return gciExternalInterface != null; } }
public GciType GciExternalInterface { get { return gciExternalInterface; } }
public bool IsExternalInitialized { get { return _gciEngine.gciExternalInterface != null; } }
public GciType gciExternalInterface { get { return _gciEngine.gciExternalInterface; } }
public GciBridgeCfg GciBridgeCfg { get { return gciBridgeCfg; } }
public UdsReaderType GetReader()
public UdsReaderType_LoginPasswords GetLoginPasswordsDataStorageReader()
{
return reader;
return _gciEngine.loginPasswordsDataStorageReader;
}
public UdsReaderType_CalibrationParams GetCalibrationParamsStorageReader()
{
return _gciEngine.calibrationParamsStorageReader;
}
public UdsWriterType GetWriter()
@ -82,9 +94,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge
gciBridgeCfg = cfg as GciBridgeCfg;
if (gciBridgeCfg == null) throw new Exception("Invalid GciBridgeCfg.");
if (!string.IsNullOrEmpty(gciBridgeCfg.ReaderName))
/*if (!string.IsNullOrEmpty(gciBridgeCfg.ReaderName))
{
reader = TbfComponents.FindComponent(gciBridgeCfg.ReaderName, components) as UdsReaderType;
reader = TbfComponents.FindComponent(gciBridgeCfg.ReaderName, components) as UdsReaderType_LoginPasswords;
if (reader == null) throw new Exception("Cannot find reader component '" + gciBridgeCfg.ReaderName + "'");
}
@ -92,7 +104,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge
{
writer = TbfComponents.FindComponent(gciBridgeCfg.WriterName, components) as UdsWriterType;
if (writer == null) throw new Exception("Cannot find writer component '" + gciBridgeCfg.WriterName + "'");
}
}*/
_gciEngine = new GciEngine();
Initialize();
}
@ -179,10 +193,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge
{
try
{
if (gciExternalInterface != null)
if (_gciEngine.gciExternalInterface != null)
return;
gciExternalInterface = new GciType();
_gciEngine.gciExternalInterface = new GciType();
log.InfoFormat("{0}: GCI external interface initialized.", Name);
}
@ -255,14 +269,15 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (!IsExternalInitialized)
TryInitializeExternalInterface();
if (gciExternalInterface == null)
if (_gciEngine.gciExternalInterface == null)
throw new Exception("GCI external interface is not initialized.");
}
void EnsureReader()
{
if (reader == null)
throw new Exception("UniDataStorageReader is not linked to GciBridge.");
/*if (reader == null)
throw new Exception("UniDataStorageReader is not linked to GciBridge.");*/
}
// API:
@ -293,7 +308,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (request.SlotId <= 0)
throw new ArgumentException("Invalid slot id.", nameof(request));
var result = await gciExternalInterface
var result = await _gciEngine.gciExternalInterface
.InitSlotAsync(request, token)
.ConfigureAwait(false);
@ -374,7 +389,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (request.SlotId <= 0)
throw new ArgumentException("Invalid slot id.", nameof(request));
var result = await gciExternalInterface
var result = await _gciEngine.gciExternalInterface
.UpdateSlotAsync(request, token)
.ConfigureAwait(false);
@ -450,7 +465,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
throw new ArgumentException("Invalid slot id.");
GciPublicModels.GciSlotInfo result =
await gciExternalInterface.GetSlotAsync(slotId, token);
await _gciEngine.gciExternalInterface.GetSlotAsync(slotId, token);
log.InfoFormat("{0}: GetSlotAsync({1}) invoked. Result={2}", Name, slotId, result);
@ -526,7 +541,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
throw new ArgumentException("Invalid slot id.", nameof(slot));
GciPublicModels.GciCleanSlotResult result =
await gciExternalInterface.CleanSlotAsync(slot, token);
await _gciEngine.gciExternalInterface.CleanSlotAsync(slot, token);
log.InfoFormat("{0}: CleanSlotAsync invoked. Slot={1}, Result={2}", Name, slot, result);
@ -599,7 +614,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
EnsureExternalInterface();
GciPublicModels.GciCleanAllSlotsResult result =
await gciExternalInterface.CleanAllSlotsAsync(token);
await _gciEngine.gciExternalInterface.CleanAllSlotsAsync(token);
log.InfoFormat("{0}: CleanAllSlotsAsync invoked.", Name);
@ -673,7 +688,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.", nameof(slotId));
var result = await gciExternalInterface
var result = await _gciEngine.gciExternalInterface
.GetPcbIdAsync(slotId, token)
.ConfigureAwait(false);
@ -790,7 +805,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (slot <= 0)
throw new ArgumentException("Invalid slot id.", nameof(slot));
var result = await gciExternalInterface
var result = await _gciEngine.gciExternalInterface
.ConnectOneSlotAsync(slot, token)
.ConfigureAwait(false);
@ -822,7 +837,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await gciExternalInterface.LoginOneSlotAsync(slotId, token);
var result = await _gciEngine.gciExternalInterface.LoginOneSlotAsync(slotId, token);
log.InfoFormat("{0}: LoginAsync({1}) invoked. Result={2}", Name, slotId, result);
@ -893,7 +908,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await gciExternalInterface.DisconnectAsync(slotId, token);
var result = await _gciEngine.gciExternalInterface.DisconnectAsync(slotId, token);
log.InfoFormat("{0}: DisconnectAsync({1}) invoked. Result={2}", Name, slotId, result);
@ -976,7 +991,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
throw new ArgumentException("Password is empty.");
GciPublicModels.GciSetPasswordResult result =
await gciExternalInterface.SetPasswordAsync(slotId, password, token);
await _gciEngine.gciExternalInterface.SetPasswordAsync(slotId, password, token);
log.InfoFormat("{0}: SetPasswordAsync({1}) invoked. Result={2}", Name, slotId, result);
@ -1051,7 +1066,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (string.IsNullOrWhiteSpace(registerName))
throw new ArgumentException("Register name is empty.");
var result = await gciExternalInterface.ReadRegisterAsync(slotId, registerName, token);
var result = await _gciEngine.gciExternalInterface.ReadRegisterAsync(slotId, registerName, token);
log.InfoFormat("{0}: ReadRegisterAsync({1}, {2}) invoked. Result={3}",
Name, slotId, registerName, result);
@ -1141,7 +1156,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
if (string.IsNullOrWhiteSpace(registerName))
throw new ArgumentException("Register name is empty.");
var result = await gciExternalInterface.WriteRegisterAsync(
var result = await _gciEngine.gciExternalInterface.WriteRegisterAsync(
slotId,
registerName,
value,
@ -1222,7 +1237,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
/// Reads password data from UniDataStorageReader by PCB ID.
///
/// Trace:
/// GciBridge.GetPasswordAsync()
/// GciBridge.ReadMeterLoginPasswordAsync()
/// -> UniDataStorageReader.Reader.GetDataFromStorageByParameterAsync()
/// -> Reader queue/lock
/// -> Reader.GetDataFromStorageByParameter()
@ -1239,13 +1254,13 @@ namespace TBF.Rig.BridgeComponents.GciBridge
EnsureReader();
if (string.IsNullOrWhiteSpace(pcbId))
throw new ArgumentException("PCB ID is empty.", nameof(pcbId));
throw new ArgumentException("PCB ID is empty.", nameof(pcbId));
try
{
UDSRPublicModels.DataQuery query = CreatePasswordQuery(pcbId);
GciUDSRPublicModels.DataQuery query = CreatePasswordQuery(pcbId);
object data = await reader.GetDataFromStorageByParameterAsync(query, token);
object data = await _gciEngine.gciExternalInterface.ReadMeterLoginPasswordAsync(query, token);
string password = ExtractPassword(data);
@ -1261,7 +1276,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
}
catch (Exception ex)
{
log.Error("GetPasswordAsync failed.", ex);
log.Error("ReadMeterLoginPasswordAsync failed.", ex);
return new UdsPasswordResult
{
@ -1273,7 +1288,128 @@ namespace TBF.Rig.BridgeComponents.GciBridge
}
}
/// <summary>
/// Reads pre-adjustment calibration parameters
/// from UniDataStorageReader by meter size.
///
/// Trace:
/// GciBridge.GetPreAdjustmentCalibrationParamsAsync()
/// -> UniDataStorageReader.Reader.GetDataFromStorageByParameterAsync()
/// -> Reader queue/lock
/// -> Reader.GetDataFromStorageByParameter()
/// -> selected storage reader by configuration
/// -> DatabaseReader.GetData()
/// -> RestApiReader.GetData()
/// -> JsonReader.GetData()
/// -> CsvReader.GetData()
///
/// Expected result:
///
/// Key:
/// Calibration parameter name
///
/// Value:
/// Calibration parameter value
///
/// Example:
///
/// GENESISFLOW_MinValidToF -> 13743895
/// GENESISFLOW_Timeout -> 1
/// </summary>
/// <param name="meterSize">
/// Meter size used as query parameter.
/// </param>
/// <param name="token">
/// Cancellation token.
/// </param>
/// <returns>
/// Calibration parameter lookup result.
/// </returns>
public async Task<UdsPreAdjustmentCalibrationParamsResult> GetPreAdjustmentCalibrationParamsAsync(
int meterSize,
CancellationToken token = default)
{
EnsureReader();
if (meterSize < 0)
throw new ArgumentException(
"Meter size must not be negative.",
nameof(meterSize));
try
{
GciUDSRPublicModels.DataQuery query = CreatePreAdjustmentCalibrationParamsQuery(meterSize);
object data = await _gciEngine.gciExternalInterface.ReadPreAdjustmentCalibrationParamsAsync(query, token);
Dictionary<string, string> calibrationParams =
ExtractPreAdjustmentCalibrationParams(data);
return new UdsPreAdjustmentCalibrationParamsResult
{
Success = calibrationParams != null &&
calibrationParams.Count > 0,
MeterSize = meterSize,
CalibrationParams = calibrationParams,
Message = calibrationParams != null &&
calibrationParams.Count > 0
? "Pre-adjustment calibration parameters found."
: "Pre-adjustment calibration parameters were not found."
};
}
catch (Exception ex)
{
log.Error(
"GetPreAdjustmentCalibrationParamsAsync failed.",
ex);
return new UdsPreAdjustmentCalibrationParamsResult
{
Success = false,
MeterSize = meterSize,
CalibrationParams = null,
Message = ex.Message
};
}
}
private GciUDSRPublicModels.DataQuery CreatePreAdjustmentCalibrationParamsQuery(
int meterSize)
{
GciUDSRPublicModels.DataQuery query =
new GciUDSRPublicModels.DataQuery();
query.QueryParams.Add(
meterSize.ToString());
return query;
}
private Dictionary<string, string> ExtractPreAdjustmentCalibrationParams(
object data)
{
Dictionary<string, string> result =
new Dictionary<string, string>();
if (data == null)
return result;
if (data is Dictionary<string, string> directDictionary)
return directDictionary;
if (data is Dictionary<string, object> objectDictionary)
{
foreach (var item in objectDictionary)
{
result[item.Key] =
item.Value?.ToString();
}
return result;
}
return result;
}
/// <summary>
/// Reads password from UniDataStorageReader using PCB ID with retry support.
@ -1288,7 +1424,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
///
/// GetPasswordWithRetryAsync()
/// -> RetryWorker.RunWithRetryAsync()
/// -> GetPasswordAsync()
/// -> ReadMeterLoginPasswordAsync()
/// -> UniDataStorageReader
///
/// Returns:
@ -1310,7 +1446,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
r => r.Success,
msg => log.Info(msg),
(msg, result) => log.InfoFormat("{0}: {1}", msg, result),
$"GetPasswordAsync pcb {pcbId}",
$"ReadMeterLoginPasswordAsync pcb {pcbId}",
maxAttempts: 5,
delayMs: 5,
timeoutMs: 30000);
@ -1362,7 +1498,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
/// -> RetryWorker.RunWithRetryAsync()
/// -> ConnectAsync()
/// -> GetPcbIdAsync()
/// -> GetPasswordAsync()
/// -> ReadMeterLoginPasswordAsync()
/// -> SetPasswordAsync()
/// -> LoginAsync()
///
@ -1393,7 +1529,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
RetryWorker.EnsureSuccess(finalResult.PcbResult, $"GetPcbIdAsync slot {slotId}");
finalResult.PasswordResult = await GetPasswordWithRetryAsync(finalResult.PcbResult.Result.PcbId, token);
RetryWorker.EnsureSuccess(finalResult.PasswordResult, $"GetPasswordAsync slot {slotId}");
RetryWorker.EnsureSuccess(finalResult.PasswordResult, $"ReadMeterLoginPasswordAsync slot {slotId}");
finalResult.SetPasswordResult = await SetPasswordWithRetryAsync(slotId, finalResult.PasswordResult.Result.Password, token);
RetryWorker.EnsureSuccess(finalResult.SetPasswordResult, $"SetPasswordAsync slot {slotId}");
@ -1442,7 +1578,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
Name,
operation);
PreAdjustmentInitializationResult result = gciExternalInterface.Preadjustment_Initialization(pp, mc);
PreAdjustmentInitializationResult result = _gciEngine.gciExternalInterface.Preadjustment_Initialization(pp, mc);
log.InfoFormat(
"{0}: {1} Finish. {2}",
@ -1486,7 +1622,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
log.InfoFormat("{0}: {1} Start.", Name, operation);
PreadjustmentDetectResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_DetectAsync(selectedSlots, token)
.ConfigureAwait(false);
@ -1533,7 +1669,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
slot);
PreAdjustmentProcessResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_PreparationAsync(
slot,
token)
@ -1584,7 +1720,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
slot);
PreAdjustmentProcessResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_AmplitudeTestAsync(
slot,
token)
@ -1635,7 +1771,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
slot);
PreAdjustmentProcessResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_TemperatureCalibrationAsync(
slot,
token)
@ -1680,7 +1816,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
operation,
temperature);
return gciExternalInterface.PreAdjustment_PushTemperature(temperature);
return _gciEngine.gciExternalInterface.PreAdjustment_PushTemperature(temperature);
}
catch (Exception ex)
{
@ -1713,7 +1849,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
slot);
PreAdjustmentProcessResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_OffsetTestAsync(
slot,
token)
@ -1764,7 +1900,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
slot);
PreAdjustmentProcessResult result =
await gciExternalInterface
await _gciEngine.gciExternalInterface
.PreAdjustment_CompletionAsync(
slot,
token)
@ -1797,9 +1933,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge
#endregion
#region ======================================= Helpers =======================================
private UDSRPublicModels.DataQuery CreatePasswordQuery(string pcbId)
private GciUDSRPublicModels.DataQuery CreatePasswordQuery(string pcbId)
{
var query = new UDSRPublicModels.DataQuery();
var query = new GciUDSRPublicModels.DataQuery();
query.QueryParams.Add(pcbId);
return query;
}
@ -1834,7 +1970,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge
public List<string> GetAllRegisterNames()
{
EnsureExternalInterface();
return gciExternalInterface.GetAllRegisterNames();
return _gciEngine.gciExternalInterface.GetAllRegisterNames();
}
}
}

View File

@ -95,6 +95,36 @@ namespace TBF.Rig.BridgeComponents.GciBridge.Interfaces
}
}
/// <summary>
/// Represents result of pre-adjustment calibration
/// parameter lookup.
///
/// Parameters are returned as key/value pairs:
///
/// Key:
/// Calibration parameter name
///
/// Value:
/// Calibration parameter value
/// </summary>
public class UdsPreAdjustmentCalibrationParamsResult
{
public bool Success { get; set; }
public int MeterSize { get; set; }
public Dictionary<string, string> CalibrationParams { get; set; }
public string Message { get; set; }
public override string ToString()
{
if (CalibrationParams == null || CalibrationParams.Count == 0)
{
return $"Success={Success}, MeterSize={MeterSize}, Message={Message}";
}
return $"Success={Success}, MeterSize={MeterSize}, Params={CalibrationParams.Count}, Message={Message}";
}
}
public class GciFullLoginResult
{
public int SlotId { get; set; }

View File

@ -90,7 +90,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
int slot = item.Key;
string pcbId = item.Value;
//var result = await _bridge.GetPasswordAsync(pcbId, token);
//var result = await _bridge.ReadMeterLoginPasswordAsync(pcbId, token);
var result = await _bridge.GetPasswordWithRetryAsync(pcbId, token);
LogResult($"UDSR/GetPassword slot {slot}, PCB={pcbId}", result);

View File

@ -21,31 +21,53 @@
private void InitializeComponent()
{
this.grpStorage = new System.Windows.Forms.GroupBox();
this.lblPcbId = new System.Windows.Forms.Label();
this.txtPcbId = new System.Windows.Forms.TextBox();
this.btnGetPasswordByPcb = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.txtLog = new System.Windows.Forms.TextBox();
this.lblPcbId = new System.Windows.Forms.Label();
this.txtPcbId = new System.Windows.Forms.TextBox();
this.getCalibrationParamsButton = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.calibrationParamsTextBox = new System.Windows.Forms.TextBox();
this.grpStorage.SuspendLayout();
this.SuspendLayout();
//
// grpStorage
//
this.grpStorage.Controls.Add(this.calibrationParamsTextBox);
this.grpStorage.Controls.Add(this.label1);
this.grpStorage.Controls.Add(this.getCalibrationParamsButton);
this.grpStorage.Controls.Add(this.lblPcbId);
this.grpStorage.Controls.Add(this.txtPcbId);
this.grpStorage.Controls.Add(this.btnGetPasswordByPcb);
this.grpStorage.Location = new System.Drawing.Point(10, 37);
this.grpStorage.Name = "grpStorage";
this.grpStorage.Size = new System.Drawing.Size(200, 261);
this.grpStorage.Size = new System.Drawing.Size(215, 261);
this.grpStorage.TabIndex = 0;
this.grpStorage.TabStop = false;
this.grpStorage.Text = "UniDataStorageReader for GCI";
//
// lblPcbId
//
this.lblPcbId.AutoSize = true;
this.lblPcbId.Location = new System.Drawing.Point(10, 25);
this.lblPcbId.Name = "lblPcbId";
this.lblPcbId.Size = new System.Drawing.Size(45, 13);
this.lblPcbId.TabIndex = 0;
this.lblPcbId.Text = "PCB ID:";
//
// txtPcbId
//
this.txtPcbId.Location = new System.Drawing.Point(65, 22);
this.txtPcbId.Name = "txtPcbId";
this.txtPcbId.Size = new System.Drawing.Size(120, 20);
this.txtPcbId.TabIndex = 1;
//
// btnGetPasswordByPcb
//
this.btnGetPasswordByPcb.Location = new System.Drawing.Point(10, 55);
this.btnGetPasswordByPcb.Location = new System.Drawing.Point(13, 48);
this.btnGetPasswordByPcb.Name = "btnGetPasswordByPcb";
this.btnGetPasswordByPcb.Size = new System.Drawing.Size(175, 28);
this.btnGetPasswordByPcb.Size = new System.Drawing.Size(196, 28);
this.btnGetPasswordByPcb.TabIndex = 2;
this.btnGetPasswordByPcb.Text = "Get password (by PCB)";
this.btnGetPasswordByPcb.UseVisualStyleBackColor = true;
@ -64,30 +86,40 @@
//
// txtLog
//
this.txtLog.Location = new System.Drawing.Point(220, 10);
this.txtLog.Location = new System.Drawing.Point(231, 10);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.txtLog.Size = new System.Drawing.Size(500, 340);
this.txtLog.Size = new System.Drawing.Size(489, 340);
this.txtLog.TabIndex = 4;
this.txtLog.WordWrap = false;
//
// lblPcbId
// getCalibrationParamsButton
//
this.lblPcbId.AutoSize = true;
this.lblPcbId.Location = new System.Drawing.Point(10, 25);
this.lblPcbId.Name = "lblPcbId";
this.lblPcbId.Size = new System.Drawing.Size(45, 13);
this.lblPcbId.TabIndex = 0;
this.lblPcbId.Text = "PCB ID:";
this.getCalibrationParamsButton.Location = new System.Drawing.Point(13, 139);
this.getCalibrationParamsButton.Name = "getCalibrationParamsButton";
this.getCalibrationParamsButton.Size = new System.Drawing.Size(196, 34);
this.getCalibrationParamsButton.TabIndex = 3;
this.getCalibrationParamsButton.Text = "Get CalibrationParams (by MeterSize)";
this.getCalibrationParamsButton.UseVisualStyleBackColor = true;
this.getCalibrationParamsButton.Click += new System.EventHandler(this.getCalibrationParamsButton_Click);
//
// txtPcbId
// label1
//
this.txtPcbId.Location = new System.Drawing.Point(65, 22);
this.txtPcbId.Name = "txtPcbId";
this.txtPcbId.Size = new System.Drawing.Size(120, 20);
this.txtPcbId.TabIndex = 1;
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(10, 116);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(54, 13);
this.label1.TabIndex = 4;
this.label1.Text = "MeterSize";
//
// calibrationParamsTextBox
//
this.calibrationParamsTextBox.Location = new System.Drawing.Point(65, 113);
this.calibrationParamsTextBox.Name = "calibrationParamsTextBox";
this.calibrationParamsTextBox.Size = new System.Drawing.Size(120, 20);
this.calibrationParamsTextBox.TabIndex = 5;
//
// UniDataSorageActionsView
//
@ -105,5 +137,8 @@
private System.Windows.Forms.Label lblPcbId;
private System.Windows.Forms.TextBox txtPcbId;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button getCalibrationParamsButton;
private System.Windows.Forms.TextBox calibrationParamsTextBox;
}
}

View File

@ -30,7 +30,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
var result = await _bridge.GetPasswordAsync(pcbId, token);
LogResult("GetPasswordAsync PCB=" + pcbId, result);
LogResult("ReadMeterLoginPasswordAsync PCB=" + pcbId, result);
});
}
@ -100,5 +100,31 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
message +
Environment.NewLine);
}
private void getCalibrationParamsButton_Click(
object sender,
EventArgs e)
{
ExecuteAsync(async token =>
{
if (!int.TryParse(
calibrationParamsTextBox.Text.Trim(),
out int meterSize))
{
throw new Exception(
"Meter size is invalid.");
}
var result =
await _bridge.GetPreAdjustmentCalibrationParamsAsync(
meterSize,
token);
LogResult(
"GetPreAdjustmentCalibrationParamsAsync MeterSize=" +
meterSize,
result);
});
}
}
}