feat(historian): typed HistorianValue contracts, IHistorianDataUpdater, IHistorianBackendProvider (no behaviour change)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 12:44:19 -04:00
parent a6643cedf9
commit ff7c6c531a
15 changed files with 357 additions and 65 deletions
@@ -38,6 +38,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Recorder;
/// </remarks>
public sealed class GatewayHistorianValueWriter : IHistorianValueWriter, IAsyncDisposable
{
private const uint LegacyGoodQuality = 192;
private readonly IHistorianGatewayClient _client;
private readonly ILogger<GatewayHistorianValueWriter> _logger;
@@ -70,10 +72,10 @@ public sealed class GatewayHistorianValueWriter : IHistorianValueWriter, IAsyncD
{
var live = new HistorianLiveValue
{
NumericValue = value.Value,
NumericValue = value.Value.Number,
// Documented-dead input on the WriteLiveValues SQL path (0.2.0 Contracts C-002 —
// server-managed, silently discarded). Populated deliberately; see the class <remarks>.
Quality = value.Quality,
Quality = LegacyGoodQuality,
};
if (value.TimestampUtc is { } timestampUtc)
@@ -17,6 +17,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Recorder;
/// </remarks>
internal static class HistorizationOutboxEntrySerializer
{
private const ushort LegacyGoodQuality = 192;
/// <summary>Serializes <paramref name="entry"/> to a fixed-layout little-endian byte array.</summary>
/// <param name="entry">The outbox entry to serialize.</param>
/// <returns>The fixed-layout little-endian byte array.</returns>
@@ -33,9 +35,9 @@ internal static class HistorizationOutboxEntrySerializer
Encoding.UTF8.GetBytes(entry.Tag, span.Slice(20, tagLen));
int p = 20 + tagLen;
BinaryPrimitives.WriteDoubleLittleEndian(span.Slice(p, 8), entry.NumericValue);
BinaryPrimitives.WriteDoubleLittleEndian(span.Slice(p, 8), entry.Value.Number);
p += 8;
BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(p, 2), entry.Quality);
BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(p, 2), LegacyGoodQuality);
p += 2;
BinaryPrimitives.WriteInt64LittleEndian(span.Slice(p, 8), entry.TimestampUtc.ToBinary());
@@ -54,10 +56,11 @@ internal static class HistorizationOutboxEntrySerializer
int p = 20 + tagLen;
double value = BinaryPrimitives.ReadDoubleLittleEndian(span.Slice(p, 8));
p += 8;
ushort quality = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(p, 2));
// Legacy layout carries a DA quality that was always 192 (Good); the typed entry
// carries status 0 instead, so the field is skipped.
p += 2;
long timestamp = BinaryPrimitives.ReadInt64LittleEndian(span.Slice(p, 8));
return new HistorizationOutboxEntry(id, tag, value, quality, DateTime.FromBinary(timestamp));
return new HistorizationOutboxEntry(id, tag, HistorianValue.FromDouble(value), 0u, DateTime.FromBinary(timestamp));
}
}