Cause: - Preadjustment Completion and TBF Q3 calibration results required configurable database persistence. - Both write operations required diagnostics and simulation support. Solution: 1. Added independent database writer configurations - Added PreadjustmentCompletionWriter and Q3CalibrationWriter to gci_config. - Support SQL commands, stored procedures and parameter mappings. - Added SQL table creation and verification scripts. 2. Added Preadjustment Completion storage - Store PCB ID, meter size, amplitude settings, temperature offsets, zero-flow offsets, timestamp and overall result. - Use captured Completion readout values. - Preserve the original HTTP reporting. - Report SQL connection and write status in the corresponding slot diagnostics. 3. Added Q3 calibration storage - Forward TBF results through GCI Bridge to the GCI database API. - Store signed deviations and written calibration factors for all three paths. - Write to SQL after successful calibration register writes and StoreCalibration. - Include PCB ID and UTC timestamp. 4. Added simulation support - Generate sample Completion and Q3 records for simulated slots. - Perform real SQL writes when the corresponding writer is enabled. - Log connection progress, write outcomes and generated record IDs. 5. Separated calibration and database outcomes - Preserve meter operation results when database storage fails. - Expose database write status independently. 6. Increased revision - Updated revision from 3.9.3146.108 to 3.9.3146.109. Validation: - Confirmed successful SQL storage for simulated Completion. - Confirmed successful Q3 calibration result storage. ToDo: - Use the GCI Q3 storage API as the foundation for future migration of Q3 calibration from the direct TBF communication branch to GCI.
637 lines
23 KiB
C#
637 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
|
using static GenesisCordonelInterface.API.InterfaceOutsideToGCI;
|
|
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
|
|
|
|
namespace GenesisCordonelInterface.API
|
|
{
|
|
/// <summary>
|
|
/// Public data contract layer for the Genesis Cordonel Interface (GCI).
|
|
///
|
|
/// This class defines all Data Transfer Objects (DTOs) that are exposed
|
|
/// to external consumers (e.g. TBF, UI, or other integration layers).
|
|
///
|
|
/// Responsibilities:
|
|
/// - Provide stable, dependency-free models for external usage
|
|
/// - Decouple internal GCI implementation (GenesisMeter, Xylem libraries)
|
|
/// from external systems
|
|
/// - Define request/response contracts for all supported operations
|
|
/// - Contain mapping methods between public DTOs and internal domain models
|
|
///
|
|
/// Architecture:
|
|
/// External world (TBF / UI)
|
|
/// ↓
|
|
/// GciPublicModels (this layer)
|
|
/// ↓
|
|
/// Internal GCI API (InterfaceGCIToLaatzen, GenesisMeter, etc.)
|
|
///
|
|
/// Notes:
|
|
/// - Public models must NOT expose internal types (e.g. GenesisMeter, IPort, etc.)
|
|
/// - All mapping between internal and external representations must be done here
|
|
/// - DTOs are designed to be simple, serializable, and stable over time
|
|
/// - Any change in internal implementation should not affect these models
|
|
///
|
|
/// Pattern:
|
|
/// Each operation follows a consistent structure:
|
|
/// Request → Operation → Result
|
|
///
|
|
/// Example:
|
|
/// GciInitSlotRequest → InitSlot → GciInitSlotResult
|
|
/// GetSlot → GciSlotInfo
|
|
/// GetPcbId → GciGetPcbIdResult
|
|
///
|
|
/// This layer acts as a boundary between domain logic and integration logic.
|
|
/// </summary>
|
|
public class PublicModels
|
|
{
|
|
/// <summary>Persisted values for one meter and one preadjustment attempt.</summary>
|
|
public sealed class PreadjustmentCompletionRecord
|
|
{
|
|
// Diagnostic routing only. Not a database column or SQL parameter.
|
|
public int? Slot { get; set; }
|
|
public int PcbId { get; set; }
|
|
public int? MeterSize { get; set; }
|
|
public int? ZeroOffset1 { get; set; }
|
|
public int? ZeroOffset2 { get; set; }
|
|
public int? ZeroOffset3 { get; set; }
|
|
public int? FirstHitUpdatePeriod { get; set; }
|
|
public int? FirstHitShift { get; set; }
|
|
public int? FirstHitPercent1 { get; set; }
|
|
public int? FirstHitPercent2 { get; set; }
|
|
public int? FirstHitPercent3 { get; set; }
|
|
public int? ToFTempOffset1 { get; set; }
|
|
public int? ToFTempOffset2 { get; set; }
|
|
public int? ToFTempOffset3 { get; set; }
|
|
// Timestamp of the attempt, not the time of a possible later write retry.
|
|
public DateTime Date { get; set; }
|
|
public bool Successful { get; set; }
|
|
}
|
|
|
|
/// <summary>Q3 values calculated and confirmed by the caller; no port access.</summary>
|
|
public sealed class Q3CalibrationRecord
|
|
{
|
|
public int PcbId { get; set; }
|
|
// Preserve the existing TBF meaning/unit of each offset (do not rescale here).
|
|
public double? P1Offset { get; set; }
|
|
public int? P1Cal { get; set; }
|
|
public double? P2Offset { get; set; }
|
|
public int? P2Cal { get; set; }
|
|
public double? P3Offset { get; set; }
|
|
public int? P3Cal { get; set; }
|
|
public DateTime Date { get; set; }
|
|
}
|
|
|
|
/// <summary>Completion outcome plus detached SQL inputs and separate write outcomes.</summary>
|
|
public sealed class CompletionProcessResult : PreAdjustmentProcessResult
|
|
{
|
|
public List<PreadjustmentCompletionRecord> DatabaseRecords { get; set; } =
|
|
new List<PreadjustmentCompletionRecord>();
|
|
public List<CompletionDatabaseWriteResult> DatabaseWrites { get; set; } =
|
|
new List<CompletionDatabaseWriteResult>();
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + ", SQL=[" + string.Join("; ",
|
|
DatabaseWrites.Select(w => "Slot=" + w.Slot + ", " + w.Result.Status)) + "]";
|
|
}
|
|
}
|
|
|
|
public sealed class CompletionDatabaseWriteResult
|
|
{
|
|
public int Slot { get; set; }
|
|
public CalibrationWriteResult Result { get; set; }
|
|
}
|
|
|
|
public enum CalibrationWriteStatus
|
|
{
|
|
Failed = 0, Stored = 1, Disabled = 2, Simulated = 3,
|
|
// The connection failed during commit; verify the DB before retrying.
|
|
OutcomeUnknown = 4
|
|
}
|
|
|
|
/// <summary>Database outcome, independent of the physical calibration outcome.</summary>
|
|
public sealed class CalibrationWriteResult
|
|
{
|
|
public CalibrationWriteStatus Status { get; set; }
|
|
public bool Success { get { return Status == CalibrationWriteStatus.Stored; } }
|
|
public int PcbId { get; set; }
|
|
public long? RecordId { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("Status={0}, PcbId={1}, RecordId={2}, Error={3}",
|
|
Status, PcbId, RecordId, ErrorMessage ?? "None");
|
|
}
|
|
}
|
|
|
|
public static bool HideSensitiveValues { get; set; } = true;
|
|
|
|
private static string FormatPassword(string password)
|
|
{
|
|
if (!HideSensitiveValues)
|
|
return password ?? "<null>";
|
|
|
|
if (string.IsNullOrEmpty(password))
|
|
return "<empty>";
|
|
|
|
return "********";
|
|
}
|
|
/// <summary>
|
|
/// Public DTOs exposed to external systems.
|
|
/// These models represent the contract of the GCI API.
|
|
/// They must remain stable and independent of internal implementation.
|
|
/// </summary>
|
|
#region ================================== PUBLIC MODELS ===========================================
|
|
|
|
public class RegisterReadResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public string RegisterName { get; set; }
|
|
public string RawHex { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"Success={0}, RegisterName={1}, RawHex={2}, ErrorMessage={3}",
|
|
Success,
|
|
RegisterName,
|
|
RawHex,
|
|
ErrorMessage);
|
|
}
|
|
}
|
|
|
|
public class WorkerDebugStatus
|
|
{
|
|
public int Slot { get; set; }
|
|
public string Name { get; set; }
|
|
public int QueueLength { get; set; }
|
|
public bool IsBusy { get; set; }
|
|
public string CurrentOperation { get; set; }
|
|
public string LastError { get; set; }
|
|
public DateTime LastActivity { get; set; }
|
|
}
|
|
|
|
public class MeterBatchDebugStatus
|
|
{
|
|
public int Slot { get; set; }
|
|
public bool Selected { get; set; }
|
|
public string PcbId { get; set; }
|
|
public bool IsConnected { get; set; }
|
|
public bool IsLoggedOn { get; set; }
|
|
public string RequestPort { get; set; }
|
|
public string StreamingPort { get; set; }
|
|
public string RequestPortType { get; set; }
|
|
public string FwVersion { get; set; }
|
|
public string InterfaceVersion { get; set; }
|
|
}
|
|
|
|
public class RegisterWriteResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public string RegisterName { get; set; }
|
|
public object WrittenValue { get; set; }
|
|
public bool StoreToDevice { get; set; }
|
|
public bool RefreshSystemState { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"Success={Success}, " +
|
|
$"Register={RegisterName ?? "<null>"}, " +
|
|
$"Value={WrittenValue ?? "<null>"}, " +
|
|
$"StoreToDevice={StoreToDevice}, " +
|
|
$"RefreshSystemState={RefreshSystemState}, " +
|
|
$"Error={ErrorMessage ?? "<none>"}";
|
|
}
|
|
}
|
|
|
|
public class PortDetectionResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public int Slot { get; set; }
|
|
public string PortName { get; set; }
|
|
public string PcbId { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"Slot={0}, Success={1}, PortName={2}, PcbId={3}, ErrorMessage={4}",
|
|
Slot,
|
|
Success,
|
|
PortName,
|
|
PcbId,
|
|
ErrorMessage);
|
|
}
|
|
}
|
|
|
|
public class GciSlotInfo
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Exists { get; set; }
|
|
public bool Success { get; set; }
|
|
public bool IsConnected { get; set; }
|
|
public bool IsLoggedOn { get; set; }
|
|
public string Message { get; set; }
|
|
public string PcbId { get; set; }
|
|
public string Password { get; set; }
|
|
public GciConfigSource ConfigSource { get; set; }
|
|
public GciPasswordSource PasswordSource { get; set; }
|
|
public GciPortConfig RequestPort { get; set; }
|
|
public GciPortConfig StreamingPort { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Exists={1}, Success={2}, IsConnected={3}, IsLoggedOn={4}, Message={5}, PcbId={6}, Password={7}, ConfigSource={8}, PasswordSource={9}, RequestPort={10}, StreamingPort={11}",
|
|
SlotId,
|
|
Exists,
|
|
Success,
|
|
IsConnected,
|
|
IsLoggedOn,
|
|
Message,
|
|
PcbId,
|
|
FormatPassword(Password),
|
|
ConfigSource,
|
|
PasswordSource,
|
|
RequestPort,
|
|
StreamingPort);
|
|
}
|
|
}
|
|
|
|
public class GciAllSlotsInfo
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public List<GciSlotInfo> Slots { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"Success={Success}, Count={Slots?.Count ?? 0}, Message={Message}";
|
|
}
|
|
}
|
|
|
|
public class Result
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, Message={2}",
|
|
SlotId,
|
|
Success,
|
|
Message);
|
|
}
|
|
}
|
|
|
|
public enum GciPasswordSource
|
|
{
|
|
RestApi = 0,
|
|
OfflineFile = 1,
|
|
InterfaceInputPassword = 2,
|
|
}
|
|
public enum GciConfigSource
|
|
{
|
|
FileConfig = 0,
|
|
InterfaceInputConfig = 1,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Collection of port settings
|
|
/// </summary>
|
|
public class GciPortConfig
|
|
{
|
|
public string PortName { get; set; }
|
|
public string Type { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("PortName={0}, Type={1}", PortName, Type);
|
|
}
|
|
}
|
|
|
|
public static GciPortConfig CreatePortConfig(string portName, string type)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(portName))
|
|
return null;
|
|
|
|
return new GciPortConfig
|
|
{
|
|
PortName = portName,
|
|
Type = MapPortType(type)
|
|
};
|
|
}
|
|
|
|
public static string MapPortType(string type)
|
|
{
|
|
switch ((type ?? "").Trim().ToUpperInvariant())
|
|
{
|
|
case "RFID":
|
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.RfidSerialPort";
|
|
|
|
case "UART":
|
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort";
|
|
|
|
case "IRDA":
|
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort";
|
|
|
|
default:
|
|
return "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort";
|
|
}
|
|
}
|
|
|
|
public class GciInitSlotRequest
|
|
{
|
|
public int SlotId { get; set; }
|
|
public GciConfigSource ConfigSource { get; set; }
|
|
public GciPasswordSource PasswordSource { get; set; }
|
|
public GciPortConfig RequestPort { get; set; }
|
|
public GciPortConfig StreamingPort { get; set; }
|
|
public string Password { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, GciConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}, Password={5}",
|
|
SlotId,
|
|
ConfigSource,
|
|
PasswordSource,
|
|
RequestPort,
|
|
StreamingPort,
|
|
FormatPassword(Password));
|
|
}
|
|
}
|
|
|
|
public class GciInitSlotResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
|
|
public bool Success { get; set; }
|
|
|
|
public string Message { get; set; }
|
|
|
|
public string PcbId { get; set; }
|
|
|
|
public bool Created { get; set; }
|
|
|
|
public bool Updated { get; set; }
|
|
|
|
public bool AlreadyExists { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, Message={2}, PcbId={3}, Created={4}, Updated={5}, AlreadyExists={6}",
|
|
SlotId,
|
|
Success,
|
|
Message,
|
|
PcbId,
|
|
Created,
|
|
Updated,
|
|
AlreadyExists);
|
|
}
|
|
}
|
|
|
|
public class GciCleanAllSlotsResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"Success={0}, Message={1}",
|
|
Success,
|
|
Message);
|
|
}
|
|
}
|
|
public class GciCleanSlotResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, Message={2}",
|
|
SlotId,
|
|
Success,
|
|
Message);
|
|
}
|
|
}
|
|
|
|
public class GciGetPcbIdResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string PcbId { get; set; }
|
|
public string Message { get; set; }
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, PcbId={2}, Message={3}",
|
|
SlotId,
|
|
Success,
|
|
PcbId,
|
|
Message);
|
|
}
|
|
}
|
|
public class GciConnectResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string PcbId { get; set; }
|
|
public bool IsConnected { get; set; }
|
|
public string Message { get; set; }
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, PcbId={2}, IsConnected={3}, Message={4}",
|
|
SlotId,
|
|
Success,
|
|
PcbId,
|
|
IsConnected,
|
|
Message);
|
|
}
|
|
}
|
|
|
|
public class GciLoginResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string PcbId { get; set; }
|
|
public bool IsLoggedOn { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"Slot={SlotId}, Success={Success}, PcbId={PcbId}, IsLoggedOn={IsLoggedOn}, Message={Message ?? "<none>"}";
|
|
}
|
|
}
|
|
|
|
public class GciRegisterSnapshot
|
|
{
|
|
public string Name { get; set; }
|
|
public string Type { get; set; }
|
|
public string RawValue { get; set; }
|
|
public string Min { get; set; }
|
|
public string Max { get; set; }
|
|
public string Description { get; set; }
|
|
public string Version { get; set; }
|
|
public string IsAvailable { get; set; }
|
|
public string Privilege { get; set; }
|
|
}
|
|
|
|
public class GciDisconnectResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, Message={2}",
|
|
SlotId,
|
|
Success,
|
|
Message);
|
|
}
|
|
}
|
|
|
|
public class GciSetPasswordResult
|
|
{
|
|
public int SlotId { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Password { get; set; }
|
|
public string Message { get; set; }
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"SlotId={0}, Success={1}, Password={2}, Message={3}",
|
|
SlotId,
|
|
Success,
|
|
FormatPassword(Password),
|
|
Message);
|
|
}
|
|
}
|
|
|
|
public class PreAdjustmentProcessResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public int Slot { get; set; }
|
|
public string ProcessName { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public override string ToString()
|
|
{
|
|
return
|
|
$"Success={Success}, " +
|
|
$"Slot={Slot}, " +
|
|
$"Process={ProcessName}, " +
|
|
$"Error={ErrorMessage ?? "None"}";
|
|
}
|
|
}
|
|
|
|
public class TemperatureCalibrationSlotResult : PreAdjustmentProcessResult
|
|
{
|
|
public decimal? ReferenceTemperatureCelsius { get; set; }
|
|
public decimal? MeterTemperatureCelsius { get; set; }
|
|
public decimal? AbsoluteDifferenceCelsius { get; set; }
|
|
// The effective existing process limit, reported for diagnostics only.
|
|
public decimal? DeviationLimitCelsius { get; set; }
|
|
public int SampleCount { get; set; }
|
|
public bool? TemperatureCheckPassed { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
var culture = System.Globalization.CultureInfo.InvariantCulture;
|
|
return base.ToString() + string.Format(culture,
|
|
", Reference={0} C, Meter={1} C, Difference={2} C, Limit={3} C, Samples={4}, Check={5}",
|
|
ReferenceTemperatureCelsius?.ToString(culture) ?? "N/A",
|
|
MeterTemperatureCelsius?.ToString(culture) ?? "N/A",
|
|
AbsoluteDifferenceCelsius?.ToString(culture) ?? "N/A",
|
|
DeviationLimitCelsius?.ToString(culture) ?? "N/A", SampleCount,
|
|
TemperatureCheckPassed.HasValue ? (TemperatureCheckPassed.Value ? "PASSED" : "FAILED") : "NOT_EVALUATED");
|
|
}
|
|
}
|
|
|
|
public class TemperatureCalibrationResult : PreAdjustmentProcessResult
|
|
{
|
|
public List<TemperatureCalibrationSlotResult> Slots { get; set; } =
|
|
new List<TemperatureCalibrationSlotResult>();
|
|
public bool IsSimulated { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString() + ", Simulated=" + IsSimulated +
|
|
", TemperatureResults=[" + string.Join("; ", Slots.Select(s => s.ToString())) + "]";
|
|
}
|
|
|
|
// Preserve status for failures before process creation and for simulation.
|
|
// Missing measurements must never be represented as a measured zero.
|
|
public static TemperatureCalibrationResult FromProcessResult(
|
|
PreAdjustmentProcessResult result, bool simulated = false)
|
|
{
|
|
if (result == null) throw new ArgumentNullException(nameof(result));
|
|
var detailed = result as TemperatureCalibrationResult;
|
|
if (detailed != null) return detailed;
|
|
return new TemperatureCalibrationResult
|
|
{
|
|
Success = result.Success, Slot = result.Slot, ProcessName = result.ProcessName,
|
|
ErrorMessage = result.ErrorMessage, IsSimulated = simulated
|
|
};
|
|
}
|
|
}
|
|
|
|
public class PreadjustmentDetectResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public int DetectedMeterCount { get; set; }
|
|
public int DetectedThermometerCount { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public override string ToString()
|
|
{
|
|
return
|
|
$"Success={Success}, " +
|
|
$"Meters={DetectedMeterCount}, " +
|
|
$"Thermometers={DetectedThermometerCount}, " +
|
|
$"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
|
|
}
|
|
}
|