Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml
T
Joseph Doherty c2c3c52274
v2-ci / build (push) Successful in 4m17s
v2-ci / unit-tests (push) Failing after 14m43s
fix(abcip): unblock the controller-browse gate, and fix the decoder it exposed (#468)
The AbCip controller browse could never be verified end to end: libplctag's
ab_server answers CIP service 0x55 (Get Instance Attribute List) on the Symbol
Object class with "unsupported", so the `@tags` walk had no backend. #468's own
definition of done offered a sim that serves `@tags` as an alternative to real
hardware; this is that.

The fixture now applies a vendored patch teaching ab_server to serve the Symbol
Object listing, with partial-transfer paging. Correctness is checked against
libplctag's own `list_tags_logix` rather than against our decoder — our decoder
is the thing under test, so its agreement would prove nothing. The clone is also
pinned to a concrete tag: the Dockerfile called `--branch release` "pinned" while
tracking a branch that moves.

That immediately exposed a real defect. CipSymbolObjectDecoder advanced to an
even byte boundary after each symbol name, but entries are packed with no
padding — libplctag walks them by sizeof(entry) + string_len, and a captured
reply confirms it. Any odd-length name therefore desynchronised every entry
behind it, and odd-length names are ordinary. In the live browse below,
`Motor_Run` is 9 bytes and first on the wire, so the old decoder would have
shown that one tag and silently dropped the other eight.

Every existing multi-entry test passed throughout, because the test builder
padded exactly as the decoder did: the two agreed with each other and neither
agreed with a controller. The builder no longer pads, and the layout is pinned
by a captured reply (CipSymbolObjectWireCaptureTests) instead of by a second
statement of the assumption. Restoring the alignment step reddens exactly the
three new tests and none of the 29 others.

Live gate on docker-dev, all three of #468's criteria:
  - populated tree — all nine seeded tags render as leaves, in wire order
  - refresh re-captures
  - leaf commit — Pump_1_Speed + TestDINT saved with their CIP tag paths

The gate also surfaced two things it was not asked about: browse-commit drops
the discovered data type, so a browsed REAL commits as a DINT and the driver
decodes it as one (filed as #536), and Test Connect fails against this fixture
regardless of the device because it probes `@raw_cpu_type` (documented).

TwinCAT and FOCAS still have no browsable backend; #468's AbCip half is closed,
theirs is not.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
2026-08-01 12:25:15 -04:00

107 lines
3.1 KiB
YAML

# AB CIP integration-test fixture — ab_server (libplctag).
#
# One service per family. All bind :44818 on the host; only one runs at a
# time. Commands mirror the CLI args AbServerProfile.cs constructs for the
# native-binary path.
#
# Usage:
# docker compose --profile controllogix up
# docker compose --profile compactlogix up
# docker compose --profile micro800 up
# docker compose --profile guardlogix up
services:
controllogix:
profiles: ["controllogix"]
build:
context: .
dockerfile: Dockerfile
image: otopcua-ab-server:libplctag-release
container_name: otopcua-ab-server-controllogix
restart: "no"
ports:
- "44818:44818"
# The last three are here for the controller-browse gate (Gitea #468) and
# are additive — the read/write suites key off the Test* tags above.
# Odd-length names are deliberate: Symbol Object entries are packed with no
# padding, so a name with an odd byte count puts every following entry on an
# odd offset. A decoder that assumes even alignment reads all six correctly
# and then loses everything after the first odd name.
command: [
"ab_server",
"--plc=ControlLogix",
"--path=1,0",
"--port=44818",
"--tag=TestDINT:DINT[1]",
"--tag=TestREAL:REAL[1]",
"--tag=TestBOOL:BOOL[1]",
"--tag=TestSINT:SINT[1]",
"--tag=TestString:STRING[1]",
"--tag=TestArray:DINT[16]",
"--tag=Pump_1_Speed:REAL[1]",
"--tag=Line1_Count:DINT[1]",
"--tag=Motor_Run:BOOL[1]"
]
compactlogix:
profiles: ["compactlogix"]
image: otopcua-ab-server:libplctag-release
build:
context: .
dockerfile: Dockerfile
container_name: otopcua-ab-server-compactlogix
restart: "no"
ports:
- "44818:44818"
# ab_server doesn't distinguish CompactLogix from ControlLogix — no
# dedicated --plc mode. Driver-side ConnectionSize cap is enforced
# separately (see AbServerProfile.CompactLogix Notes).
command: [
"ab_server",
"--plc=ControlLogix",
"--path=1,0",
"--port=44818",
"--tag=TestDINT:DINT[1]",
"--tag=TestREAL:REAL[1]",
"--tag=TestBOOL:BOOL[1]"
]
micro800:
profiles: ["micro800"]
image: otopcua-ab-server:libplctag-release
build:
context: .
dockerfile: Dockerfile
container_name: otopcua-ab-server-micro800
restart: "no"
ports:
- "44818:44818"
# ab_server does have a Micro800 plc mode (unconnected-only, empty path).
command: [
"ab_server",
"--plc=Micro800",
"--port=44818",
"--tag=TestDINT:DINT[1]",
"--tag=TestREAL:REAL[1]"
]
guardlogix:
profiles: ["guardlogix"]
image: otopcua-ab-server:libplctag-release
build:
context: .
dockerfile: Dockerfile
container_name: otopcua-ab-server-guardlogix
restart: "no"
ports:
- "44818:44818"
# ab_server has no safety subsystem — _S suffix triggers driver-side
# classification only.
command: [
"ab_server",
"--plc=ControlLogix",
"--path=1,0",
"--port=44818",
"--tag=TestDINT:DINT[1]",
"--tag=SafetyDINT_S:DINT[1]"
]