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)]
|
[DataMember(Order = 6)]
|
||||||
public StringArray? StringValues { get; set; }
|
public StringArray? StringValues { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Order = 7)]
|
||||||
|
public DatetimeArray? DatetimeValues { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
@@ -164,6 +167,13 @@ public class StringArray
|
|||||||
public List<string> Values { get; set; } = [];
|
public List<string> Values { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class DatetimeArray
|
||||||
|
{
|
||||||
|
[DataMember(Order = 1)]
|
||||||
|
public List<long> Values { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
// Quality Code (v2)
|
// Quality Code (v2)
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -271,12 +271,30 @@ public partial class LmxProxyClient : ILmxProxyClient
|
|||||||
TypedValueCase.StringValue => tv.StringValue,
|
TypedValueCase.StringValue => tv.StringValue,
|
||||||
TypedValueCase.BytesValue => tv.BytesValue,
|
TypedValueCase.BytesValue => tv.BytesValue,
|
||||||
TypedValueCase.DatetimeValue => new DateTime(tv.DatetimeValue, DateTimeKind.Utc),
|
TypedValueCase.DatetimeValue => new DateTime(tv.DatetimeValue, DateTimeKind.Utc),
|
||||||
TypedValueCase.ArrayValue => tv.ArrayValue,
|
TypedValueCase.ArrayValue => ExtractArrayValue(tv.ArrayValue),
|
||||||
TypedValueCase.None => null,
|
TypedValueCase.None => null,
|
||||||
_ => 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)
|
private async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (_resiliencePipeline is not null)
|
if (_resiliencePipeline is not null)
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.Domain
|
|||||||
|
|
||||||
case DateTime[] dtArr:
|
case DateTime[] dtArr:
|
||||||
{
|
{
|
||||||
var arr = new Scada.Int64Array();
|
var arr = new Scada.DatetimeArray();
|
||||||
arr.Values.AddRange(Array.ConvertAll(dtArr, dt => dt.ToUniversalTime().Ticks));
|
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:
|
default:
|
||||||
@@ -202,6 +202,17 @@ namespace ZB.MOM.WW.LmxProxy.Host.Domain
|
|||||||
? ToArray(arrayValue.StringValues.Values)
|
? ToArray(arrayValue.StringValues.Values)
|
||||||
: Array.Empty<string>();
|
: 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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,21 +38,23 @@ message TypedValue {
|
|||||||
|
|
||||||
message ArrayValue {
|
message ArrayValue {
|
||||||
oneof values {
|
oneof values {
|
||||||
BoolArray bool_values = 1;
|
BoolArray bool_values = 1;
|
||||||
Int32Array int32_values = 2;
|
Int32Array int32_values = 2;
|
||||||
Int64Array int64_values = 3;
|
Int64Array int64_values = 3;
|
||||||
FloatArray float_values = 4;
|
FloatArray float_values = 4;
|
||||||
DoubleArray double_values = 5;
|
DoubleArray double_values = 5;
|
||||||
StringArray string_values = 6;
|
StringArray string_values = 6;
|
||||||
|
DatetimeArray datetime_values = 7; // UTC DateTime.Ticks arrays
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message BoolArray { repeated bool values = 1; }
|
message BoolArray { repeated bool values = 1; }
|
||||||
message Int32Array { repeated int32 values = 1; }
|
message Int32Array { repeated int32 values = 1; }
|
||||||
message Int64Array { repeated int64 values = 1; }
|
message Int64Array { repeated int64 values = 1; }
|
||||||
message FloatArray { repeated float values = 1; }
|
message FloatArray { repeated float values = 1; }
|
||||||
message DoubleArray { repeated double values = 1; }
|
message DoubleArray { repeated double values = 1; }
|
||||||
message StringArray { repeated string values = 1; }
|
message StringArray { repeated string values = 1; }
|
||||||
|
message DatetimeArray { repeated int64 values = 1; } // UTC DateTime.Ticks
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// OPC UA-Style Quality Codes
|
// OPC UA-Style Quality Codes
|
||||||
|
|||||||
Reference in New Issue
Block a user