Auto: twincat-5.1 — IAlarmSource via TC3 EventLogger (gated, scaffold)

Closes #316
This commit is contained in:
Joseph Doherty
2026-04-26 11:13:24 -04:00
parent 3babfb8a99
commit c88e0b6bed
13 changed files with 1238 additions and 7 deletions

View File

@@ -89,7 +89,22 @@ default 1024-element cap (UDT per-member coverage; see
Capability surfaces whose contract is verified: `IDriver`, `IReadable`,
`IWritable`, `ITagDiscovery`, `ISubscribable`, `IHostConnectivityProbe`,
`IPerCallHostResolver`.
`IPerCallHostResolver`, `IAlarmSource` (PR 5.1 / #316, gated behind
`EnableAlarms=true` — see capability matrix below).
## Capability matrix
| Capability | Status | Notes |
| --- | --- | --- |
| `IDriver` | yes | Lifecycle + health |
| `IReadable` | yes | Sum-read for scalars; per-tag for bit / array |
| `IWritable` | yes | Sum-write for scalars; per-tag for bit-RMW / array |
| `ITagDiscovery` | yes | Pre-declared + opt-in symbol-table walk |
| `ISubscribable` | yes | Native ADS notifications by default; poll fallback |
| `IHostConnectivityProbe` | yes | `ReadStateAsync` + system-symbol diagnostics |
| `IPerCallHostResolver` | yes | Tag → device hostAddress |
| `IAlarmSource` (PR 5.1 / #316) | partial | Scaffold + unit-tested; live wire decode is best-effort against AMS port 110, see `docs/v3/twincat-eventlogger-spike.md` |
| `IHistoryProvider` | no | Not in scope for this driver family |
## What it does NOT cover
@@ -134,11 +149,11 @@ Native ADS notifications fire on the PLC cycle boundary. The fake test
harness assumes notifications fire on a timer the test controls;
cycle-aligned firing under real PLC control is not verified.
### 6. Alarms / history
### 6. History
Driver doesn't implement `IAlarmSource` or `IHistoryProvider` — not in
scope for this driver family. TwinCAT 3's TcEventLogger could theoretically
back an `IAlarmSource`, but shipping that is a separate feature.
Driver doesn't implement `IHistoryProvider` — not in scope for this
driver family. (Alarms now have a dedicated `IAlarmSource` bridge — see
the capability matrix below + `docs/drivers/TwinCAT.md`.)
## When to trust TwinCAT tests, when to reach for a rig

115
docs/drivers/TwinCAT.md Normal file
View File

@@ -0,0 +1,115 @@
# TwinCAT driver — operator guide
Beckhoff TwinCAT 2 / TwinCAT 3 ADS driver. Talks to the runtime via
`Beckhoff.TwinCAT.Ads` v6 (managed); requires a reachable AMS router on
the host (local TwinCAT XAR, the standalone `Beckhoff.TwinCAT.Ads.TcpRouter`
NuGet, or any Windows box with TwinCAT installed and an authorised AMS
route).
## Configuration surface
`TwinCATDriverOptions` (one instance supports N AMS targets, each a
`TwinCATDeviceOptions`). Wire format mirrors the C# class on the JSON
side — every `init`-only property round-trips through
`System.Text.Json` with the default options.
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| `Devices` | `TwinCATDeviceOptions[]` | `[]` | One entry per AMS target. |
| `Tags` | `TwinCATTagDefinition[]` | `[]` | Pre-declared symbol set. |
| `Probe.Enabled` | `bool` | `true` | Per-tick `ReadStateAsync` against the runtime. |
| `Probe.Interval` | `TimeSpan` | `5 s` | |
| `Timeout` | `TimeSpan` | `2 s` | Per-operation timeout. |
| `UseNativeNotifications` | `bool` | `true` | False = fall through to PollGroupEngine. |
| `EnableControllerBrowse` | `bool` | `false` | Walk symbol table on `DiscoverAsync`. |
| `MaxArrayExpansion` | `int` | `1024` | Per-element cutoff during nested-UDT browse. |
| `EnableAlarms` (PR 5.1) | `bool` | `false` | Opt-in TC3 EventLogger bridge — see "Alarms" below. |
## Alarms (TC3 EventLogger bridge, PR 5.1 / #316)
When `EnableAlarms=true`, the driver implements `IAlarmSource` by
opening a second `AdsClient` against AMS port **110**
(`AMSPORT_EVENTLOG`) and adding a device notification on
`ADSIGRP_TCEVENTLOG_ALARMS`. Subscribers receive `OnAlarmEvent`
notifications for every transition the EventLogger surfaces (raise /
clear / acknowledge).
### Decode caveat
Beckhoff doesn't ship a managed wrapper for `TcEventLogger` in the
regular `Beckhoff.TwinCAT.Ads` v6 NuGet — only the C++ TcCOM headers
exist. The driver therefore decodes the AMS-port-110 binary payload
manually. The current implementation is best-effort: event class GUIDs
and source names usually decode cleanly; some less-common fields may
surface as `"Unknown"` until a follow-up PR lands a complete decoder.
Spike output captured at
[`docs/v3/twincat-eventlogger-spike.md`](../v3/twincat-eventlogger-spike.md).
### Wire path
| Layer | What it does |
| --- | --- |
| Primary `AdsClient` | The existing per-device session against the PLC runtime port (default `851`) — handles reads / writes / native subscriptions. |
| Secondary `AdsClient` (alarms) | Opens against AMS port `110` on the same target NetId. Adds one device notification on `ADSIGRP_TCEVENTLOG_ALARMS` with a `length=...` payload covering the full alarm-list shape. |
| `ITwinCATAlarmGate` (driver-internal) | Decodes incoming notifications into `TwinCATAlarmEvent` records (`EventClass`, `Source`, `Severity`, `Message`, `OccurrenceUtc`, `Acked`). |
| `TwinCATAlarmSource` | Projects `TwinCATAlarmEvent` onto the driver-agnostic `IAlarmSource.OnAlarmEvent`. |
### Severity mapping (TC3 → OPC UA AC)
TC3 EventLogger severity is a 0255 `USINT`. The driver maps it onto
the four-bucket `AlarmSeverity` enum the OPC UA AC layer consumes:
| TC3 severity | `AlarmSeverity` |
| --- | --- |
| 064 | `Low` |
| 65128 | `Medium` |
| 129192 | `High` |
| 193255 | `Critical` |
### Acknowledge
`AcknowledgeAsync` round-trips through `ITwinCATAlarmGate.AcknowledgeAsync`,
which writes to the EventLogger ack index group. Best-effort — the wire
format isn't documented in managed code, so individual ack failures don't
poison the batch and the gate returns silently when the EventLogger isn't
configured.
### Disabling
`EnableAlarms=false` (default) returns a sentinel handle from
`SubscribeAlarmsAsync` and never opens the secondary `AdsClient`.
`OnAlarmEvent` simply never fires. Capability negotiation still works,
which is why the driver advertises `IAlarmSource` unconditionally.
## CLI
The `otopcua-twincat-cli` test client exposes an `alarms` subcommand
that wraps the bridge end-to-end:
```powershell
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli -- alarms `
--ams-net-id 5.23.91.23.1.1 --ams-port 851 `
--source Conveyor1.MotorOverload
```
See [`docs/Driver.TwinCAT.Cli.md`](../Driver.TwinCAT.Cli.md) for the
full CLI surface.
## Test coverage
- **Unit**: `TwinCATAlarmSourceTests` covers (a) feature-gating off vs.
on, (b) gate-event projection shape, (c) multi-event ordering, (d)
source-filter matching, (e) acknowledge round-trip, (f) JSON DTO
round-trip.
- **Integration**: `TwinCATAlarmIntegrationTests.Driver_raises_alarm_event_when_PLC_logs_event`
ships build-only in PR 5.1; the GVL + FB_AlarmHarness ship as XAE
stubs at `tests/.../TwinCatProject/PLC/`. Once the XAR project
imports them the test transitions skip → pass.
## See also
- [`docs/v3/twincat-eventlogger-spike.md`](../v3/twincat-eventlogger-spike.md)
— spike output for the managed-wrapper question
- [`docs/drivers/TwinCAT-Test-Fixture.md`](TwinCAT-Test-Fixture.md)
— coverage map + capability matrix
- [`docs/Driver.TwinCAT.Cli.md`](../Driver.TwinCAT.Cli.md) — CLI guide