Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.IntegrationTests/Docker/Devices.xml
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

130 lines
8.4 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),
fixture_writeback (never fed; write target)
EVENT, DATA_SET representation -> String fixture_varset (JSON object, #650)
EVENT, TABLE representation -> String fixture_worktable (JSON object of objects, #650)
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.
fixture_writeback is likewise never fed, and exists for a different reason: it is the
target the write legs POST to (agent.cfg has AllowPut = true). It is a SEPARATE item from
fixture_block on purpose — the first successful write would take fixture_block off
UNAVAILABLE for the life of the container and quietly rob the mapping test above of its
subject. Because no adapter ever writes fixture_writeback, the value a write leg reads back
out of /current can only have come from that write.
-->
<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"/>
<!--
The write-back target (MTCONNECT_WRITE_TARGET overrides the id when the suite is
pointed at a real Agent). Free-text EVENT, so any string a write leg sends is a
legal value, and never fed by the adapter, so nothing races the assertion.
-->
<DataItem category="EVENT" id="fixture_writeback" name="Pwriteback" type="PROGRAM_COMMENT"/>
<!-- DATA_SET representation is served as a String carrying JSON (#650); the Agent sends key=value entries. -->
<DataItem category="EVENT" id="fixture_varset" name="Pvars" type="VARIABLE" representation="DATA_SET"/>
<!-- TABLE representation (#650): rows of cells, served as nested JSON. -->
<DataItem category="EVENT" id="fixture_worktable" name="Ptab" type="WORK_OFFSET" representation="TABLE"/>
<DataItem category="CONDITION" id="fixture_logic" name="Plogic" type="LOGIC_PROGRAM"/>
</DataItems>
</Path>
</Components>
</Controller>
</Components>
</Device>
</Devices>
</MTConnectDevices>