f8f8eb9fb8
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
107 lines
5.8 KiB
Markdown
107 lines
5.8 KiB
Markdown
# 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`](docker-compose.yml) | Two services: `agent` (published on :5000) and `adapter` (internal, :7878) |
|
|
| [`agent.cfg`](agent.cfg) | Agent configuration, bind-mounted at `/mtconnect/config/agent.cfg` (the image's CMD path) |
|
|
| [`Devices.xml`](Devices.xml) | The canned device model the Agent serves from `/probe` |
|
|
| [`adapter.py`](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`):
|
|
|
|
```bash
|
|
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"):
|
|
|
|
```powershell
|
|
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:
|
|
|
|
```bash
|
|
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.
|