cf9ce91e51
The first and only thing in the MTConnect workstream that exercises the driver
against a real Agent; everything else runs on canned XML.
Fixture (Docker/): two services, both stock images with this folder bind-mounted.
- `agent` mtconnect/agent:2.7.0.12 on :5000. NOTE the repo name: the source
project is mtconnect/cppagent but the PUBLISHED image is
mtconnect/agent; mtconnect/cppagent does not exist on Docker Hub.
- `adapter` a stdlib SHDR feeder. Required, not optional: the agent image ships
only the binary + schemas/styles, so a lone Agent answers /probe and
then reports every observation UNAVAILABLE forever, which can prove
nothing about reads or streaming.
Devices.xml seeds one DataItem per inference branch (SAMPLE+units, TIME_SERIES,
PART_COUNT/LINE_NUMBER, controlled vocab, DATA_SET, CONDITION), gives every named
item `name != id` (the inverse of the hand-authored unit fixtures), and leaves one
EVENT permanently unfed so UNAVAILABLE -> BadNoCommunication has a live subject.
Suite (12 tests) asserts STRUCTURALLY against the Agent's own /probe response, never
by literal id, so it survives an edit to Devices.xml and can be pointed at a real
machine tool via MTCONNECT_AGENT_ENDPOINT. It skips cleanly (12/12) when no Agent
answers, and each test carries a hard [Fact(Timeout)] so a half-up fixture cannot
wedge a build.
Three findings from bringing the fixture up, each now pinned in a comment:
- agent.cfg must be pure ASCII; one non-ASCII byte in a COMMENT makes the config
parser reject the whole file with a bare "Failed / Stopped at line: N" and exit.
- `sampleCount` is an attribute of a TIME_SERIES observation, not of a DataItem
declaration. A real Agent meeting one on a declaration DROPS THE ENTIRE DATA ITEM
from the device model, so the inference only ever sees null and a live TIME_SERIES
tag is always variable-length. Asserted, not ignored.
- The Agent's own <Agent> self-model publishes update-rate SAMPLEs that tick whether
or not any adapter is attached. Excluding them is load-bearing: with them included
the "live stream delivered a changed value" test passed with the fixture's data
source deliberately stopped.
MTConnectError-under-HTTP-200 is NOT covered live and cannot be: cppagent 2.7 answers
an unknown device 404 and an out-of-range sequence 400, each with a well-formed error
body. That shape stays canned-only, and the suite says so.
113 lines
7.1 KiB
XML
113 lines
7.1 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!--
|
|
MTConnect integration-test device model — the canned Devices.xml the fixture Agent
|
|
(mtconnect/agent) serves from /probe.
|
|
|
|
This model is authored to exercise every branch of MTConnectDataTypeInference.Infer,
|
|
and to cover the two things the driver's canned unit-test fixtures structurally CANNOT:
|
|
|
|
1. UPPER_SNAKE type spellings. A probe document writes DataItem@type in UPPER_SNAKE
|
|
(PART_COUNT, ROTARY_VELOCITY); a streams document names the same concept in
|
|
PascalCase (PartCount, RotaryVelocity). The inference is separator-insensitive to
|
|
bridge the two, and a real Agent is the only thing that proves it — an earlier
|
|
revision typed PART_COUNT as String and passed every unit test.
|
|
|
|
2. name != id on every named DataItem. The hand-authored unit fixtures use ids like
|
|
"dev1_pos" that double as browse names; on a real machine tool the two always
|
|
differ, and DriverAttributeInfo.FullName must be the *id* (the observation
|
|
correlation key) while the browse name is the *name*. Every DataItem below that
|
|
carries a name deliberately gives it a value unequal to its id.
|
|
|
|
Inference coverage map (category / type / representation -> expected DriverDataType):
|
|
|
|
SAMPLE, units -> Float64 fixture_x_pos, fixture_x_load, fixture_c_speed
|
|
SAMPLE, TIME_SERIES, sampleCount -> Float64[] array fixture_c_temp_series
|
|
EVENT, PART_COUNT -> Int64 fixture_partcount (the regression case)
|
|
EVENT, LINE_NUMBER -> Int64 fixture_linenumber
|
|
EVENT, controlled vocabulary -> String fixture_execution, fixture_avail, fixture_mode
|
|
EVENT, free text -> String fixture_program, fixture_block (never fed)
|
|
EVENT, DATA_SET representation -> String fixture_varset
|
|
CONDITION (any type) -> String + IsAlarm fixture_x_travel, fixture_logic
|
|
|
|
fixture_block is deliberately NEVER fed by the adapter, so the Agent reports it
|
|
UNAVAILABLE forever — the live half of the UNAVAILABLE -> BadNoCommunication mapping.
|
|
-->
|
|
<MTConnectDevices
|
|
xmlns="urn:mtconnect.org:MTConnectDevices:2.0"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="urn:mtconnect.org:MTConnectDevices:2.0 http://schemas.mtconnect.org/schemas/MTConnectDevices_2.0.xsd">
|
|
<Header creationTime="2026-07-24T00:00:00Z" sender="otopcua-fixture" instanceId="0" version="2.0" assetBufferSize="128" assetCount="0" bufferSize="16384"/>
|
|
<Devices>
|
|
<!--
|
|
The Adapters block in agent.cfg is keyed by this Device's NAME (OtFixtureCnc). Rename
|
|
one and you must rename the other, or the Agent starts with no adapter attached and
|
|
every observation stays UNAVAILABLE.
|
|
-->
|
|
<Device id="otfixture" name="OtFixtureCnc" uuid="otopcua-mtconnect-fixture">
|
|
<Description manufacturer="OtOpcUa" model="IntegrationFixture" serialNumber="fixture-001">MTConnect integration-test fixture</Description>
|
|
<DataItems>
|
|
<!-- Device-level data items, declared OUTSIDE any component: the walker must not drop these. -->
|
|
<DataItem category="EVENT" id="fixture_avail" name="Favail" type="AVAILABILITY"/>
|
|
<DataItem category="EVENT" id="fixture_asset_changed" type="ASSET_CHANGED"/>
|
|
<DataItem category="EVENT" id="fixture_asset_removed" type="ASSET_REMOVED"/>
|
|
</DataItems>
|
|
<Components>
|
|
<Axes id="fixture_axes" name="Axes">
|
|
<Components>
|
|
<Linear id="fixture_x" name="X">
|
|
<DataItems>
|
|
<!-- SAMPLE + units -> Float64. Driven as a sinusoid, so its value CHANGES between reads. -->
|
|
<DataItem category="SAMPLE" id="fixture_x_pos" name="Xact" type="POSITION" subType="ACTUAL" units="MILLIMETER" nativeUnits="MILLIMETER"/>
|
|
<DataItem category="SAMPLE" id="fixture_x_load" name="Xload" type="LOAD" units="PERCENT" nativeUnits="PERCENT"/>
|
|
<!-- CONDITION -> String + IsAlarm, whatever its type says. -->
|
|
<DataItem category="CONDITION" id="fixture_x_travel" name="Xtravel" type="POSITION"/>
|
|
</DataItems>
|
|
</Linear>
|
|
<Rotary id="fixture_c" name="C">
|
|
<DataItems>
|
|
<DataItem category="SAMPLE" id="fixture_c_speed" name="Cspeed" type="ROTARY_VELOCITY" subType="ACTUAL" units="REVOLUTION/MINUTE" nativeUnits="REVOLUTION/MINUTE"/>
|
|
<!--
|
|
TIME_SERIES -> Float64 ARRAY, with ArrayDim NULL (variable length).
|
|
|
|
There is deliberately no sampleCount attribute here, and there cannot be:
|
|
`sampleCount` is an attribute of a TIME_SERIES *observation*, not of a
|
|
DataItem declaration. A real Agent rejects it outright: it 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, so the tag simply vanishes from /probe (verified live
|
|
against mtconnect/agent 2.7.0.12). MTConnectDataTypeInference therefore only
|
|
ever sees sampleCount = null from a real Agent, and stamps ArrayDim = null.
|
|
`sampleRate` IS a valid DataItem attribute and is kept.
|
|
-->
|
|
<DataItem category="SAMPLE" id="fixture_c_temp_series" name="Ctemps" type="TEMPERATURE" units="CELSIUS" nativeUnits="CELSIUS" representation="TIME_SERIES" sampleRate="100"/>
|
|
</DataItems>
|
|
</Rotary>
|
|
</Components>
|
|
</Axes>
|
|
<Controller id="fixture_controller" name="Controller">
|
|
<DataItems>
|
|
<!-- Controlled vocabulary EVENT -> String. -->
|
|
<DataItem category="EVENT" id="fixture_mode" name="Cmode" type="CONTROLLER_MODE"/>
|
|
</DataItems>
|
|
<Components>
|
|
<Path id="fixture_path" name="Path">
|
|
<DataItems>
|
|
<DataItem category="EVENT" id="fixture_execution" name="Pexec" type="EXECUTION"/>
|
|
<!-- THE regression case: UPPER_SNAKE PART_COUNT must infer Int64, not String. -->
|
|
<DataItem category="EVENT" id="fixture_partcount" name="Pcount" type="PART_COUNT"/>
|
|
<DataItem category="EVENT" id="fixture_linenumber" name="Pline" type="LINE_NUMBER"/>
|
|
<DataItem category="EVENT" id="fixture_program" name="Pprogram" type="PROGRAM"/>
|
|
<!-- Free-text EVENT the adapter NEVER feeds -> stays UNAVAILABLE. -->
|
|
<DataItem category="EVENT" id="fixture_block" name="Pblock" type="BLOCK"/>
|
|
<!-- DATA_SET representation demotes to String even though the Agent sends key=value entries. -->
|
|
<DataItem category="EVENT" id="fixture_varset" name="Pvars" type="VARIABLE" representation="DATA_SET"/>
|
|
<DataItem category="CONDITION" id="fixture_logic" name="Plogic" type="LOGIC_PROGRAM"/>
|
|
</DataItems>
|
|
</Path>
|
|
</Components>
|
|
</Controller>
|
|
</Components>
|
|
</Device>
|
|
</Devices>
|
|
</MTConnectDevices>
|