Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.IntegrationTests/Docker/README.md
T
Joseph Doherty f8f8eb9fb8 feat(mtconnect): serve DATA_SET/TABLE as compact JSON strings; park EVENT enumerations (#650)
Parser captures Entry/Cell content; the observation index merges /sample deltas
into the /current baseline (resetTriggered replaces, removed deletes, UNAVAILABLE
drops). Fixture gains a TABLE item and periodic DATA_SET/TABLE churn.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:15:32 -04:00

5.8 KiB

MTConnect integration-test fixture — the official C++ Agent + an SHDR data source

The MTConnect C++ Agent (mtconnect/agent, pinned) serving a canned device model, fed live data by a standard-library SHDR adapter. No image build step: both services run stock images with this folder's files bind-mounted.

The published image is mtconnect/agent, not mtconnect/cppagent. cppagent is the name of the source project on GitHub; there is no Docker Hub repository under that name.

File Purpose
docker-compose.yml Two services: agent (published on :5000) and adapter (internal, :7878)
agent.cfg Agent configuration, bind-mounted at /mtconnect/config/agent.cfg (the image's CMD path)
Devices.xml The canned device model the Agent serves from /probe
adapter.py SHDR feeder — the data source. Pure stdlib, runs on a stock python:*-alpine

Why there is an adapter service

The mtconnect/agent image ships only the agent binary plus its schemas and styles — there is no bundled simulator. An Agent with no adapter answers /probe correctly and then reports every observation UNAVAILABLE forever, which cannot prove that reads return real values or that the /sample long poll delivers anything. adapter.py is what makes the fixture live.

The Agent dials out to the adapter (see the Adapters block in agent.cfg); the adapter is not published to the host.

Run

From the shared Docker host (stack dir /opt/otopcua-mtconnect):

docker compose up -d --wait
docker compose logs -f agent
docker compose down

From a dev box via the helper (see CLAUDE.md "Docker Workflow"):

lmxopcua-fix sync mtconnect     # push this folder to /opt/otopcua-mtconnect/
lmxopcua-fix up   mtconnect     # single-service-shaped stack, no profile argument
lmxopcua-fix logs mtconnect
lmxopcua-fix down mtconnect

Running it on a Mac

macOS squats port 5000 — AirPlay Receiver (ControlCenter) binds *:5000 and wins the race, so docker port reports a healthy publish while every request answers 403 Forbidden with Server: AirTunes. Use the host-port override:

MTCONNECT_AGENT_HOST_PORT=5555 docker compose up -d --wait
MTCONNECT_AGENT_ENDPOINT=http://127.0.0.1:5555 dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.IntegrationTests

Endpoint

  • Default: http://10.100.0.35:5000 (the shared Docker host; 5000 is the Agent's own default port).
  • Override with MTCONNECT_AGENT_ENDPOINT to point at a real Agent on a machine tool.
  • MTCONNECT_AGENT_HOST_PORT changes only the published host port of the fixture container.

MTConnectAgentFixture issues one GET {endpoint}/probe at collection init and records a SkipReason when it fails, so the suite skips cleanly on a box with no fixture running.

The seeded device model

Every named DataItem deliberately has name != id — the inverse of the driver's hand-authored unit fixtures, and the only arrangement under which confusing the browse name with the observation correlation key is visible.

DataItem id name Category Type Repr. Inferred type
fixture_avail Favail EVENT AVAILABILITY String
fixture_x_pos Xact SAMPLE POSITION (ACTUAL, MILLIMETER) Float64 — moves
fixture_x_load Xload SAMPLE LOAD (PERCENT) Float64 — moves
fixture_x_travel Xtravel CONDITION POSITION String + IsAlarm
fixture_c_speed Cspeed SAMPLE ROTARY_VELOCITY (REVOLUTION/MINUTE) Float64 — moves
fixture_c_temp_series Ctemps SAMPLE TEMPERATURE (CELSIUS) TIME_SERIES Float64 array, ArrayDim null
fixture_mode Cmode EVENT CONTROLLER_MODE String
fixture_execution Pexec EVENT EXECUTION String
fixture_partcount Pcount EVENT PART_COUNT Int64 — the regression case
fixture_linenumber Pline EVENT LINE_NUMBER Int64
fixture_program Pprogram EVENT PROGRAM String
fixture_block Pblock EVENT BLOCK String — never fed ⇒ UNAVAILABLE
fixture_varset Pvars EVENT VARIABLE DATA_SET String — JSON object (#650); toolNumber/offsetX/offsetZ stable, cycle moves every ~5 s
fixture_worktable Ptab EVENT WORK_OFFSET TABLE String — JSON object of objects (#650); G54/G55 stable, G56 row moves every ~5 s
fixture_logic Plogic CONDITION LOGIC_PROGRAM String + IsAlarm
fixture_asset_changed / fixture_asset_removed — EVENT ASSET_CHANGED / ASSET_REMOVED String

The Agent additionally injects its own <Agent> self-model device (connection status, observation update rate, adapter URI). That is normal for every MTConnect 2.x Agent and the test suite excludes it from value-plane assertions — its update-rate samples tick whether or not any adapter is attached, so including them would let "the stream delivered a changed value" pass against an Agent with no data source at all.

Two things a real Agent taught us (both cost a fixture restart to find)

  1. agent.cfg must be pure ASCII. A single non-ASCII byte — even inside a comment — makes the config parser reject the whole file with a bare Failed / Stopped at line: N and exit.
  2. sampleCount is not a DataItem attribute. It belongs to a TIME_SERIES observation. An Agent that meets it on a declaration logs The following keys were present and not expected: sampleCount followed by DataItems: Invalid element 'DataItem' and drops the entire data item from the device model. MTConnectDataTypeInference therefore only ever sees sampleCount = null from a real Agent, so a live TIME_SERIES tag is always a variable-length array.