M3 R3.2: AddHistoricalValuesAsync supports Double + Int (Int2/Int4/UInt4)

Extended the historical-write serializer from Float-only to all five analog types EnsureTagAsync
supports. Captured each type's "ON" buffer live from the native client (sandbox tag per type,
written + captured + deleted):

- The 4-byte value descriptor (C0 10 01 00) is CONSTANT across types — it does not encode the type.
- The value is u32(0) + native-width value, width by the tag's declared type:
  Float->float32, Double->double64, Int2->int16, Int4->int32, UInt4->uint32.

HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer now takes the HistorianDataType and
encodes accordingly (unsupported types throw ProtocolEvidenceMissingException). The orchestrator
resolves the type from the tag-info NativeDataTypeDescriptor via MapDataType. Harness capture-write
gained --data-type. Golden-tested against all five live captures + the gated write/read-back test
validated each type end-to-end through the pure-managed SDK; 281 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
Joseph Doherty
2026-06-21 21:48:29 -04:00
parent d527784def
commit d1e96f48de
7 changed files with 137 additions and 69 deletions
@@ -227,7 +227,17 @@ namespace AVEVA.Historian.Grpc2023CaptureHarness
string tagName = GetOption(args, "--tag") ?? "SdkM3CaptureSandbox";
bool create = args.Contains("--create");
bool commit = args.Contains("--commit");
float sampleValue = float.TryParse(GetOption(args, "--value"), out float fv) ? fv : 123.0f;
string dataType = GetOption(args, "--data-type") ?? "Float"; // Float|Double|Int2|Int4|UInt4
string rawValue = GetOption(args, "--value") ?? "123";
// Box the value as the CLR type the HistorianDataValue expects for this tag type.
object sampleValue = dataType switch
{
"Double" => (object)double.Parse(rawValue),
"Int2" => (object)short.Parse(rawValue),
"Int4" => (object)int.Parse(rawValue),
"UInt4" => (object)uint.Parse(rawValue),
_ => (object)float.Parse(rawValue),
};
string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");
string? password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD");
if (string.IsNullOrEmpty(user))
@@ -278,7 +288,7 @@ namespace AVEVA.Historian.Grpc2023CaptureHarness
object tag = Activator.CreateInstance(tagType)!;
SetProp(tag, "TagName", tagName);
TrySetProp(tag, "TagDescription", "histsdk M3 non-streamed-write capture sandbox");
TrySetProp(tag, "TagDataType", Enum.Parse(tagDataTypeEnum, "Float", true));
TrySetProp(tag, "TagDataType", Enum.Parse(tagDataTypeEnum, dataType, true));
TrySetProp(tag, "TagStorageType", Enum.Parse(tagStorageEnum, "Cyclic", true));
object addErr = Activator.CreateInstance(errorType)!;
object?[] addArgs = { tag, 0u, addErr };
@@ -332,7 +342,7 @@ namespace AVEVA.Historian.Grpc2023CaptureHarness
// --- build the historical (backfill) value ---
object value = Activator.CreateInstance(valueType)!;
SetProp(value, "TagKey", tagKey);
TrySetProp(value, "DataValueType", Enum.Parse(valueDataTypeEnum, "Float", true));
TrySetProp(value, "DataValueType", Enum.Parse(valueDataTypeEnum, dataType, true));
TrySetProp(value, "OpcQuality", (ushort)192);
TrySetProp(value, "Value", sampleValue);
DateTime ts = DateTime.UtcNow.AddHours(-2); // backfill = past timestamp