Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Contracts/TwinCATDriverOptions.cs
T
Joseph Doherty fbcb521d1b fix(twincat): review fixes for sum-read and diagnostics (#645)
Strict width+kind eligibility for sum-batched scalars (others read singly); latch only on ADS
1793/1794, split 1792/1797/1798 without latching; 32 KiB per-command budget and 500-item ceiling;
short sum response handled per item. Diagnostics: opt-in diagnosticsEnabled (default off), reuse the
probe's ReadState, sample after the host-state transition, JitterMs renamed HeadroomMs, compact
per-device line so many devices fit the 16-entry cap, unavailable-symbol latch cleared on reconnect.
AdminUI: min/max on sum-read max, diagnostics checkbox. Pure planner extracted and unit-tested.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:29:35 -04:00

135 lines
7.3 KiB
C#

using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
/// <summary>
/// TwinCAT ADS driver configuration. One instance supports N targets (each identified by
/// an AMS Net ID + port). Compiles + runs without a local AMS router but every wire call
/// fails with <c>BadCommunicationError</c> until a router is reachable. The tags the driver
/// serves are delivered as authored raw tags in <see cref="RawTags"/>; the driver maps each
/// through <see cref="TwinCATTagDefinitionFactory.FromTagConfig"/> to build its
/// RawPath → definition table, routing each to its device by the entry's
/// <see cref="RawTagEntry.DeviceName"/> (multi-device).
/// </summary>
public sealed class TwinCATDriverOptions
{
/// <summary>Gets the list of TwinCAT devices to connect to.</summary>
public IReadOnlyList<TwinCATDeviceOptions> Devices { get; init; } = [];
/// <summary>
/// Authored raw tags this driver serves. The deploy artifact hands each authored raw
/// <c>Tag</c> as a <see cref="RawTagEntry"/> (RawPath identity + driver <c>TagConfig</c> blob +
/// WriteIdempotent flag + owning <see cref="RawTagEntry.DeviceName"/>); the driver maps each
/// through <see cref="TwinCATTagDefinitionFactory.FromTagConfig"/> into its RawPath → definition
/// table and routes it to the matching <see cref="Devices"/> entry by DeviceName.
/// </summary>
public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];
/// <summary>Gets the probe options for TwinCAT connection probing.</summary>
public TwinCATProbeOptions Probe { get; init; } = new();
/// <summary>Gets the default communication timeout.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
/// <summary>
/// When <c>true</c> (default), <c>SubscribeAsync</c> registers native ADS notifications
/// via <c>AddDeviceNotificationExAsync</c> — the PLC pushes changes on its own cycle
/// rather than the driver polling. Strictly better for latency + CPU when the target
/// supports it (TC2 + TC3 PLC runtimes always do; some soft-PLC / third-party ADS
/// implementations may not). When <c>false</c>, the driver falls through to the shared
/// <c>PollGroupEngine</c> — same semantics as the other
/// libplctag-backed drivers. Set <c>false</c> for deployments where the AMS router has
/// notification limits you can't raise.
/// </summary>
public bool UseNativeNotifications { get; init; } = true;
/// <summary>
/// When <c>true</c>, <c>DiscoverAsync</c> walks each device's symbol table via the
/// TwinCAT <c>SymbolLoaderFactory</c> (flat mode) + surfaces controller-resident
/// globals / program locals under a <c>Discovered/</c> sub-folder. Authored raw tags
/// from <see cref="RawTags"/> always emit regardless. Default <c>false</c> to preserve
/// the strict-config path for deployments where only declared tags should appear.
/// </summary>
public bool EnableControllerBrowse { get; init; }
/// <summary>
/// Maximum batching delay in milliseconds for ADS device notifications. Passed to
/// <c>NotificationSettings</c> as the max-delay value: TwinCAT may coalesce notifications
/// up to this delay before pushing them. <c>0</c> (default) = no batching, push
/// immediately. Useful for high-churn signals where the OPC UA subscriber tolerates a
/// small delay in exchange for fewer wire round-trips. Listed in <c>docs/v2/driver-specs.md</c>
/// section 6 — was previously hard-coded to 0.
/// </summary>
public int NotificationMaxDelayMs { get; init; }
/// <summary>
/// When <c>true</c>, a poll's reads for one device are batched into ADS sum-read commands
/// (IndexGroup <c>0xF080</c>) of up to <see cref="SumReadMaxItems"/> symbols each instead of one
/// round-trip per tag (Gitea #645 / #310). Default <c>false</c> (opt-in): a TwinCAT 2 CE runtime's
/// sum-command support is unverified. A target that refuses the sum command is detected at runtime
/// and that device falls back to individual reads for the rest of the driver's life.
/// </summary>
public bool UseSumRead { get; init; }
/// <summary>
/// Maximum symbols per ADS sum-read command; larger polls are split into chunks of this size.
/// Bounds the request/response size against the target's ADS buffer. Values below 1 are treated as 1.
/// Values above 500 (the ADS sub-command ceiling) are treated as 500. Only consulted when
/// <see cref="UseSumRead"/> is <c>true</c>.
/// </summary>
public int SumReadMaxItems { get; init; } = 200;
/// <summary>
/// When <c>true</c>, each healthy probe tick also reads up to four <c>TwinCAT_SystemInfoVarList</c>
/// symbols (app name, online-change count, task cycle and last-exec time) and the ADS state the probe
/// already fetched, and publishes them on the <c>/hosts</c> diagnostics bag (Gitea #645 / #314).
/// Default <c>false</c>: that is up to four extra read requests per device per probe interval against
/// the PLC, not yet live-gated. Pending the read-only live gate on a TC2 and a TC3 target.
/// </summary>
public bool DiagnosticsEnabled { get; init; }
/// <summary>
/// Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
/// 60s server-side maximum; this default is what the form pre-fills for new instances.
/// </summary>
[Display(Name = "Probe timeout (seconds)", Description = "Connection test timeout. Default 10s.", GroupName = "Diagnostics")]
[Range(1, 60)]
public int ProbeTimeoutSeconds { get; init; } = 10;
}
/// <summary>
/// One TwinCAT target. <paramref name="HostAddress"/> must parse via
/// <c>TwinCATAmsAddress.TryParse</c>; misconfigured devices fail driver initialisation.
/// </summary>
public sealed record TwinCATDeviceOptions(
string HostAddress,
string? DeviceName = null);
/// <summary>
/// One TwinCAT-backed OPC UA variable. <paramref name="SymbolPath"/> is the full TwinCAT
/// symbolic name (e.g. <c>MAIN.bStart</c>, <c>GVL.Counter</c>, <c>Motor1.Status.Running</c>).
/// </summary>
/// <param name="ArrayLength">
/// When non-null, this tag is a 1-D array of <paramref name="ArrayLength"/> elements of
/// <paramref name="DataType"/>. Drives <c>IsArray</c>/<c>ArrayDim</c> at discovery and a
/// native ADS array read at runtime (Phase 4c). <c>null</c> = scalar (the default).
/// </param>
public sealed record TwinCATTagDefinition(
string Name,
string DeviceHostAddress,
string SymbolPath,
TwinCATDataType DataType,
bool Writable = true,
bool WriteIdempotent = false,
int? ArrayLength = null);
/// <summary>Probe options for TwinCAT connection monitoring.</summary>
public sealed class TwinCATProbeOptions
{
/// <summary>Gets a value indicating whether probing is enabled.</summary>
public bool Enabled { get; init; } = true;
/// <summary>Gets the probe interval.</summary>
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
/// <summary>Gets the probe timeout.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
}