Auto: twincat-5.1 — IAlarmSource via TC3 EventLogger (gated, scaffold)
Closes #316
This commit is contained in:
@@ -217,3 +217,37 @@ in screen-recorded bug reports.
|
||||
`--poll-only` polls go through the same cached-handle path as `read`, so
|
||||
repeated polls of the same symbol carry only a 4-byte handle on the wire
|
||||
rather than the full symbolic path.
|
||||
|
||||
### `alarms` (PR 5.1 / #316)
|
||||
|
||||
Stream TC3 EventLogger alarms via the driver's `IAlarmSource` bridge.
|
||||
Subscribes against AMS port 110 (`AMSPORT_EVENTLOG`) on the same target,
|
||||
prints each event with timestamp / source / severity / message until
|
||||
Ctrl+C.
|
||||
|
||||
```powershell
|
||||
# All alarms — every event the EventLogger surfaces
|
||||
otopcua-twincat-cli alarms -n 192.168.1.40.1.1
|
||||
|
||||
# Filter by source — only events whose source name matches (case-insensitive)
|
||||
otopcua-twincat-cli alarms -n 192.168.1.40.1.1 --source Conveyor1.MotorOverload
|
||||
|
||||
# Multiple sources — repeat the flag
|
||||
otopcua-twincat-cli alarms -n 192.168.1.40.1.1 --source Conveyor1 --source Pump3
|
||||
```
|
||||
|
||||
| Flag | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `--source` | (none) | Optional source filter; repeat for multiple |
|
||||
|
||||
Output format (one line per event):
|
||||
|
||||
```
|
||||
[HH:mm:ss.fff] <source> sev=<Low|Medium|High|Critical> type=<event-class> cond=<condition-id> "<message>"
|
||||
```
|
||||
|
||||
The verb forces `EnableAlarms=true` on the underlying driver; the
|
||||
default driver config keeps it off so deployments without an
|
||||
EventLogger configured pay no cost. See
|
||||
[`docs/drivers/TwinCAT.md` §Alarms](drivers/TwinCAT.md) for the
|
||||
full bridge architecture and decode caveats.
|
||||
|
||||
@@ -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
115
docs/drivers/TwinCAT.md
Normal 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 0–255 `USINT`. The driver maps it onto
|
||||
the four-bucket `AlarmSeverity` enum the OPC UA AC layer consumes:
|
||||
|
||||
| TC3 severity | `AlarmSeverity` |
|
||||
| --- | --- |
|
||||
| 0–64 | `Low` |
|
||||
| 65–128 | `Medium` |
|
||||
| 129–192 | `High` |
|
||||
| 193–255 | `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
|
||||
101
docs/v3/twincat-eventlogger-spike.md
Normal file
101
docs/v3/twincat-eventlogger-spike.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# TC3 EventLogger spike — managed-wrapper investigation
|
||||
|
||||
**Question (b) from the PR 5.1 / #316 plan**: Does Beckhoff publish a
|
||||
managed `TcEventLogger` wrapper that lets the driver subscribe to
|
||||
alarms via `EventLogger.AlarmRaised` instead of decoding AMS port 110
|
||||
notifications by hand?
|
||||
|
||||
## TL;DR
|
||||
|
||||
**No managed wrapper.** The `Beckhoff.TwinCAT.Ads` v6 NuGet (the regular
|
||||
managed SDK the driver already takes a dependency on) ships only the
|
||||
ADS read/write/notification surface — it does not surface
|
||||
`TcEventLogger` on the .NET side. The C++ TcCOM headers
|
||||
(`TcEventLogger.h` etc.) exist in the on-box TwinCAT install
|
||||
(`%TC_INSTALLPATH%\Components\TcEventLogger\`) but there is no managed
|
||||
projection of those COM interfaces in any official Beckhoff NuGet as
|
||||
of TC3 build 4024.x.
|
||||
|
||||
Decision: **ship a binary-protocol decode against AMS port 110**
|
||||
(`AMSPORT_EVENTLOG`) with index group `ADSIGRP_TCEVENTLOG_ALARMS`. The
|
||||
decoder lands in `AdsTwinCATAlarmGate` (production) and `NullTwinCATAlarmGate`
|
||||
(default / no-op). Best-effort field decoding — fields the protocol
|
||||
analyzer hasn't yet identified surface as `"Unknown"`.
|
||||
|
||||
## What was checked
|
||||
|
||||
| Source | Result |
|
||||
| --- | --- |
|
||||
| `Beckhoff.TwinCAT.Ads` v6.x NuGet, namespace inventory | `TwinCAT.Ads`, `TwinCAT.Ads.SumCommand`, `TwinCAT.Ads.TypeSystem`, `TwinCAT.TypeSystem`. **No** `TcEventLogger` namespace. |
|
||||
| `Beckhoff.TwinCAT.Ads.TcpRouter` v6.x NuGet | Router only; no EventLogger surface. |
|
||||
| Beckhoff Information System (Infosys) → TwinCAT 3 → EventLogger → API reference | Documents only the C++ TcCOM API + the PLC-side `Tc3_EventLogger` library. No managed-language section. |
|
||||
| TwinCAT install on dev box → `Components\TcEventLogger\` | C++ headers + DLL only; the `.tlb` could be COM-imported via `tlbimp` but that creates a brittle install-path-coupled binding. |
|
||||
| Public Beckhoff GitHub orgs | `Beckhoff/TwinCAT-Tools-Library` etc. — no managed EventLogger wrapper. |
|
||||
|
||||
## Why decode at the wire?
|
||||
|
||||
A `tlbimp` projection of the on-box TcCOM `.tlb` would technically work
|
||||
but introduces three problems:
|
||||
|
||||
1. **Install-path coupling** — the `.tlb` lives under
|
||||
`%TC_INSTALLPATH%`; the driver would need to find / load it at
|
||||
runtime + ship a per-build interop assembly.
|
||||
2. **Bitness lock-in** — TcCOM is x86; the driver builds AnyCPU.
|
||||
3. **No upgrade path** — Beckhoff makes no API-stability guarantees
|
||||
on the TcCOM surface across TC3 builds.
|
||||
|
||||
Direct AMS-port-110 notifications keep the driver coupled to **only**
|
||||
the `Beckhoff.TwinCAT.Ads` v6 NuGet's stable wire surface. Trade-off:
|
||||
the binary protocol is undocumented in managed-code form; we work
|
||||
around that by:
|
||||
|
||||
- Writing a permissive decoder that surfaces unrecognised fields as
|
||||
`"Unknown"` rather than throwing.
|
||||
- Gating the entire bridge behind `EnableAlarms=false` so deployments
|
||||
that don't run TcEventLogger pay no cost.
|
||||
- Logging the raw payload at TRACE level when a decode partially
|
||||
succeeds, so operators can hand the bytes to the integration team
|
||||
for follow-up decoding.
|
||||
|
||||
## What ships in PR 5.1
|
||||
|
||||
- `ITwinCATAlarmGate` interface — driver-internal seam.
|
||||
- `NullTwinCATAlarmGate` — default no-op implementation, used when
|
||||
`EnableAlarms=false` and as the unit-test substitute base.
|
||||
- `TwinCATAlarmSource` — projects `TwinCATAlarmEvent` onto the
|
||||
driver's `IAlarmSource` surface; handles subscription bookkeeping
|
||||
+ source-id filtering.
|
||||
- `TwinCATDriver` declares `IAlarmSource`; methods short-circuit when
|
||||
the gate is null (default).
|
||||
- Production `AdsTwinCATAlarmGate` (with the binary decoder) is
|
||||
scaffolded — the wire path is best-effort and can be tightened in
|
||||
a follow-up PR without touching the driver's public surface.
|
||||
|
||||
## Open questions for the follow-up PR
|
||||
|
||||
1. **Exact byte layout** of the alarm-list notification payload —
|
||||
needs a wire trace from a known-good TC3 EventLogger configuration
|
||||
compared against the C++ `TcEventLogger.h` struct definitions.
|
||||
2. **Acknowledge wire format** — the `AcknowledgeAsync` path writes
|
||||
to the EventLogger ack index group; the operand layout (event-id
|
||||
vs. condition-id mapping) is best-effort in PR 5.1.
|
||||
3. **Multi-language alarm text** — TC3 EventLogger supports localized
|
||||
message texts. The decoder should pick the runtime's configured
|
||||
language; PR 5.1 falls back to the first text it finds.
|
||||
4. **Active-alarm refresh on subscribe** — TC3's `RefreshActive`
|
||||
semantic is documented in C++ but not exposed through AMS port 110
|
||||
notifications directly. The follow-up PR should investigate
|
||||
whether a separate `Read` against the active-alarm-list index
|
||||
group can backfill the snapshot at subscribe time.
|
||||
|
||||
## Why land PR 5.1 anyway
|
||||
|
||||
The driver's public `IAlarmSource` surface, the options knob, the unit
|
||||
tests, the CLI verb, and the integration-test scaffold are all
|
||||
independent of the wire decoder's completeness. Deferring the entire
|
||||
PR until decode coverage is 100 % blocks every consumer that just
|
||||
needs the capability negotiation contract (the OPC UA server's
|
||||
`DriverNodeManager` checks `driver is IAlarmSource` to decide whether
|
||||
to expose the alarm subtree). Shipping the gated scaffold now lets
|
||||
those consumers light up without committing to a specific decoder
|
||||
quality bar.
|
||||
Reference in New Issue
Block a user