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.
Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken);
/// True when the AMS router + target both accept commands.
bool IsConnected { get; }
///
/// 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). When
/// is non-null + non-empty, the symbol is treated
/// as a whole-array read and the boxed value is a flat 1-D CLR
/// sized to product(arrayDimensions).
///
Task<(object? value, uint status)> ReadValueAsync(
string symbolPath,
TwinCATDataType type,
int? bitIndex,
int[]? arrayDimensions,
CancellationToken cancellationToken);
///
/// Write a symbolic value. Returns the mapped OPC UA status for the operation
/// (0 = Good, non-zero = error mapped via ).
/// mirrors ; PR-1.4
/// ships read-only whole-array support so writers may surface BadNotSupported.
///
Task WriteValueAsync(
string symbolPath,
TwinCATDataType type,
int? bitIndex,
int[]? arrayDimensions,
object? value,
CancellationToken cancellationToken);
///
/// Bulk-read N scalar symbols in a single AMS request via Beckhoff's ADS Sum-command
/// family (IndexGroup 0xF080..0xF084). The result is a parallel array preserving
/// ordering — element i's outcome maps to request i.
/// Empty input returns an empty result without a wire round-trip.
///
///
/// This is the throughput-optimised path used by
/// to replace the per-tag loop — one ADS sum-read for N
/// symbols beats N individual round-trips by ~10× on the typical PLC link.
///
/// 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.
///
Task> ReadValuesAsync(
IReadOnlyList reads,
CancellationToken cancellationToken);
///
/// Bulk-write N scalar symbols in a single AMS request via Beckhoff's
/// SumWriteBySymbolPath. Result is a parallel status array preserving
/// ordering. Empty input returns an empty result.
///
///
/// Whole-array writes + bit-RMW writes are not in scope for the bulk path — those continue
/// through the per-tag path. The driver layer pre-filters.
///
Task> WriteValuesAsync(
IReadOnlyList writes,
CancellationToken cancellationToken);
///
/// Cheap health probe — returns true when the target's AMS state is reachable.
/// Used by 's probe loop.
///
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.
/// Minimum interval between change notifications (native-floor depends on target).
///
/// Per-tag MaxDelay in milliseconds — the upper bound on how long the runtime can
/// buffer / coalesce change events before dispatching them (PR 3.1 / issue #313).
/// 0 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 NotificationSettings(mode, cycleTime, maxDelay).
///
/// Invoked with (symbolPath, boxedValue) per notification.
/// Cancels the initial registration; does not tear down an established notification.
Task AddNotificationAsync(
string symbolPath,
TwinCATDataType type,
int? bitIndex,
TimeSpan cycleTime,
int maxDelayMs,
Action onChange,
CancellationToken cancellationToken);
///
/// Walk the target's symbol table via the TwinCAT SymbolLoaderFactory (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 DataType = null so callers can
/// decide whether to drill in via their own walker.
///
IAsyncEnumerable BrowseSymbolsAsync(CancellationToken cancellationToken);
///
/// 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.
///
Task FlushOptionalCachesAsync();
}
/// 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 ; null when the symbol's type
/// doesn't map onto our supported atomic surface (UDTs, pointers, function blocks).
/// true when the symbol's AccessRights flag forbids writes.
public sealed record TwinCATDiscoveredSymbol(
string InstancePath,
TwinCATDataType? DataType,
bool ReadOnly);
/// Factory for s. One client per device.
public interface ITwinCATClientFactory
{
ITwinCATClient Create();
}
/// One element of an request — the symbol path
/// + the IEC type for marshalling. Strings carry an explicit for
/// fixed-size STRING(n) declarations (defaults to 80 matching IEC 61131-3).
public sealed record TwinCATBulkReadItem(
string SymbolPath,
TwinCATDataType Type,
int StringLength = 80);
/// One element of an request.
/// Mirror of with the value to push.
public sealed record TwinCATBulkWriteItem(
string SymbolPath,
TwinCATDataType Type,
object? Value,
int StringLength = 80);