b4480bf45a
The fixture Agent flips to AllowPut = true (its comment rewritten: writes are in
scope now that the driver implements IWritable) and the suite grows three
env-gated write legs, all still Assert.Skip-clean with no Agent reachable.
Devices.xml gains a DataItem, and that is the deliberate part. The write target
has to be one the SHDR adapter NEVER feeds, or the assertion races the stream --
but it must NOT be the existing never-fed fixture_block: the first successful
write takes an item off UNAVAILABLE for the life of the container, which would
silently rob Read_maps_an_unavailable_observation_to_BadNoCommunication of its
subject. So fixture_writeback (free-text EVENT, PROGRAM_COMMENT) is a separate,
dedicated target, overridable via MTCONNECT_WRITE_TARGET for a run pointed at a
real machine tool.
The three legs:
* Round trip -- driver write accepted, then the injected value read back off
/current over plain HTTP (not through the parser under test) AND through the
driver's own ReadAsync. The value carries a TickCount64 suffix: the Agent
keeps an injected observation for the container's life, so a constant would
be satisfied by a previous run's leftover.
* Agent refusal -> BadCommunicationError. The design doc doubted this leg was
constructible ("an id in the probe but refused by the agent"); it is, and the
mechanism is BuildWriteTargets. Write targets are derived from the tags the
BINDING serves, with the device resolved from the probe model and falling
back to the configured deviceName -- so an authored tag naming an id the
Agent never declared, on a driver whose deviceName IS set, resolves to a real
URL and is POSTed. That is exactly the shape a stale tag has after a DataItem
is renamed. The second half of the test is the falsifiability control: the
SAME unknown id with NO deviceName has no URL and must be refused locally as
BadNodeIdUnknown. Two different codes is what proves the first write actually
reached the Agent rather than being short-circuited into a look-alike.
* Disabled gate -- write.enabled absent returns BadNotWritable AND /current is
unchanged from the snapshot taken just before, which is what distinguishes
"refused" from "reported refused and sent anyway".
RAN LIVE, not just compiled: the fixture was brought up locally (OrbStack,
MTCONNECT_AGENT_HOST_PORT=5555 -- macOS AirPlay squats :5000) and the whole
suite passed 15/15, 0 skipped. Evidence beyond the green: the Agent reports
<ProgramComment dataItemId="fixture_writeback">OTOPCUA-WRITE-2CBCB530</...> and
its log carries "Error processing request:
/OtFixtureCnc?otopcua_no_such_data_item_2CBCB3D9=refuse-me" -- the refusal leg
demonstrably went to the wire. Offline (no Agent) the suite is 15 skipped, 0
failed; build 0 warnings / 0 errors.
Note for anyone reading agent.cfg: BufferSize = 14 is an EXPONENT (2^14 = 16384
observations), not a 14-deep ring, and /current is answered from the Agent's
per-DataItem checkpoint anyway -- so eviction is not a hazard for a write
read-back even on this busy fixture stream.
LiveOptions/ConfigJson/LiveDriverAsync gained writeEnabled + deviceName
parameters, both defaulted off/null so every pre-existing test still runs
against the shipped read-only driver whatever the Agent's AllowPut says. The
config JSON sources write.enabled FROM the options object, so the parsed config
can never disagree with the options a test thinks it built.
61 lines
2.8 KiB
INI
61 lines
2.8 KiB
INI
# MTConnect C++ Agent configuration for the OtOpcUa integration-test fixture.
|
|
#
|
|
# ASCII ONLY. The Agent's config parser rejects the whole file on a non-ASCII byte --
|
|
# even inside a comment -- with a bare "Failed / Stopped at line: N" and exits. An em
|
|
# dash in a comment is enough to make the fixture never start.
|
|
#
|
|
# The image's CMD is `/usr/bin/mtcagent run /mtconnect/config/agent.cfg`, so this file is
|
|
# bind-mounted at exactly that path (see docker-compose.yml). Paths below are ABSOLUTE on
|
|
# purpose: a relative `Devices` is resolved against the Agent's working directory
|
|
# (/home/agent), not against this file, and would silently start an Agent with no device
|
|
# model.
|
|
|
|
Devices = /mtconnect/config/Devices.xml
|
|
Port = 5000
|
|
ServiceName = OtOpcUaMTConnectFixture
|
|
|
|
# 2^14 = 16384 observations retained. Generous on purpose: the driver's /sample pump
|
|
# re-baselines through /current when its cursor falls out of the Agent's ring buffer, and
|
|
# a small buffer would make the fixture exercise that recovery path on every run instead
|
|
# of the steady-state streaming the suite is here to prove.
|
|
BufferSize = 14
|
|
CheckpointFrequency = 1000
|
|
MaxAssets = 128
|
|
|
|
# Writes ARE in scope for this fixture, because the driver now implements IWritable (see
|
|
# docs/plans/2026-08-05-mtconnect-writeback-design.md). The mechanism is this very key: with
|
|
# AllowPut on, POST {agent}/{device}?{dataItemId}={value} sets what the Agent REPORTS for that
|
|
# observation. It is injection into the Agent's buffer -- visible to every consumer of the
|
|
# Agent, overwritten by the next SHDR update for the same DataItem -- and never a command to a
|
|
# machine. The integration suite's write legs (round-trip, agent refusal, disabled gate) need
|
|
# it on; with the driver's own write.enabled off, the shipped read-only behaviour is unchanged.
|
|
#
|
|
# No AllowPutFrom is set, so any host that can reach the port may write. Acceptable for a
|
|
# throwaway test Agent on the fixture network; NOT acceptable for a plant Agent, where the
|
|
# allow-list is the second half of the gate.
|
|
AllowPut = true
|
|
|
|
SchemaVersion = 2.0
|
|
MonitorConfigFiles = false
|
|
Pretty = true
|
|
|
|
# The block name must equal the Device name attribute in Devices.xml (OtFixtureCnc) --
|
|
# that is how the Agent binds an adapter connection to a device. "adapter" is the compose
|
|
# service name of the SHDR feeder; the Agent dials OUT to it.
|
|
Adapters {
|
|
OtFixtureCnc {
|
|
Host = adapter
|
|
Port = 7878
|
|
ReconnectInterval = 2000
|
|
}
|
|
}
|
|
|
|
# Log to stdout rather than a file: /mtconnect/log is an image-declared VOLUME that is
|
|
# created root-owned while the Agent runs as uid 1000, so file logging fails to open.
|
|
# stdout also puts adapter-connect / device-model errors straight into `docker logs`,
|
|
# which is how you diagnose a fixture that comes up but reports everything UNAVAILABLE.
|
|
logger_config {
|
|
logging_level = info
|
|
output = cout
|
|
}
|