AB CIP PR 3 — IReadable against libplctag (swappable factory + fake + skip-gated smoke) #110

Merged
dohertj2 merged 1 commits from abcip-pr3-ireadable into v2 2026-04-19 16:41:03 -04:00
Owner

Summary

PR 3 of the AB CIP sequence. First PR that exercises the native libplctag library.

Abstraction layer:

  • IAbCipTagRuntime + IAbCipTagFactory — thin wire-layer interface (matches Modbus transport-factory pattern)
  • LibplctagTagRuntime — default implementation wrapping libplctag.Tag
  • Covers Bool (standalone + BOOL-in-DINT via .N), SInt/USInt, Int/UInt, DInt/UDInt, LInt/ULInt, Real, LReal, String, Dt

AbCipDriver.ReadAsync:

  • Per-tag ordering preserved; unknown refs → BadNodeIdUnknown
  • Lazy init per tag on first touch; cached for device lifetime
  • libplctag status → OPC UA via AbCipStatusMapper
  • OperationCanceledException propagates
  • Health surface tracks success / degradation
  • Init failure disposes the half-created runtime (no native leak)

Test plan

  • 12 new unit tests (AbCipDriverReadTests) with FakeAbCipTag / FakeAbCipTagFactory
    • unknown ref, unknown device, successful read, lazy-init + reuse
    • libplctag status mapping, exception → BadCommunicationError
    • batched reads preserve order, health transitions
    • AbCipTagCreateParams composition, cancellation propagation
    • shutdown disposes runtimes, init-failure disposes aborted runtime
  • 88/88 AbCip unit tests pass (76 from PR 2 + 12 new)
  • Integration test project created (Driver.AbCip.IntegrationTests)
    • AbServerFixture + AbServerFact (skip when binary missing)
    • one DInt read smoke test — skips cleanly on boxes without ab_server
  • Full solution builds 0 errors (28 projects)

Follow-ups (not in this PR)

  • ab_server CI fixture — download prebuilt Windows binary as GitHub release asset (PR 9 territory)
  • Per-family JSON profiles — ship with PRs 9–12
  • UDT fidelity hand-rolled CIP stub — ship with PR 6

Merges to v2.

## Summary PR 3 of the AB CIP sequence. First PR that exercises the native libplctag library. **Abstraction layer:** - `IAbCipTagRuntime` + `IAbCipTagFactory` — thin wire-layer interface (matches Modbus transport-factory pattern) - `LibplctagTagRuntime` — default implementation wrapping `libplctag.Tag` - Covers Bool (standalone + BOOL-in-DINT via `.N`), SInt/USInt, Int/UInt, DInt/UDInt, LInt/ULInt, Real, LReal, String, Dt **`AbCipDriver.ReadAsync`:** - Per-tag ordering preserved; unknown refs → `BadNodeIdUnknown` - Lazy init per tag on first touch; cached for device lifetime - libplctag status → OPC UA via `AbCipStatusMapper` - `OperationCanceledException` propagates - Health surface tracks success / degradation - Init failure disposes the half-created runtime (no native leak) ## Test plan - [x] 12 new unit tests (`AbCipDriverReadTests`) with `FakeAbCipTag` / `FakeAbCipTagFactory` - unknown ref, unknown device, successful read, lazy-init + reuse - libplctag status mapping, exception → `BadCommunicationError` - batched reads preserve order, health transitions - `AbCipTagCreateParams` composition, cancellation propagation - shutdown disposes runtimes, init-failure disposes aborted runtime - [x] **88/88 AbCip unit tests pass** (76 from PR 2 + 12 new) - [x] Integration test project created (`Driver.AbCip.IntegrationTests`) - `AbServerFixture` + `AbServerFact` (skip when binary missing) - one DInt read smoke test — skips cleanly on boxes without `ab_server` - [x] Full solution builds 0 errors (28 projects) ## Follow-ups (not in this PR) - `ab_server` CI fixture — download prebuilt Windows binary as GitHub release asset (PR 9 territory) - Per-family JSON profiles — ship with PRs 9–12 - UDT fidelity hand-rolled CIP stub — ship with PR 6 Merges to `v2`.
dohertj2 added 1 commit 2026-04-19 16:40:53 -04:00
AB CIP PR 3 — IReadable implementation against libplctag. Introduces IAbCipTagRuntime + IAbCipTagFactory abstraction matching the Modbus transport-factory pattern (ctor optional arg, default production impl injected) so the driver's read/status-mapping logic is unit-testable without a live PLC or the native libplctag binary. LibplctagTagRuntime is the default wire-backed implementation — wraps libplctag.Tag + translates our AbCipDataType enum into GetInt8/GetUInt8/GetInt16/GetUInt16/GetInt32/GetUInt32/GetInt64/GetUInt64/GetFloat32/GetFloat64/GetString/GetBit calls covering Bool (standalone + BOOL-in-DINT via .N bit selector), SInt/USInt, Int/UInt, DInt/UDInt, LInt/ULInt, Real, LReal, String, Dt (epoch DINT), with Structure deferred to PR 6. MapPlcType bridges our libplctag attribute strings (controllogix, compactlogix, micro800) to libplctag.PlcType enum; CompactLogix rolls under ControlLogix per libplctag's family grouping which matches the wire protocol reality. AbCipDriver now implements IReadable — ReadAsync iterates fullReferences preserving order, looks up each tag definition + its device, lazily materialises the tag runtime via EnsureTagRuntimeAsync on first touch (cached thereafter for the lifetime of the device), catches OperationCanceledException to honor cancellation, maps libplctag non-zero status via AbCipStatusMapper.MapLibplctagStatus, catches any other exception as BadCommunicationError. Health surface moves to Healthy on success + Degraded with the last error message on failure. Initialize-failure path disposes the half-created runtime before rethrowing so no native handles leak. DeviceState gains a Runtimes dict alongside the existing TagHandles collection; DisposeHandles walks both so ShutdownAsync + ReinitializeAsync cleanly destroy every native tag. 12 new unit tests in AbCipDriverReadTests using FakeAbCipTag / FakeAbCipTagFactory (test fake under tests/...AbCip.Tests/FakeAbCipTag.cs) covering unknown reference → BadNodeIdUnknown, unknown device → BadNodeIdUnknown, successful DInt read with correct Good status + captured value, lazy-init on first read with reuse across subsequent reads, non-zero libplctag status mapping via AbCipStatusMapper, exception during read surfacing as BadCommunicationError with health Degraded, batched reads preserving order + per-tag status, health Healthy after success, TagCreateParams composition from device + profile (gateway / port / CIP path / libplctag attribute / tag name wiring), cancellation propagation via OperationCanceledException, ShutdownAsync disposing every runtime, Initialize-failure disposing the aborted runtime. Total AbCip unit tests now 88/88 passing. Integration test project scaffolding — tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests with AbServerFixture (IAsyncLifetime that starts ab_server when the binary is on PATH, otherwise marks IsAvailable=false), AbServerFact attribute (Fact-equivalent that skips when ab_server is missing), one smoke test exercising DInt read end-to-end. Project runs cleanly — the single smoke test skips on boxes without ab_server (0 failed, 0 passed, 1 skipped) + runs on boxes with it. Follow-up work captured in comments — ab_server CI fixture (download prebuilt Windows x64 binary as GitHub release asset) + per-family JSON profiles + hand-rolled CIP stub for UDT fidelity ship in the PR 6/9-12 window. Solution file updated. Full solution builds 0 errors across all 28 projects. Modbus + other existing tests untouched. cc35c77d64
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit be2379107d into v2 2026-04-19 16:41:03 -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#110