101 lines
6.7 KiB
Markdown
101 lines
6.7 KiB
Markdown
# AB Legacy Driver
|
|
|
|
In-process native-protocol driver that exposes legacy Allen-Bradley PLCs —
|
|
**SLC 500**, **MicroLogix**, **PLC-5**, and Logix-via-PCCC — as OPC UA nodes. It
|
|
runs inside the OtOpcUa server's .NET 10 AnyCPU process and speaks PCCC over
|
|
EtherNet/IP through the same libplctag.NET wrapper as the AB CIP driver, but
|
|
addresses data by **file** (data-table) rather than by symbolic tag. One driver
|
|
instance can serve many devices; per-device routing is keyed on the canonical
|
|
`ab://gateway[:port]/cip-path` host-address string. PCCC has no native push
|
|
model, so subscriptions are a polling overlay on top of `IReadable`.
|
|
|
|
For the driver spec (capability surface, config shape, payload limits), see
|
|
[docs/v2/driver-specs.md §4](../v2/driver-specs.md). For the manual test client,
|
|
see [Driver.AbLegacy.Cli.md](../Driver.AbLegacy.Cli.md). For the integration
|
|
fixture coverage map, see [AbLegacy-Test-Fixture.md](AbLegacy-Test-Fixture.md).
|
|
|
|
## Project Layout
|
|
|
|
| Project | Role |
|
|
|---------|------|
|
|
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/` | The driver — `AbLegacyDriver`, the libplctag runtime wrapper, the PCCC file-address parser (`AbLegacyAddress`), the host-address parser, and the status mapper. |
|
|
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Contracts/` | `AbLegacyDriverOptions`, `AbLegacyDeviceOptions`, `AbLegacyTagDefinition`, and the `AbLegacyDataType` / `AbLegacyPlcFamily` / `AbLegacyPlcFamilyProfile` records bound from the driver's `DriverConfig` JSON. |
|
|
|
|
Per family the `AbLegacyPlcFamilyProfile` supplies the libplctag `plc` attribute,
|
|
default CIP path, max-payload bytes, and the `SupportsStringFile` /
|
|
`SupportsLongFile` capability flags. MicroLogix uses direct EIP (empty default
|
|
path); MicroLogix and PLC-5 don't ship L-files; PLC-5 predates them entirely.
|
|
Tag types are validated against the device's profile at init time — declaring a
|
|
`Long` or `String` tag on a family that can't support it fails fast with a clear
|
|
message.
|
|
|
|
## Capability Surface
|
|
|
|
`AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver, IDisposable, IAsyncDisposable`
|
|
(`Driver.AbLegacy/AbLegacyDriver.cs`). There is **no `IAlarmSource`** — unlike the
|
|
AB CIP driver, PCCC has no ALMD instruction to project, so alarms are out of
|
|
scope.
|
|
|
|
| Capability | Implementation entry point | Notes |
|
|
|------------|---------------------------|-------|
|
|
| `ITagDiscovery` | `DiscoverAsync` | Emits pre-declared tags under per-device folders. Tags are single-element today (`IsArray` hard-wired false); multi-element file ranges are a tracked follow-up. |
|
|
| `IReadable` | `ReadAsync` | Per-tag reads serialized per cached runtime under a lock (a libplctag `Tag` handle is not concurrency-safe across the server read path + poll loop). |
|
|
| `IWritable` | `WriteAsync` | Bit-within-word writes (N-file `N7:0/3`, B-file bits) do a per-parent-word read-modify-write under a lock. Non-writable tags return `BadNotWritable`. |
|
|
| `ISubscribable` | `SubscribeAsync` driven by the shared `PollGroupEngine` | No push model — subscriptions become polling groups. |
|
|
| `IHostConnectivityProbe` | `ProbeLoopAsync` + `GetHostStatuses` | One probe loop per device reading `Probe.ProbeAddress`; transitions log Warning (down) / Information (recover). |
|
|
| `IPerCallHostResolver` | `ResolveHost` | Routes each call to the tag's `DeviceHostAddress`; unknown references fall back to the first device, never throwing (per the interface contract). |
|
|
|
|
## Addressing Model
|
|
|
|
Per-device host addresses are the canonical `ab://gateway[:port]/cip-path` form
|
|
parsed by `AbLegacyHostAddress.TryParse`. When the parsed CIP path is empty the
|
|
family profile's default path is used (e.g. SLC 500 gets `1,0`; MicroLogix stays
|
|
empty for direct EIP).
|
|
|
|
Tags carry a PCCC **file address** parsed by `AbLegacyAddress` (`AbLegacyAddress.cs`)
|
|
— file letter + file number + word number, with an optional bit index (`/N`) or
|
|
structured sub-element (`.ACC`, `.PRE`, …). The string is passed straight through
|
|
to libplctag's `name=` attribute; the parser validates shape and surfaces the
|
|
pieces for driver-side routing (e.g. deciding a bit needs read-modify-write):
|
|
|
|
| Form | Meaning |
|
|
|------|---------|
|
|
| `N7:0` | Integer file 7, word 0 (signed 16-bit) |
|
|
| `F8:0` | Float file 8, word 0 (32-bit IEEE-754) |
|
|
| `B3:0/0` | Bit file 3, word 0, bit 0 |
|
|
| `L9:0` | Long-integer file (SLC 5/05+, 32-bit) |
|
|
| `ST9:0` | String file (82-byte fixed-length) |
|
|
| `T4:0.ACC` / `C5:0.PRE` | Timer / counter sub-element |
|
|
| `I:0/0` / `O:1/2` / `S:1` | Input / output / status system files (no file number) |
|
|
|
|
`AbLegacyDataType` covers the corresponding PCCC types: `Bit`, `Int` (N), `Long`
|
|
(L), `Float` (F), `AnalogInt` (A), `String` (ST), and the `TimerElement` /
|
|
`CounterElement` / `ControlElement` sub-element families. The parser enforces
|
|
PCCC structural rules — bit-addressing only on 16/32-bit element files,
|
|
sub-elements only on T/C/R files, no file number on I/O/S — rejecting malformed
|
|
addresses before they reach libplctag.
|
|
|
|
## Configuration
|
|
|
|
`AbLegacyDriverOptions` (`Driver.AbLegacy.Contracts/AbLegacyDriverOptions.cs`)
|
|
binds from the driver's `DriverConfig` JSON:
|
|
|
|
- **`Devices`** — one `AbLegacyDeviceOptions` per PLC (`HostAddress`, `PlcFamily`, optional `DeviceName`).
|
|
- **`Tags`** — pre-declared `AbLegacyTagDefinition` list (`Name`, `DeviceHostAddress`, `Address`, `DataType`, `Writable`, `WriteIdempotent`).
|
|
- **`Probe`** — connectivity-probe `Enabled` / `Interval` / `Timeout` / `ProbeAddress`.
|
|
|
|
Full per-field descriptions live in the contracts assembly. The JSON skeleton is
|
|
reproduced in [docs/v2/driver-specs.md §4](../v2/driver-specs.md).
|
|
|
|
## Testing
|
|
|
|
- **Unit tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/` cover the driver, the PCCC address parser, and the host-address parser via fake tag runtimes.
|
|
- **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/` run against the AB Legacy Docker fixture. See [AbLegacy-Test-Fixture.md](AbLegacy-Test-Fixture.md) for the coverage map.
|
|
- **Manual client** — [Driver.AbLegacy.Cli.md](../Driver.AbLegacy.Cli.md).
|
|
|
|
## Operational Notes
|
|
|
|
- **Native heap is invisible to the GC.** As with AB CIP, `GetMemoryFootprint()` reports CLR allocations only; watch whole-process RSS and use `ReinitializeAsync` to recycle libplctag handles.
|
|
- **PCCC reconnect is more expensive than CIP** — legacy PLCs have no connection multiplexing, so the resilience pipeline should use longer backoff than for AB CIP (see [docs/v2/driver-specs.md §4](../v2/driver-specs.md)).
|
|
- **Single-element addressing today** — a PCCC file is inherently an array (an N7 file is up to 256 words), but the current tag surface addresses one element per tag; range-spanning tags must be enumerated element-by-element until multi-element addressing lands.
|