Auto: twincat-1.2 — native UA TIME/DATE/DT/TOD

IEC 61131-3 TIME/TOD now surface as TimeSpan (UA Duration); DATE/DT
surface as DateTime (UTC). The wire form stays UDINT — AdsTwinCATClient
post-processes raw values in ReadValueAsync and OnAdsNotificationEx,
and accepts native CLR types in ConvertForWrite. Added Duration to
DriverDataType (back-compat: existing switches default to BaseDataType
for unknown enum values) and mapped it to DataTypeIds.Duration in
DriverNodeManager.

Closes #306
This commit is contained in:
Joseph Doherty
2026-04-25 17:14:12 -04:00
parent 8286255ae5
commit 80b2d7f8c3
5 changed files with 171 additions and 6 deletions

View File

@@ -89,7 +89,91 @@ public sealed class TwinCATDriverTests
TwinCATDataType.LReal.ToDriverDataType().ShouldBe(DriverDataType.Float64);
TwinCATDataType.String.ToDriverDataType().ShouldBe(DriverDataType.String);
TwinCATDataType.WString.ToDriverDataType().ShouldBe(DriverDataType.String);
TwinCATDataType.Time.ToDriverDataType().ShouldBe(DriverDataType.Int32);
// IEC durations map to UA Duration; absolute timestamps map to UA DateTime.
TwinCATDataType.Time.ToDriverDataType().ShouldBe(DriverDataType.Duration);
TwinCATDataType.TimeOfDay.ToDriverDataType().ShouldBe(DriverDataType.Duration);
TwinCATDataType.Date.ToDriverDataType().ShouldBe(DriverDataType.DateTime);
TwinCATDataType.DateTime.ToDriverDataType().ShouldBe(DriverDataType.DateTime);
}
[Fact]
public void IecTime_post_process_converts_TIME_to_TimeSpan_ms()
{
var ts = (TimeSpan)AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.Time, 12_345u)!;
ts.ShouldBe(TimeSpan.FromMilliseconds(12_345));
}
[Fact]
public void IecTime_post_process_converts_TOD_to_TimeSpan_ms()
{
var ts = (TimeSpan)AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.TimeOfDay, 3_600_000u)!;
ts.ShouldBe(TimeSpan.FromHours(1));
}
[Fact]
public void IecTime_post_process_converts_DT_to_DateTime_utc()
{
// 1970-01-01 + 1 hour = 1970-01-01 01:00:00 UTC
var dt = (DateTime)AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.DateTime, 3600u)!;
dt.ShouldBe(new DateTime(1970, 1, 1, 1, 0, 0, DateTimeKind.Utc));
dt.Kind.ShouldBe(DateTimeKind.Utc);
}
[Fact]
public void IecTime_post_process_converts_DATE_to_midnight_utc()
{
// 86400 seconds = exactly 1970-01-02 00:00 UTC
var dt = (DateTime)AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.Date, 86_400u)!;
dt.ShouldBe(new DateTime(1970, 1, 2, 0, 0, 0, DateTimeKind.Utc));
}
[Fact]
public void IecTime_post_process_passthrough_for_non_time_types()
{
AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.DInt, 42).ShouldBe(42);
AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.LReal, 3.14).ShouldBe(3.14);
}
[Fact]
public void ConvertForWrite_TIME_accepts_TimeSpan_and_returns_UDINT_ms()
{
var raw = AdsTwinCATClient.ConvertForWrite(TwinCATDataType.Time, TimeSpan.FromMilliseconds(2_500));
raw.ShouldBeOfType<uint>();
((uint)raw).ShouldBe(2_500u);
}
[Fact]
public void ConvertForWrite_TOD_accepts_double_ms()
{
var raw = AdsTwinCATClient.ConvertForWrite(TwinCATDataType.TimeOfDay, 60_000.0);
((uint)raw).ShouldBe(60_000u);
}
[Fact]
public void ConvertForWrite_DT_accepts_DateTime_and_returns_UDINT_seconds()
{
var raw = AdsTwinCATClient.ConvertForWrite(
TwinCATDataType.DateTime,
new DateTime(1970, 1, 1, 1, 0, 0, DateTimeKind.Utc));
((uint)raw).ShouldBe(3600u);
}
[Fact]
public void ConvertForWrite_DT_round_trips_via_post_process()
{
var original = new DateTime(2024, 6, 15, 12, 30, 45, DateTimeKind.Utc);
var raw = (uint)AdsTwinCATClient.ConvertForWrite(TwinCATDataType.DateTime, original);
var roundTrip = (DateTime)AdsTwinCATClient.PostProcessIecTime(TwinCATDataType.DateTime, raw)!;
roundTrip.ShouldBe(original);
}
[Fact]
public void ConvertForWrite_DT_rejects_pre_epoch_values()
{
Should.Throw<ArgumentOutOfRangeException>(() =>
AdsTwinCATClient.ConvertForWrite(
TwinCATDataType.DateTime,
new DateTime(1969, 12, 31, 23, 59, 59, DateTimeKind.Utc)));
}
[Theory]