feat(twincat): native ADS notifications for array tags, decoded from raw bytes
An array tag subscribed natively used to register only its declared scalar (publishing element 0 at Good) and was then refused wholesale with BadTypeMismatch. The driver now hands the authored ArrayLength to the client, which registers the whole array as raw bytes and decodes every notification. - TwinCATTypeGuard.CheckArray plans the layout from the RESOLVED array type: element checked like a scalar notification (width / float kind / string kind); STRING(n)/WSTRING(n) elements stride n+1 / 2(n+1); multi-dim arrays flattened in PLC memory order; an authored ArrayLength shorter than the PLC array reads the leading sub-range, a longer one is refused; struct / pointer / nested-array elements, non-array symbols and VAR_IN_OUT refs are refused. - CheckNotificationSize caps a native array at 128 KiB (largest registration proven on a live TC 2.11 target); above it the tag is refused BadOutOfRange. - TwinCATArrayCodec decodes the bytes into the element-typed CLR array. - The poll read uses the same plan + codec instead of the Beckhoff typed array read, which failed live for most real arrays (exact CLR element type, rank-N for multi-dim, member arrays sliced by the under-reported TC2 symbol size). - TwinCATNotificationRefusedException carries the refusal's status; the type mismatch derives from it and the driver isolates either per tag.
This commit is contained in:
@@ -41,9 +41,11 @@ public interface ITwinCATClient : IDisposable
|
||||
/// <param name="symbolPath">The ADS symbol path.</param>
|
||||
/// <param name="type">The target data type.</param>
|
||||
/// <param name="bitIndex">Optional bit index for bit extraction within a word.</param>
|
||||
/// <param name="arrayCount">When non-null, read a 1-D array of this many <paramref name="type"/>
|
||||
/// elements; the boxed value is the element-typed CLR array (<c>int[]</c> / <c>float[]</c> /
|
||||
/// <c>bool[]</c> / <c>string[]</c> / …). When null, read a scalar (Phase 4c).</param>
|
||||
/// <param name="arrayCount">When non-null, read an array of this many <paramref name="type"/>
|
||||
/// elements (a multi-dimensional PLC array flattened in memory order; a count shorter than the PLC
|
||||
/// array reads its leading sub-range); the boxed value is the element-typed CLR array
|
||||
/// (<c>int[]</c> / <c>float[]</c> / <c>bool[]</c> / <c>string[]</c> / …) decoded by
|
||||
/// <see cref="TwinCATArrayCodec"/>. When null, read a scalar.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <returns>The boxed value read (or <c>null</c>) together with the mapped OPC UA status.</returns>
|
||||
Task<(object? value, uint status)> ReadValueAsync(
|
||||
@@ -87,6 +89,10 @@ public interface ITwinCATClient : IDisposable
|
||||
/// <param name="symbolPath">ADS symbol path (e.g. <c>MAIN.bStart</c>).</param>
|
||||
/// <param name="type">Declared type; drives the native layout + callback value boxing.</param>
|
||||
/// <param name="bitIndex">For BOOL-within-word tags — the bit to extract from the parent word.</param>
|
||||
/// <param name="arrayCount">When non-null (and no <paramref name="bitIndex"/>), register the whole
|
||||
/// array: <paramref name="arrayCount"/> elements of <paramref name="type"/> as raw bytes, decoded per
|
||||
/// notification into the same element-typed CLR array <see cref="ReadValueAsync"/> returns
|
||||
/// (<see cref="TwinCATArrayCodec"/>). Null = a scalar.</param>
|
||||
/// <param name="cycleTime">Minimum interval between change notifications (native-floor depends on target).</param>
|
||||
/// <param name="maxDelayMs">Maximum batching delay in milliseconds — TwinCAT may coalesce
|
||||
/// notifications up to this delay before pushing them. <c>0</c> = no batching, push
|
||||
@@ -94,13 +100,18 @@ public interface ITwinCATClient : IDisposable
|
||||
/// <param name="onChange">Invoked with <c>(symbolPath, boxedValue)</c> per notification.</param>
|
||||
/// <param name="cancellationToken">Cancels the initial registration; does not tear down an established notification.</param>
|
||||
/// <returns>A handle whose disposal tears the registered notification down.</returns>
|
||||
/// <exception cref="TwinCATTypeMismatchException">The declared <paramref name="type"/> cannot decode
|
||||
/// the PLC symbol (width, float/integer or string kind differ) — the caller isolates that one tag
|
||||
/// to Bad rather than registering a notification that would publish wrong bytes at Good.</exception>
|
||||
/// <exception cref="TwinCATNotificationRefusedException">The notification is refused for this one tag
|
||||
/// — the declared <paramref name="type"/> / <paramref name="arrayCount"/> cannot decode the PLC
|
||||
/// symbol (<see cref="TwinCATTypeMismatchException"/>: width, float/integer or string kind differ,
|
||||
/// not an array, too long), or the array exceeds
|
||||
/// <see cref="TwinCATTypeGuard.MaxNotificationArrayBytes"/>. The caller isolates that one tag to the
|
||||
/// exception's status rather than registering a notification that would publish wrong bytes at
|
||||
/// Good.</exception>
|
||||
Task<ITwinCATNotificationHandle> AddNotificationAsync(
|
||||
string symbolPath,
|
||||
TwinCATDataType type,
|
||||
int? bitIndex,
|
||||
int? arrayCount,
|
||||
TimeSpan cycleTime,
|
||||
int maxDelayMs,
|
||||
Action<string, object?> onChange,
|
||||
|
||||
Reference in New Issue
Block a user