From 43c25874983e00b5cb5709a531390ee7deb31dee Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 25 Jun 2026 14:38:27 -0400 Subject: [PATCH] feat(write): un-gate UInt1/Int8/UInt8 tag-create + historical-value encoders GetAnalogDataTypeCode gains UInt1=0x08/Int8=0x19/UInt8=0x39 (read-side MapDataType already maps these); EncodeNativeValue gains 1-byte UInt1 + 8-byte LE Int8/UInt8 (mirrors the captured Double value layout). Value API stays double; 2^53 exact-magnitude ceiling documented. Golden tests + live round-trip follow. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii --- .../Wcf/HistorianHistoricalWriteProtocol.cs | 26 ++++++++++++++++--- .../Wcf/HistorianTagWriteProtocol.cs | 15 +++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/AVEVA.Historian.Client/Wcf/HistorianHistoricalWriteProtocol.cs b/src/AVEVA.Historian.Client/Wcf/HistorianHistoricalWriteProtocol.cs index f485d0c..c252c48 100644 --- a/src/AVEVA.Historian.Client/Wcf/HistorianHistoricalWriteProtocol.cs +++ b/src/AVEVA.Historian.Client/Wcf/HistorianHistoricalWriteProtocol.cs @@ -28,10 +28,12 @@ namespace AVEVA.Historian.Client.Wcf; /// +0x26 UInt32 0 // value high dword (constant zero) /// +0x2A value bytes, native width by tag type: /// Float → Float32(4) · Double → Float64(8) · Int2 → Int16(2) -/// Int4 → Int32(4) · UInt4 → UInt32(4) +/// Int4 → Int32(4) · UInt4 → UInt32(4) · UInt1 → UInt8(1) +/// Int8 → Int64(8) · UInt8 → UInt64(8) /// /// -/// Captured for the five analog types EnsureTagAsync supports (Float/Double/Int2/Int4/UInt4). +/// Live-captured for Float/Double/Int2/Int4/UInt4; UInt1/Int8/UInt8 mirror the captured +/// Double value layout (8 LE bytes for Int8/UInt8, 1 byte for UInt1) pending live round-trip. /// Other tag types have no captured value encoding and are rejected. /// internal static class HistorianHistoricalWriteProtocol @@ -53,6 +55,8 @@ internal static class HistorianHistoricalWriteProtocol /// is the tag's declared analog type (selects the value width), and /// is the storage/received timestamp the orchestrator stamps. /// Throws for tag types without a captured encoding. + /// Int8/UInt8 carry exact magnitude only up to 2^53 — the value API is ; + /// full 64-bit range is a separate follow-on. /// public static byte[] SerializeAddStreamValuesBuffer( Guid tagGuid, @@ -117,10 +121,26 @@ internal static class HistorianHistoricalWriteProtocol BinaryPrimitives.WriteUInt32LittleEndian(b, checked((uint)value)); return b; } + case HistorianDataType.UInt1: + { + return [checked((byte)value)]; + } + case HistorianDataType.Int8: + { + byte[] b = new byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(b, checked((long)value)); + return b; + } + case HistorianDataType.UInt8: + { + byte[] b = new byte[8]; + BinaryPrimitives.WriteUInt64LittleEndian(b, checked((ulong)value)); + return b; + } default: throw new ProtocolEvidenceMissingException( $"AddHistoricalValuesAsync has no captured value encoding for tag data type '{dataType}'. " + - "Captured types: Float, Double, Int2, Int4, UInt4."); + "Captured types: Float, Double, Int2, Int4, UInt4, UInt1, Int8, UInt8."); } } diff --git a/src/AVEVA.Historian.Client/Wcf/HistorianTagWriteProtocol.cs b/src/AVEVA.Historian.Client/Wcf/HistorianTagWriteProtocol.cs index a45faf6..f5e2aaa 100644 --- a/src/AVEVA.Historian.Client/Wcf/HistorianTagWriteProtocol.cs +++ b/src/AVEVA.Historian.Client/Wcf/HistorianTagWriteProtocol.cs @@ -58,9 +58,10 @@ internal static class HistorianTagWriteProtocol ]; /// - /// Native CDataType wire codes per data type — captured 2026-05-04 by probing - /// every type via instrument-wcf-writemessage. Matches the codes already documented - /// in MapDataType for the read path. + /// Native CDataType wire codes per data type — captured by probing every type via + /// instrument-wcf-writemessage. Matches the codes already documented in + /// MapDataType for the read path; UInt1/Int8/UInt8 + /// reuse the same read-side codes (0x08/0x19/0x39). /// public static byte GetAnalogDataTypeCode(Models.HistorianDataType dataType) => dataType switch { @@ -70,8 +71,11 @@ internal static class HistorianTagWriteProtocol Models.HistorianDataType.UInt4 => 0x11, Models.HistorianDataType.Int2 => 0x29, Models.HistorianDataType.Int4 => 0x31, + Models.HistorianDataType.UInt1 => 0x08, + Models.HistorianDataType.Int8 => 0x19, + Models.HistorianDataType.UInt8 => 0x39, _ => throw new ProtocolEvidenceMissingException( - $"EnsureTagAsync data type {dataType} has no captured CTagMetadata wire code; supported: Float, Double, UInt2, UInt4, Int2, Int4."), + $"EnsureTagAsync data type {dataType} has no captured CTagMetadata wire code; supported: Float, Double, UInt2, UInt4, Int2, Int4, UInt1, Int8, UInt8."), }; private static readonly byte[] AnalogPadding16 = new byte[16]; @@ -118,7 +122,8 @@ internal static class HistorianTagWriteProtocol /// /// Serializes a CTagMetadata payload for an analog tag. Live-verified for Float, - /// Double, Int2, Int4, UInt4 — see for the + /// Double, Int2, Int4, UInt4; UInt1/Int8/UInt8 supported via the read-side type + /// codes (pending live round-trip) — see for the /// type-code mapping. Output matches the byte-for-byte capture for the same inputs. /// When MinEU/MaxEU/MinRaw/MaxRaw are all defaults (0/100/0/100) emits the compact /// `1A 03` scaling marker; otherwise emits `1F` + 4 doubles in order.