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
This commit is contained in:
Joseph Doherty
2026-06-25 14:38:27 -04:00
parent e04eb539f7
commit 43c2587498
2 changed files with 33 additions and 8 deletions
@@ -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)
/// </code>
///
/// Captured for the five analog types <c>EnsureTagAsync</c> 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.
/// </remarks>
internal static class HistorianHistoricalWriteProtocol
@@ -53,6 +55,8 @@ internal static class HistorianHistoricalWriteProtocol
/// <paramref name="dataType"/> is the tag's declared analog type (selects the value width), and
/// <paramref name="receivedTimeUtc"/> is the storage/received timestamp the orchestrator stamps.
/// Throws <see cref="ProtocolEvidenceMissingException"/> for tag types without a captured encoding.
/// Int8/UInt8 carry exact magnitude only up to 2^53 — the value API is <see langword="double"/>;
/// full 64-bit range is a separate follow-on.
/// </summary>
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.");
}
}