Files
tbf/GenesisCordonelInterface/API/PublicModels.cs
T
marekf c426df990c <Feat>: Add per-slot temperature calibration results, diagnostic hold and settling delay, increase revision to 3.9.3146.107
Cause:

- Temperature calibration results lacked measured meter temperatures and comparison details.
- Diagnostics closed immediately after a phase, preventing inspection of failures.
- Push Temperature remained visible when temperature was supplied directly from T1/T2.
- Automatic phase transitions provided insufficient settling time before temperature calibration, causing temperature checks to fail.

Solution:

1. Added structured temperature calibration results
   - Return reference temperature, meter temperature, absolute difference, effective limit and sample count per slot.
   - Distinguish temperature check status from overall calibration completion.
   - Preserve existing API signatures and add typed result methods.

2. Improved temperature diagnostics
   - Log sample count, invalid values, minimum, maximum and mean for each path.
   - Add bounded TEMP-RAW capture with raw temperature, scaling factor, decoded temperature, timestamps and frame content.
   - Attach diagnostic capture only during the temperature check and detach it afterwards, including on cancellation.
   - Preserve the original streaming handler used during Amplitude Test.
   - Keep the original comparison algorithm and temperature deviation limit.
   - Remove configurable deviation from the GCI Bridge component dialog.

3. Added optional hold after preadjustment
   - Add the Hold after preadjustment procedure parameter.
   - Keep completed results and diagnostics visible until Continue is pressed.
   - Process STOP during hold and exclude hold time from measurement duration.

4. Updated phase display
   - Hide Push Temperature in sensor mode unless that action is explicitly running.
   - Keep failed slots and their exclusion status visible during hold.

5. Corrected settling time
   - Remove initialization overrides that forced OffsetTestSettlingTime to zero.
   - Set OffsetTestSettlingTime to 20 seconds.
   - Apply settling before temperature calibration and during Offset Test.
   - Preserve STOP handling during the wait.
   - The existing inclusive countdown produces approximately 21 seconds of waiting.

6. Aligned project versions
   - Update TBF revision to 3.9.3146.107.
   - The corresponding Laatzen PreadjustmentUI project state matches this TBF revision exactly.
   - Build and deploy these matching project versions together.

Validation:

- Compared raw temperature frames from GCI GUI and GenesisCommunication.
- Confirmed that decoded Meter temperatures matched the raw frame values.
- Confirmed that adding a pause after Amplitude Test allowed temperature calibration to pass.
2026-09-11 19:05:17 +02:00

556 lines
19 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
{
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
}
}