6.7 KiB
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. For the manual test client, see Driver.AbLegacy.Cli.md. For the integration fixture coverage map, see 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— oneAbLegacyDeviceOptionsper PLC (HostAddress,PlcFamily, optionalDeviceName).Tags— pre-declaredAbLegacyTagDefinitionlist (Name,DeviceHostAddress,Address,DataType,Writable,WriteIdempotent).Probe— connectivity-probeEnabled/Interval/Timeout/ProbeAddress.
Full per-field descriptions live in the contracts assembly. The JSON skeleton is reproduced in docs/v2/driver-specs.md §4.
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 for the coverage map. - Manual client — 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 useReinitializeAsyncto 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).
- 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.