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:
@@ -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)
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user