fb57717f6f
Closes #313
174 lines
9.3 KiB
C#
174 lines
9.3 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||
|
||
/// <summary>
|
||
/// Wire-layer abstraction over one connection to a TwinCAT AMS target. One instance per
|
||
/// <see cref="TwinCATAmsAddress"/>; reused across reads / writes / probes for the device.
|
||
/// Tests swap in a fake via <see cref="ITwinCATClientFactory"/>.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Unlike libplctag-backed drivers where one native handle exists per tag, TwinCAT's
|
||
/// AdsClient is one connection per target with symbolic reads / writes issued against it.
|
||
/// The abstraction reflects that — single <see cref="ConnectAsync"/>, many
|
||
/// <see cref="ReadValueAsync"/> / <see cref="WriteValueAsync"/> calls.
|
||
/// </remarks>
|
||
public interface ITwinCATClient : IDisposable
|
||
{
|
||
/// <summary>Establish the AMS connection. Idempotent — subsequent calls are no-ops when already connected.</summary>
|
||
Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken);
|
||
|
||
/// <summary>True when the AMS router + target both accept commands.</summary>
|
||
bool IsConnected { get; }
|
||
|
||
/// <summary>
|
||
/// Read a symbolic value. Returns a boxed .NET value matching the requested
|
||
/// <paramref name="type"/>, or <c>null</c> when the read produced no data; the
|
||
/// <c>status</c> tuple member carries the mapped OPC UA status (0 = Good). When
|
||
/// <paramref name="arrayDimensions"/> is non-null + non-empty, the symbol is treated
|
||
/// as a whole-array read and the boxed value is a flat 1-D CLR
|
||
/// <see cref="Array"/> sized to <c>product(arrayDimensions)</c>.
|
||
/// </summary>
|
||
Task<(object? value, uint status)> ReadValueAsync(
|
||
string symbolPath,
|
||
TwinCATDataType type,
|
||
int? bitIndex,
|
||
int[]? arrayDimensions,
|
||
CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Write a symbolic value. Returns the mapped OPC UA status for the operation
|
||
/// (0 = Good, non-zero = error mapped via <see cref="TwinCATStatusMapper"/>).
|
||
/// <paramref name="arrayDimensions"/> mirrors <see cref="ReadValueAsync"/>; PR-1.4
|
||
/// ships read-only whole-array support so writers may surface <c>BadNotSupported</c>.
|
||
/// </summary>
|
||
Task<uint> WriteValueAsync(
|
||
string symbolPath,
|
||
TwinCATDataType type,
|
||
int? bitIndex,
|
||
int[]? arrayDimensions,
|
||
object? value,
|
||
CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Bulk-read N scalar symbols in a single AMS request via Beckhoff's ADS Sum-command
|
||
/// family (IndexGroup <c>0xF080..0xF084</c>). The result is a parallel array preserving
|
||
/// <paramref name="reads"/> ordering — element <c>i</c>'s outcome maps to request <c>i</c>.
|
||
/// Empty input returns an empty result without a wire round-trip.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>This is the throughput-optimised path used by <see cref="TwinCATDriver.ReadAsync"/>
|
||
/// to replace the per-tag <see cref="ReadValueAsync"/> loop — one ADS sum-read for N
|
||
/// symbols beats N individual round-trips by ~10× on the typical PLC link.</para>
|
||
///
|
||
/// <para>Whole-array reads + bit-extracted BOOL reads stay on the per-tag path because
|
||
/// the Sum-command surface only marshals scalars + bitIndex needs CLR-side post-processing.
|
||
/// Callers should pre-filter or fall back as appropriate.</para>
|
||
/// </remarks>
|
||
Task<IReadOnlyList<(object? value, uint status)>> ReadValuesAsync(
|
||
IReadOnlyList<TwinCATBulkReadItem> reads,
|
||
CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Bulk-write N scalar symbols in a single AMS request via Beckhoff's
|
||
/// <c>SumWriteBySymbolPath</c>. Result is a parallel status array preserving
|
||
/// <paramref name="writes"/> ordering. Empty input returns an empty result.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Whole-array writes + bit-RMW writes are not in scope for the bulk path — those continue
|
||
/// through the per-tag <see cref="WriteValueAsync"/> path. The driver layer pre-filters.
|
||
/// </remarks>
|
||
Task<IReadOnlyList<uint>> WriteValuesAsync(
|
||
IReadOnlyList<TwinCATBulkWriteItem> writes,
|
||
CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Cheap health probe — returns <c>true</c> when the target's AMS state is reachable.
|
||
/// Used by <see cref="Core.Abstractions.IHostConnectivityProbe"/>'s probe loop.
|
||
/// </summary>
|
||
Task<bool> ProbeAsync(CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Register a cyclic / on-change ADS notification for a symbol. Returns a handle whose
|
||
/// <see cref="IDisposable.Dispose"/> tears the notification down. Callback fires on the
|
||
/// thread libplctag / AdsClient uses for notifications — consumers should marshal to
|
||
/// their own scheduler before doing work of any size.
|
||
/// </summary>
|
||
/// <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="cycleTime">Minimum interval between change notifications (native-floor depends on target).</param>
|
||
/// <param name="maxDelayMs">
|
||
/// Per-tag <c>MaxDelay</c> in milliseconds — the upper bound on how long the runtime can
|
||
/// buffer / coalesce change events before dispatching them (PR 3.1 / issue #313).
|
||
/// <c>0</c> means "fire ASAP, no coalescing" (the pre-PR-3.1 default behaviour); larger
|
||
/// values let the runtime coalesce bursty high-frequency changes so the OPC UA queue
|
||
/// downstream doesn't flood. Plumbs straight into <c>NotificationSettings(mode, cycleTime, maxDelay)</c>.
|
||
/// </param>
|
||
/// <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>
|
||
Task<ITwinCATNotificationHandle> AddNotificationAsync(
|
||
string symbolPath,
|
||
TwinCATDataType type,
|
||
int? bitIndex,
|
||
TimeSpan cycleTime,
|
||
int maxDelayMs,
|
||
Action<string, object?> onChange,
|
||
CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// Walk the target's symbol table via the TwinCAT <c>SymbolLoaderFactory</c> (flat mode).
|
||
/// Yields each top-level symbol the PLC exposes — global variables, program-scope locals,
|
||
/// function-block instance fields. Filters for our atomic type surface; structured /
|
||
/// UDT / function-block typed symbols surface with <c>DataType = null</c> so callers can
|
||
/// decide whether to drill in via their own walker.
|
||
/// </summary>
|
||
IAsyncEnumerable<TwinCATDiscoveredSymbol> BrowseSymbolsAsync(CancellationToken cancellationToken);
|
||
|
||
/// <summary>
|
||
/// PR 2.2 — wipe process-scoped optional caches (today: the ADS variable-handle
|
||
/// cache backing per-tag reads / writes). Surfaces so operators + the future PR
|
||
/// 2.3 Symbol-Version invalidation listener can flush stale handles after a known
|
||
/// online change without forcing a full reconnect. Safe to call mid-traffic — in-flight
|
||
/// reads continue to use the handles they already hold; the next read for a symbol
|
||
/// will re-resolve. Best-effort wire-side delete; failures are swallowed.
|
||
/// </summary>
|
||
Task FlushOptionalCachesAsync();
|
||
}
|
||
|
||
/// <summary>Opaque handle for a registered ADS notification. <see cref="IDisposable.Dispose"/> tears it down.</summary>
|
||
public interface ITwinCATNotificationHandle : IDisposable { }
|
||
|
||
/// <summary>
|
||
/// One symbol yielded by <see cref="ITwinCATClient.BrowseSymbolsAsync"/> — full instance
|
||
/// path + detected <see cref="TwinCATDataType"/> + read-only flag.
|
||
/// </summary>
|
||
/// <param name="InstancePath">Full dotted symbol path (e.g. <c>MAIN.bStart</c>, <c>GVL.Counter</c>).</param>
|
||
/// <param name="DataType">Mapped <see cref="TwinCATDataType"/>; <c>null</c> when the symbol's type
|
||
/// doesn't map onto our supported atomic surface (UDTs, pointers, function blocks).</param>
|
||
/// <param name="ReadOnly"><c>true</c> when the symbol's AccessRights flag forbids writes.</param>
|
||
public sealed record TwinCATDiscoveredSymbol(
|
||
string InstancePath,
|
||
TwinCATDataType? DataType,
|
||
bool ReadOnly);
|
||
|
||
/// <summary>Factory for <see cref="ITwinCATClient"/>s. One client per device.</summary>
|
||
public interface ITwinCATClientFactory
|
||
{
|
||
ITwinCATClient Create();
|
||
}
|
||
|
||
/// <summary>One element of an <see cref="ITwinCATClient.ReadValuesAsync"/> request — the symbol path
|
||
/// + the IEC type for marshalling. Strings carry an explicit <paramref name="StringLength"/> for
|
||
/// fixed-size <c>STRING(n)</c> declarations (defaults to <c>80</c> matching IEC 61131-3).</summary>
|
||
public sealed record TwinCATBulkReadItem(
|
||
string SymbolPath,
|
||
TwinCATDataType Type,
|
||
int StringLength = 80);
|
||
|
||
/// <summary>One element of an <see cref="ITwinCATClient.WriteValuesAsync"/> request.
|
||
/// Mirror of <see cref="TwinCATBulkReadItem"/> with the value to push.</summary>
|
||
public sealed record TwinCATBulkWriteItem(
|
||
string SymbolPath,
|
||
TwinCATDataType Type,
|
||
object? Value,
|
||
int StringLength = 80);
|