namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; /// /// Wire-layer abstraction over one connection to a TwinCAT AMS target. One instance per /// ; reused across reads / writes / probes for the device. /// Tests swap in a fake via . /// /// /// 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 , many /// / calls. /// public interface ITwinCATClient : IDisposable { /// Establish the AMS connection. Idempotent — subsequent calls are no-ops when already connected. /// The target AMS address. /// The connection timeout. /// Cancellation token for the connection attempt. /// A task that represents the asynchronous operation. Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken); /// True when the AMS router + target both accept commands. bool IsConnected { get; } /// /// Raised when the client observes the ADS symbol-version-changed code /// ( — DeviceSymbolVersionInvalid /// 1809 / 0x0711) on any read / write / notification — the signal that a PLC program /// re-download has invalidated every symbol + notification handle. The driver forwards /// this to so Core /// rebuilds the address space subtree (docs/v2/driver-specs.md §6). /// event EventHandler? OnSymbolVersionChanged; /// /// Read a symbolic value. Returns a boxed .NET value matching the requested /// , or null when the read produced no data; the /// status tuple member carries the mapped OPC UA status (0 = Good). /// /// The ADS symbol path. /// The target data type. /// Optional bit index for bit extraction within a word. /// When non-null, read an array of this many /// 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 /// (int[] / float[] / bool[] / string[] / …) decoded by /// . When null, read a scalar. /// Cancellation token for the read operation. /// The boxed value read (or null) together with the mapped OPC UA status. Task<(object? value, uint status)> ReadValueAsync( string symbolPath, TwinCATDataType type, int? bitIndex, int? arrayCount, CancellationToken cancellationToken); /// /// Reads several symbols at once — the ADS sum-read path (Gitea #645 / #310). The result is a parallel /// list: element i is the outcome of [i], each decoded and /// status-mapped exactly as would (an item the sum command cannot carry — /// a bit-indexed BOOL, a handle-addressed symbol — is read individually inside the call). /// /// /// The default implementation reads one item at a time, so a client without a sum path (or a test /// fake) is correct if not faster. An implementation that issues a real sum command throws /// when the TARGET refuses the command as a whole /// (service / index group not supported); the caller then falls back to individual reads. A /// comms-level failure propagates as the ordinary exception. /// /// The symbols to read, at most the caller's chunk size. /// Cancellation token for the read. /// One (value, status) per item, in request order. /// The target refused the sum command itself. async Task> ReadValuesAsync( IReadOnlyList reads, CancellationToken cancellationToken) { var results = new (object? value, uint status)[reads.Count]; for (var i = 0; i < reads.Count; i++) { var r = reads[i]; results[i] = await ReadValueAsync(r.SymbolPath, r.Type, r.BitIndex, r.ArrayCount, cancellationToken) .ConfigureAwait(false); } return results; } /// /// The target's ADS device state (Run, Stop, Config, …) as text, or null /// when it cannot be read. Feeds the driver's /hosts diagnostics (Gitea #645 / #314). /// Default null — a client without a state read simply reports none. /// /// Cancellation token for the read. /// The state name, or null. Task ReadAdsStateAsync(CancellationToken cancellationToken) => Task.FromResult(null); /// /// Write a symbolic value. Returns the mapped OPC UA status for the operation /// (0 = Good, non-zero = error mapped via ). /// /// The ADS symbol path. /// The data type. /// Optional bit index for bit manipulation within a word. /// The value to write. /// Cancellation token for the write operation. /// The mapped OPC UA status for the write (0 = Good). Task WriteValueAsync( string symbolPath, TwinCATDataType type, int? bitIndex, object? value, CancellationToken cancellationToken); /// /// Cheap health probe — returns true when the target's AMS state is reachable. /// Used by 's probe loop. /// /// Cancellation token for the probe operation. /// true when the target is reachable; otherwise false. Task ProbeAsync(CancellationToken cancellationToken); /// /// Register a cyclic / on-change ADS notification for a symbol. Returns a handle whose /// 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. /// /// ADS symbol path (e.g. MAIN.bStart). /// Declared type; drives the native layout + callback value boxing. /// For BOOL-within-word tags — the bit to extract from the parent word. /// When non-null (and no ), register the whole /// array: elements of as raw bytes, decoded per /// notification into the same element-typed CLR array returns /// (). Null = a scalar. /// Minimum interval between change notifications (native-floor depends on target). /// Maximum batching delay in milliseconds — TwinCAT may coalesce /// notifications up to this delay before pushing them. 0 = no batching, push /// immediately. /// Invoked with (symbolPath, boxedValue) per notification. /// Cancels the initial registration; does not tear down an established notification. /// A handle whose disposal tears the registered notification down. /// The notification is refused for this one tag /// — the declared / cannot decode the PLC /// symbol (: width, float/integer or string kind differ, /// not an array, too long), or the array exceeds /// . The caller isolates that one tag to the /// exception's status rather than registering a notification that would publish wrong bytes at /// Good. Task AddNotificationAsync( string symbolPath, TwinCATDataType type, int? bitIndex, int? arrayCount, TimeSpan cycleTime, int maxDelayMs, Action onChange, CancellationToken cancellationToken); /// /// Walk the target's symbol table via the TwinCAT SymbolLoaderFactory (flat mode) /// and expand all struct / UDT / function-block symbols to their atomic member leaves. /// The implementation uses to recursively /// descend each struct/UDT/FB-instance symbol into its addressable atomic members, /// yielding only atomic leaves (scalars or 1-D arrays with a non-null /// ) and dropping struct/UDT/FB containers. /// Callers receive fully-qualified InstancePaths (e.g. MAIN.Motor1.Speed) /// and never see struct-typed entries — expansion is performed here, not in the caller. /// /// Cancellation token for the enumeration operation. /// An asynchronous stream of the target's atomic-leaf symbols. IAsyncEnumerable BrowseSymbolsAsync(CancellationToken cancellationToken); } /// One element of an request — the same four facts /// takes. /// The ADS symbol path (a bit-indexed BOOL keeps its .N suffix, as for a single read). /// The declared data type. /// The bit to extract for a BOOL-within-word tag; null otherwise. /// The authored array length for an array tag; null for a scalar. public sealed record TwinCATBulkReadItem( string SymbolPath, TwinCATDataType Type, int? BitIndex = null, int? ArrayCount = null); /// Opaque handle for a registered ADS notification. tears it down. public interface ITwinCATNotificationHandle : IDisposable { } /// /// One symbol yielded by — full instance /// path + detected + read-only flag. /// /// Full dotted symbol path (e.g. MAIN.bStart, GVL.Counter). /// Mapped of the (element) type; null /// when the symbol's type doesn't map onto our supported atomic surface (UDTs, pointers, /// function blocks). For an array symbol this is the ELEMENT type, with /// carrying the dimension. /// true when the symbol's AccessRights flag forbids writes. /// When non-null, the symbol is a 1-D array of this many /// elements. null = scalar. Multi-dimensional arrays are /// reported as null (treated as scalar/unsupported) — only 1-D is in scope for Phase 4c. public sealed record TwinCATDiscoveredSymbol( string InstancePath, TwinCATDataType? DataType, bool ReadOnly, int? ArrayLength = null); /// Factory for s. One client per device. public interface ITwinCATClientFactory { /// Creates a new TwinCAT client instance. /// A new, not-yet-connected . ITwinCATClient Create(); }