feat(lmxproxy): add DatetimeArray proto type for DateTime[] round-trip fidelity

Added DatetimeArray message (repeated int64, UTC ticks) to proto and
code-first contracts. Host serializes DateTime[] → DatetimeArray.
Client deserializes DatetimeArray → DateTime[] (not raw long[]).
Client ExtractArrayValue now unpacks all array types including DateTime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-22 15:08:15 -04:00
parent b050371dd5
commit ce3942990e
4 changed files with 56 additions and 15 deletions

View File

@@ -120,6 +120,9 @@ public class ArrayValue
[DataMember(Order = 6)]
public StringArray? StringValues { get; set; }
[DataMember(Order = 7)]
public DatetimeArray? DatetimeValues { get; set; }
}
[DataContract]
@@ -164,6 +167,13 @@ public class StringArray
public List<string> Values { get; set; } = [];
}
[DataContract]
public class DatetimeArray
{
[DataMember(Order = 1)]
public List<long> Values { get; set; } = [];
}
// ────────────────────────────────────────────────────────────────
// Quality Code (v2)
// ────────────────────────────────────────────────────────────────

View File

@@ -271,12 +271,30 @@ public partial class LmxProxyClient : ILmxProxyClient
TypedValueCase.StringValue => tv.StringValue,
TypedValueCase.BytesValue => tv.BytesValue,
TypedValueCase.DatetimeValue => new DateTime(tv.DatetimeValue, DateTimeKind.Utc),
TypedValueCase.ArrayValue => tv.ArrayValue,
TypedValueCase.ArrayValue => ExtractArrayValue(tv.ArrayValue),
TypedValueCase.None => null,
_ => null
};
}
internal static object? ExtractArrayValue(ArrayValue? av)
{
if (av is null) return null;
if (av.BoolValues is not null) return av.BoolValues.Values.ToArray();
if (av.Int32Values is not null) return av.Int32Values.Values.ToArray();
if (av.Int64Values is not null) return av.Int64Values.Values.ToArray();
if (av.FloatValues is not null) return av.FloatValues.Values.ToArray();
if (av.DoubleValues is not null) return av.DoubleValues.Values.ToArray();
if (av.StringValues is not null) return av.StringValues.Values.ToArray();
if (av.DatetimeValues is not null)
{
return av.DatetimeValues.Values
.Select(ticks => new DateTime(ticks, DateTimeKind.Utc))
.ToArray();
}
return null;
}
private async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation, CancellationToken ct)
{
if (_resiliencePipeline is not null)

View File

@@ -109,9 +109,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.Domain
case DateTime[] dtArr:
{
var arr = new Scada.Int64Array();
var arr = new Scada.DatetimeArray();
arr.Values.AddRange(Array.ConvertAll(dtArr, dt => dt.ToUniversalTime().Ticks));
return new Scada.TypedValue { ArrayValue = new Scada.ArrayValue { Int64Values = arr } };
return new Scada.TypedValue { ArrayValue = new Scada.ArrayValue { DatetimeValues = arr } };
}
default:
@@ -202,6 +202,17 @@ namespace ZB.MOM.WW.LmxProxy.Host.Domain
? ToArray(arrayValue.StringValues.Values)
: Array.Empty<string>();
case Scada.ArrayValue.ValuesOneofCase.DatetimeValues:
if (arrayValue.DatetimeValues?.Values?.Count > 0)
{
var ticks = ToArray(arrayValue.DatetimeValues.Values);
var result = new DateTime[ticks.Length];
for (int i = 0; i < ticks.Length; i++)
result[i] = new DateTime(ticks[i], DateTimeKind.Utc);
return result;
}
return Array.Empty<DateTime>();
default:
return null;
}

View File

@@ -38,21 +38,23 @@ message TypedValue {
message ArrayValue {
oneof values {
BoolArray bool_values = 1;
Int32Array int32_values = 2;
Int64Array int64_values = 3;
FloatArray float_values = 4;
DoubleArray double_values = 5;
StringArray string_values = 6;
BoolArray bool_values = 1;
Int32Array int32_values = 2;
Int64Array int64_values = 3;
FloatArray float_values = 4;
DoubleArray double_values = 5;
StringArray string_values = 6;
DatetimeArray datetime_values = 7; // UTC DateTime.Ticks arrays
}
}
message BoolArray { repeated bool values = 1; }
message Int32Array { repeated int32 values = 1; }
message Int64Array { repeated int64 values = 1; }
message FloatArray { repeated float values = 1; }
message DoubleArray { repeated double values = 1; }
message StringArray { repeated string values = 1; }
message BoolArray { repeated bool values = 1; }
message Int32Array { repeated int32 values = 1; }
message Int64Array { repeated int64 values = 1; }
message FloatArray { repeated float values = 1; }
message DoubleArray { repeated double values = 1; }
message StringArray { repeated string values = 1; }
message DatetimeArray { repeated int64 values = 1; } // UTC DateTime.Ticks
// ============================================================
// OPC UA-Style Quality Codes