AB CIP PR 2 — Scaffolding: AbCipDriver skeleton + libplctag binding + parsers #109

Merged
dohertj2 merged 1 commits from abcip-pr2-scaffolding into v2 2026-04-19 16:00:29 -04:00
Owner

Summary

PR 2 of the AB CIP driver sequence. Stands up the Driver.AbCip project as a compiling assembly with everything needed before wire calls land in PR 3.

Core types shipped:

  • AbCipDriverIDriver only (read/write/subscribe ship in PRs 3–8)
  • AbCipDriverOptions + AbCipDeviceOptions + AbCipTagDefinition
  • AbCipHostAddress — parser for ab://gateway[:port]/cip-path canonical form
  • AbCipTagPath — full Logix symbolic tag surface (scope / member / array / bit-in-DINT)
  • AbCipDataType + ToDriverDataType adapter
  • AbCipStatusMapper — CIP general-status + libplctag status-code translation
  • PlcTagHandleSafeHandle around native tag ID (lifetime guaranteed via finalizer)
  • AbCipPlcFamilyProfile — ControlLogix / CompactLogix / Micro800 / GuardLogix defaults

libplctag: v1.5.2 (stable — 1.6.x is alpha). NuGet wrapper pulls libplctag.NativeImport transitively.

Project shape: only Core.Abstractions reference (matches Modbus / OpcUaClient / S7), no dependency on Core / Configuration / Polly.

Test plan

  • 76 new tests across 4 test classes, all passing
    • AbCipHostAddressTests — 20+ cases (valid / invalid / round-trip)
    • AbCipTagPathTests — 18 cases (scope / member / array / bit / rejected shapes)
    • AbCipStatusMapperTests — 20 cases (CIP + libplctag codes)
    • AbCipDriverTests — IDriver lifecycle + multi-device + profile lookup + SafeHandle
  • Full solution builds 0 errors
  • Modbus + other existing tests untouched

Unblocks

  • PR 3 — IReadable against ab_server (first wire-level integration)
  • PR 4 — IWritable (same wire layer)
  • PR 5 — ITagDiscovery (@tags + Template Object)

Merges to v2.

## Summary PR 2 of the AB CIP driver sequence. Stands up the `Driver.AbCip` project as a compiling assembly with everything needed before wire calls land in PR 3. **Core types shipped:** - `AbCipDriver` — `IDriver` only (read/write/subscribe ship in PRs 3–8) - `AbCipDriverOptions` + `AbCipDeviceOptions` + `AbCipTagDefinition` - `AbCipHostAddress` — parser for `ab://gateway[:port]/cip-path` canonical form - `AbCipTagPath` — full Logix symbolic tag surface (scope / member / array / bit-in-DINT) - `AbCipDataType` + `ToDriverDataType` adapter - `AbCipStatusMapper` — CIP general-status + libplctag status-code translation - `PlcTagHandle` — `SafeHandle` around native tag ID (lifetime guaranteed via finalizer) - `AbCipPlcFamilyProfile` — ControlLogix / CompactLogix / Micro800 / GuardLogix defaults **libplctag**: v1.5.2 (stable — 1.6.x is alpha). NuGet wrapper pulls `libplctag.NativeImport` transitively. **Project shape**: only `Core.Abstractions` reference (matches Modbus / OpcUaClient / S7), no dependency on `Core` / `Configuration` / `Polly`. ## Test plan - [x] 76 new tests across 4 test classes, all passing - `AbCipHostAddressTests` — 20+ cases (valid / invalid / round-trip) - `AbCipTagPathTests` — 18 cases (scope / member / array / bit / rejected shapes) - `AbCipStatusMapperTests` — 20 cases (CIP + libplctag codes) - `AbCipDriverTests` — IDriver lifecycle + multi-device + profile lookup + SafeHandle - [x] Full solution builds 0 errors - [x] Modbus + other existing tests untouched ## Unblocks - PR 3 — `IReadable` against `ab_server` (first wire-level integration) - PR 4 — `IWritable` (same wire layer) - PR 5 — `ITagDiscovery` (@tags + Template Object) Merges to `v2`.
dohertj2 added 1 commit 2026-04-19 16:00:19 -04:00
AB CIP PR 2 — scaffolding + Core (AbCipDriver skeleton + libplctag binding + host / tag-path / data-type / status-code parsers + per-family profiles + SafeHandle wrapper + test harness). Ships everything needed to stand up the driver project as a compiling assembly with no wire calls yet — PR 3 adds IReadable against ab_server which is the first PR that actually touches the native library. Project reference shape matches Modbus / OpcUaClient / S7 (only Core.Abstractions, no Core / Configuration / Polly) so the driver stays lean and doesn't drag EF Core into every deployment that wants AB support. libplctag 1.5.2 pinned (1.6.x only exists as alpha — stable 1.5 series covers ControlLogix / CompactLogix / Micro800 / SLC500 / PLC-5 / MicroLogix which matches plan decision #11 family coverage). libplctag.NativeImport arrives transitively. AbCipHostAddress parses ab://gateway[:port]/cip-path canonical strings end-to-end: handles hostname or IP gateway, optional explicit port (default 44818 EtherNet-IP reserved), CIP path including bridged routes (1,2,2,10.0.0.10,1,0), empty path for Micro800 / MicroLogix without backplane routing, case-insensitive scheme, default-port stripping in canonical form for round-trip stability. Opaque string survives straight into libplctag's gateway / path attributes so no translation layer at wire time. AbCipTagPath handles the full Logix symbolic tag surface — controller-scope (Motor1_Speed), program-scope (Program:MainProgram.StepIndex), structured member access (Motor1.Speed.Setpoint), multi-dim array subscripts (Matrix[1,2,3]), bit-within-DINT via .N syntax (Flags.3, Motor.Status.12) with valid range 0-31 per Logix 5000 General Instructions Reference. Structural capture so PR 6 UDT work can walk the path against a cached template without reparsing. Rejects malformed shapes (empty scopes, ident starting with digit, spaces, empty/negative/non-numeric subscripts, unbalanced brackets, leading / trailing dots). Round-trips via ToLibplctagName producing the exact string libplctag's name attribute expects. AbCipDataType mirrors ModbusDataType shape — atomic Bool / SInt / Int / DInt / LInt / USInt / UInt / UDInt / ULInt / Real / LReal / String / Dt plus a Structure marker for UDT-typed tags (resolved via CIP Template Object at discovery time in PR 5/6). ToDriverDataType adapter follows the Modbus widening convention for unsigned + 64-bit until DriverDataType picks those up. AbCipStatusMapper covers the CIP general-status values an AB PLC actually returns during normal operation (0x00/0x04/0x05/0x06/0x08/0x0A/0x0B/0x0E/0x10/0x13/0x16) + libplctag PLCTAG_STATUS_* codes (0, >0 pending, negative error families). Mirrors ModbusDriver.MapModbusExceptionToStatus so Admin UI status displays stay uniform across drivers. PlcTagHandle is a SafeHandle around the int32 native tag ID with plc_tag_destroy slot wired as a no-op for PR 2 (P/Invoke DllImport arrives with PR 3 when the wire calls land). Lifetime guaranteed by the SafeHandle finalizer — every leaked handle gets cleaned up even when the owner is GC'd without explicit Dispose. IsInvalid when native ID <= 0 so destroying a negative (error) handle never happens. Critical because driver-specs.md §3 flags libplctag native heap as invisible to GetMemoryFootprint — leaked handles directly feed the Tier-B recycle trigger. AbCipDriverOptions captures the multi-device shape — one driver instance can talk to N PLCs via Devices[] (each with HostAddress + PlcFamily + optional DeviceName); Tags[] references devices by HostAddress as the cross-key; AbCipProbeOptions + driver-wide Timeout. AbCipDriver implements IDriver only — InitializeAsync parses every device's HostAddress and selects its PlcFamilyProfile (fails fast on malformed strings via InvalidOperationException → Faulted health), per-device state cached in a DeviceState record with parsed address + profile + empty TagHandles dict for later PRs. ReinitializeAsync is the Tier-B escape hatch — shuts down every device, disposes every PlcTagHandle via SafeHandle lifetime, reinitializes from options. ShutdownAsync clears the device dict and flips health to Unknown. PlcFamilies/AbCipPlcFamilyProfile gives four baseline profiles — ControlLogix (4002 ConnectionSize, path 1,0, Large Forward Open + request packing + connected messaging, FW20+ baseline), CompactLogix (narrower 504 default for 5069-L3x safety), Micro800 (488 cap, empty path, unconnected-only, no request packing), GuardLogix (shares ControlLogix wire protocol — safety partition is tag-level, surfaced as ViewOnly in PR 12). Tests — 76 new cases across 4 test classes — AbCipHostAddressTests (10 valid shapes, 10 invalid shapes, ToString canonicalization, round-trip stability), AbCipTagPathTests (18 cases including multi-scope / multi-member / multi-subscript / bit-in-DINT / rejected shapes / underscore idents / round-trip), AbCipStatusMapperTests (12 CIP + 8 libplctag codes), AbCipDriverTests (IDriver lifecycle + multi-device init + malformed-address fault + per-family profile lookup + PlcTagHandle invalid/dispose idempotency + AbCipDataType mapping). Full solution builds 0 errors; 254 warnings are pre-existing xUnit1051 CancellationToken hints outside this PR. Solution file updated to include both new projects. Unblocks PR 3 (IReadable against ab_server) which is the first PR to exercise the native library end-to-end. 3e0452e8a4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit 59b59b8ccd into v2 2026-04-19 16:00:29 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#109