Auto: twincat-1.4 — whole-array reads
Surface int[]? ArrayDimensions on TwinCATTagDefinition + thread it through ITwinCATClient.ReadValueAsync / WriteValueAsync. When non-null + non-empty, AdsTwinCATClient issues a single ADS read against the symbol with clrType.MakeArrayType() and returns the flat 1-D CLR Array; for IEC TIME / DATE / DT / TOD element types we project per-element to the native TimeSpan / DateTime so consumers see consistent types regardless of rank. DiscoverAsync surfaces IsArray=true + ArrayDim=product(dims) onto DriverAttributeInfo via a new ResolveArrayShape helper. Multi-dim shapes flatten to the product on the wire — DriverAttributeInfo.ArrayDim is single-uint today and the OPC UA layer reflects rank via its own metadata. Native ADS notification subscriptions skip whole-array tags so the OPC UA layer falls through to a polled snapshot — the per-element AdsNotificationEx callback shape doesn't fit a flat array. Whole-array WRITES are out of scope for this PR — AdsTwinCATClient.WriteValueAsync returns BadNotSupported when ArrayDimensions is set. Tests: TwinCATArrayReadTests covers ResolveArrayShape (null / empty / single-dim / multi-dim flatten / non-positive defensive), DiscoverAsync emitting IsArray + ArrayDim for declared array tags, single-dim + multi-dim fake-client read fan-out, and the BadNotSupported gate on whole-array writes. Existing 137 unit tests still pass — total now 143. Closes #308
This commit is contained in:
@@ -49,18 +49,27 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
int? bitIndex,
|
||||
int[]? arrayDimensions,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clrType = MapToClrType(type);
|
||||
var result = await _client.ReadValueAsync(symbolPath, clrType, cancellationToken)
|
||||
var readType = IsWholeArray(arrayDimensions) ? clrType.MakeArrayType() : clrType;
|
||||
|
||||
var result = await _client.ReadValueAsync(symbolPath, readType, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result.ErrorCode != AdsErrorCode.NoError)
|
||||
return (null, TwinCATStatusMapper.MapAdsError((uint)result.ErrorCode));
|
||||
|
||||
var value = result.Value;
|
||||
if (IsWholeArray(arrayDimensions))
|
||||
{
|
||||
value = PostProcessArray(type, value);
|
||||
return (value, TwinCATStatusMapper.Good);
|
||||
}
|
||||
|
||||
if (bitIndex is int bit && type == TwinCATDataType.Bool && value is not bool)
|
||||
value = ExtractBit(value, bit);
|
||||
value = PostProcessIecTime(type, value);
|
||||
@@ -73,13 +82,40 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWholeArray(int[]? arrayDimensions) =>
|
||||
arrayDimensions is { Length: > 0 } && arrayDimensions.All(d => d > 0);
|
||||
|
||||
/// <summary>Apply per-element IEC TIME/DATE post-processing to a flat array result.</summary>
|
||||
private static object? PostProcessArray(TwinCATDataType type, object? value)
|
||||
{
|
||||
if (value is not Array arr) return value;
|
||||
var elementProjector = type switch
|
||||
{
|
||||
TwinCATDataType.Time or TwinCATDataType.TimeOfDay
|
||||
or TwinCATDataType.Date or TwinCATDataType.DateTime
|
||||
=> (Func<object?, object?>)(v => PostProcessIecTime(type, v)),
|
||||
_ => null,
|
||||
};
|
||||
if (elementProjector is null) return arr;
|
||||
// IEC time post-processing changes the CLR element type (uint -> TimeSpan / DateTime).
|
||||
// Project into an object[] so the array element type matches the projected values.
|
||||
var projected = new object?[arr.Length];
|
||||
for (var i = 0; i < arr.Length; i++)
|
||||
projected[i] = elementProjector(arr.GetValue(i));
|
||||
return projected;
|
||||
}
|
||||
|
||||
public async Task<uint> WriteValueAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
int? bitIndex,
|
||||
int[]? arrayDimensions,
|
||||
object? value,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (IsWholeArray(arrayDimensions))
|
||||
return TwinCATStatusMapper.BadNotSupported; // PR-1.4 ships read-only whole-array
|
||||
|
||||
if (bitIndex is int bit && type == TwinCATDataType.Bool)
|
||||
return await WriteBitInWordAsync(symbolPath, bit, value, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user