Replaced the "ab_server PCCC upstream-broken" skip gate with the actual
root cause: libplctag's ab_server rejects empty CIP routing paths at the
unconnected-send layer before the PCCC dispatcher runs. Real SLC/
MicroLogix/PLC-5 hardware accepts empty paths (no backplane); ab_server
does not. With `/1,0` in place, N (Int16), F (Float32), and L (Int32)
file reads + writes round-trip cleanly across all three compose profiles.
## Fixture changes
- `AbLegacyServerFixture.cs`:
- Drop `AB_LEGACY_TRUST_WIRE` env var + the reachable-but-untrusted
skip branch. Fixture now only skips on TCP unreachability.
- Add `AB_LEGACY_CIP_PATH` env var (default `1,0`) + expose `CipPath`
property. Set `AB_LEGACY_CIP_PATH=` (empty) against real hardware.
- Shorter skip messages on the `[AbLegacyFact]` / `[AbLegacyTheory]`
attributes — one reason: endpoint not reachable.
- `AbLegacyReadSmokeTests.cs`:
- Device URI built from `sim.CipPath` instead of hardcoded empty path.
- New `AB_LEGACY_COMPOSE_PROFILE` env var filters the parametric
theory to the running container's family. Only one container binds
`:44818` at a time, so cross-family params would otherwise fail.
- `Slc500_write_then_read_round_trip` skips cleanly when the running
profile isn't `slc500`.
## E2E + seed + docs
- `scripts/e2e/test-ablegacy.ps1` — drop the `AB_LEGACY_TRUST_WIRE`
skip gate; synopsis calls out the `/1,0` vs empty cip-path split
between the Docker fixture and real hardware.
- `scripts/e2e/e2e-config.sample.json` — sample gateway flipped from
the hardware placeholder (`192.168.1.10`) to the Docker fixture
(`127.0.0.1/1,0`); comment rewritten.
- `scripts/e2e/README.md` — AB Legacy expected-matrix row goes from
SKIP to PASS.
- `scripts/smoke/seed-ablegacy-smoke.sql` — default HostAddress points
at the Docker fixture + header / footer text reflect the new state.
- `tests/.../Docker/README.md` — "Known limitations" section rewritten
to describe the cip-path gate (not a dispatcher gap); env-var table
picks up `AB_LEGACY_CIP_PATH` + `AB_LEGACY_COMPOSE_PROFILE`.
- `docs/drivers/AbLegacy-Test-Fixture.md` + `docs/drivers/README.md`
+ `docs/DriverClis.md` — flip status from blocked to functional;
residual bit-file-write gap (B3:0/5 → 0x803D0000) documented.
## Residual gap
Bit-file writes (`B3:0/5` style) surface `0x803D0000` against
`ab_server --plc=SLC500`; bit reads work. Non-blocking for smoke
coverage — N/F/L round-trip is enough. Real hardware / RSEmulate 500
for bit-write fidelity. Documented in `Docker/README.md` §"Known
limitations" + the `AbLegacy-Test-Fixture.md` follow-ups list.
## Verified
- Full-solution build: 0 errors, 334 pre-existing warnings.
- Integration suite passes per-profile with
`AB_LEGACY_COMPOSE_PROFILE=<slc500|micrologix|plc5>` + matching
compose container up.
- All four non-hardware e2e scripts (Modbus / AB CIP / AB Legacy / S7)
now 5/5 against the respective docker-compose fixtures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.6 KiB
Driver test-client CLIs
Five shell-level ad-hoc validation tools, one per native-protocol driver family.
Each mirrors the v1 otopcua-cli shape (probe / read / write / subscribe) against
the same driver the OtOpcUa server uses — so "does the CLI see it?" and
"does the server see it?" are the same question.
| CLI | Protocol | Docs |
|---|---|---|
otopcua-modbus-cli |
Modbus-TCP | Driver.Modbus.Cli.md |
otopcua-abcip-cli |
CIP / EtherNet-IP (Logix symbolic) | Driver.AbCip.Cli.md |
otopcua-ablegacy-cli |
PCCC (SLC / MicroLogix / PLC-5) | Driver.AbLegacy.Cli.md |
otopcua-s7-cli |
S7comm / ISO-on-TCP | Driver.S7.Cli.md |
otopcua-twincat-cli |
Beckhoff ADS | Driver.TwinCAT.Cli.md |
The OPC UA client CLI lives separately and predates this suite —
see Client.CLI.md for otopcua-cli.
Shared commands
Every driver CLI exposes the same four verbs:
probe— open a session, read one sentinel tag, print driver health. Fastest "is the device talking?" check.read— synthesise a one-tag driver config from--type/--address(or--tag/--symbol) flags, read once, print the snapshot. No extra config file needed.write— same shape plus--value. Values parse per--typeusing invariant culture. Booleans accepttrue/false/1/0/yes/no/on/off. Writes are non-idempotent by default — a timeout after the device already applied the write will not auto-retry (plan decisions #44, #45).subscribe— long-running data-change stream until Ctrl+C. Uses native push where available (TwinCAT ADS notifications) and falls back to polling (PollGroupEngine) where the protocol has no push (Modbus, AB, S7).
Shared infrastructure
All five CLIs depend on src/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common/:
DriverCommandBase—--verbose+ Serilog configuration + the abstractTimeoutsurface every protocol-specific base overrides with its own default.SnapshotFormatter— consistent output across every CLI: tag / value / status / source-time / server-time for single reads, a 4-column table for batches,Write <tag>: 0x... (Name)for writes, and one line per change event for subscriptions. OPC UA status codes render as0xXXXXXXXX (Name)with a shortlist forGood/Bad*/Uncertain; unknown codes fall back to hex.
Writing a sixth CLI (hypothetical Galaxy / FOCAS) costs roughly 150 lines:
a {Family}CommandBase + four thin command classes that hand their flag
values to the already-shipped driver.
Typical cross-CLI workflows
- Commissioning a new device —
probefirst, thenreada known-good tag. If the device is up + talking the protocol, both pass; if the tag is wrong you'll see the read fail with a protocol-specific error. - Reproducing a production bug —
subscribeto the tag the bug report names, then have the operator run the scenario. You get an HH:mm:ss.fff timeline of exactly when each value changed. - Validating a recipe write —
write+readback. If the server's write path would have done anything different, the CLI would have too. - Byte-order / word-swap debugging —
readwith one--byte-order, then the other. The plausible result identifies the correct setting for that device family. (Modbus, S7.)
Known gaps
- AB Legacy cip-path quirk — libplctag's ab_server requires a
non-empty CIP routing path before forwarding to the PCCC dispatcher.
Pass
--gateway "ab://127.0.0.1:44818/1,0"against the Docker fixture; real SLC / MicroLogix / PLC-5 hardware accepts an empty path (ab://host:44818/). Bit-file writes (B3:0/5) still surface0x803D0000against ab_server — route operator-critical bit writes to real hardware until upstream fixes this. - S7 PUT/GET communication must be enabled in TIA Portal for any S7-1200/1500. See Driver.S7.Cli.md.
- TwinCAT AMS router must be reachable (local XAR, standalone Router NuGet, or authorised remote route). See Driver.TwinCAT.Cli.md.
- Structure / UDT writes are refused by the AB CIP + TwinCAT CLIs — whole-UDT writes need a declared member layout that belongs in a real driver config, not a one-shot flag.
Tracking
Tasks #249 / #250 / #251 shipped the suite. 122 unit tests cumulative
(16 shared-lib + 106 across the five CLIs) — run
dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests +
tests/ZB.MOM.WW.OtOpcUa.Driver.*.Cli.Tests to re-verify.