Verified: Modbus TCP
Each claim the Modbus TCP page makes, with the tests that
prove it in the last green CI run: 19 of 49 verified, 6 with no test yet.
The claims live in docs/claims/modbus.yaml.
Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims
MB-001 verified
A modbus driver without a `manifest:` key is rejected at project load (so `naut check` fails), and a misspelled manifest key or an unknown source reference is a load error, not a silently dropped binding.
- pass
TestLoadModbusErrorsininternal/project(5 of 5 passed) - pass
TestParseManifestRejectsUnknownKeysinmodbus(6 of 6 passed) - pass
TestSchemaRequiresWhatLoaderRequiresininternal/project
MB-002 verified
`New` never dials: it validates the manifest and computes the block plan offline, so a project with a modbus driver loads and checks with no device present, and an invalid manifest fails in `New`.
- pass
TestNewNeverDialsAndReadsFaultBeforeStartinmodbus - pass
TestLoadModbusDriverininternal/project
MB-003 verified
`naut modbus import` emits a manifest and a tag file that are byte-identical on every re-run.
- pass
TestModbusImportGoldenincmd/naut - pass
TestGenerateDeterministicinmodbus/codegen
MB-004 partly verified
The device map's per-type defaults and per-instance overrides are applied to the generated manifest, and `--instance` and the `--tags` / `--writable` globs select and mark the right instances and tags.
- pass
TestGenerateAppliesOverridesAndDefaultsinmodbus/codegen - pass
TestGenerateInstanceFilterinmodbus/codegen - pass
TestGenerateWritableAndTagGlobsinmodbus/codegen
MB-005 verified
The generated tag file gives each tag the right role (input or output) and `--tags-skip` leaves hand-declared tags out of it.
- pass
TestTagsYAMLRolesAndSkipinmodbus/codegen
MB-006 verified
`naut modbus tags` re-derives the tag file from a committed manifest byte for byte with `--map`, and without `--map` keeps names and roles and drops the unit/desc/init columns.
- pass
TestModbusTagsRederivesincmd/naut
MB-007 verified
The planner coalesces bindings on the same source, table and scan class into one block read, so adjacent registers of mixed formats become a single request; sources, tables and classes never share a block.
MB-008 verified
A block spans up to 8 unaddressed registers rather than split, a hole beyond that splits it, and `WithBlockGap(0)` never bridges a hole.
- pass
TestPlanGapToleranceinmodbus
MB-009 partly verified
Without `max-block`, a block holds at most 125 registers (FC3/FC4) or 2000 coils (FC1/FC2); `max-block` lowers the cap, and a multi-register value is never split across two blocks.
MB-010 partly verified
The reserved scan class `none` catalogs a tag without ever polling it; `write-only` bindings are also left out of the read plan while a writable binding that can be read back is still polled.
- pass
TestPlanSkipsNoPollAndWriteOnlyinmodbus
MB-011 partly verified
Scan classes poll at their own rates: a binding's `scan-class` places it in its own poll group and unassigned bindings use the default class at `scan-rate`.
- pass
TestDriverDeliversTagsPerClassinmodbus
MB-012 no test yet
`tag-classes:` globs in `nautilus.yaml` override the generated scan class, so re-running the import never erases polling policy.
MB-013 partly verified
Addresses are 0-based PDU addresses end to end (datasheet 40001 is holding address 0), identical in the map, manifest, `browse` and plan output.
MB-014 verified
Marking a binding on a read-only table (input or discrete) `writable` is a validation error.
MB-015 partly verified
The formats int16, uint16, int32, uint32, float32, float64, bool and `bit:N` decode correctly, and bit tables accept only `bool`.
MB-016 verified
Scaling is `engineering = raw * scale + offset` on read, inverted on write, and `scale: 0` means 1 (an unset scale is the identity).
MB-017 verified
Word order and byte order are per source: `wordorder: little` reverses the register sequence of a multi-register value and `byteorder: little` swaps the bytes inside each register, in all four combinations.
MB-018 verified
Each source gets a `<source>__Online` BOOL input that is true while its connection is up and false while the source is dead or parked.
MB-019 partly verified
The `__Online` companion is present from `Start`, before any poll has landed, and data tags stay absent from the snapshot until their block has been read at least once.
- pass
TestForeignExceptionMarksOnlyThatBlockinmodbus
MB-020 partly verified
A source with an `enable:` tag that is false is parked: no connection is attempted, `__Online` is false, its tags hold their last values and report Stale, and setting the tag true wakes the source.
- pass
TestEnableTagParksSourceinmodbus
MB-021 partly verified
The enable tag is a command to the driver, not a register write: it appears in `OutputNames()` and never goes on the wire.
- pass
TestEnableTagParksSourceinmodbus
MB-022 verified
A Modbus exception response marks only that block's tags Bad while sibling blocks on the same device stay Good, the source stays online and polling continues without reconnecting, and the block recovers when the fault clears.
MB-023 partly verified
Three consecutive exceptions park just that block for one `retrymin` period (no requests for it, siblings keep polling), after which one probe goes out; a refused block is counted as `badBlocks`.
MB-024 partly verified
A transport failure (dial refused, timeout, short read, wrong unit id) takes the whole source down: the driver drops the connection and reconnects, values hold, tags delivered at least once report Stale, and `__Online` goes false.
MB-025 no test yet
Reconnect backoff is exponential from `retrymin` (1s) to `retrymax` (60s) and resets on success.
MB-026 verified
A tag never delivered reports NotConnected rather than a made-up value, and nothing is zeroed or invented while a source is down.
MB-027 partly verified
Quality is reported non-Good only (a healthy plant reports an empty map) and is source-scoped: a dead device affects only its own tags.
- pass
TestDriverDeliversTagsPerClassinmodbus
MB-028 verified
A `writable: true` binding on a holding or coil table becomes an output whose writes reach the device as FC 5/6/15/16, and the value lands in the device register.
MB-029 verified
The first output snapshot after `Start` is a baseline that writes nothing, and thereafter only a value that changed goes on the wire, once.
MB-030 partly verified
A command raised while the source is down is queued per source (latest value per tag), flushed on reconnect, and counted as `queuedWrites`.
- pass
TestSlaveDeathHoldsValuesQueuesWritesRecoversinmodbus
MB-031 partly verified
A binding with `rewrite:` re-asserts its last commanded value on that period even when nothing changed, and its baseline value is written on connect, while non-rewrite bindings stay change-only.
- pass
TestRewriteFiresOnPeriodAndStopsOnGateinmodbus
MB-032 partly verified
A `write-only` register is taken out of the read plan; without it a writable binding is also polled and the read-back is the source of truth.
- pass
TestPlanSkipsNoPollAndWriteOnlyinmodbus
MB-033 partly verified
Exactly adjacent register writes in one flush merge into a single FC16, adjacent coils into one FC15, and a lone register or coil goes as FC6 or FC5.
- pass
TestWriteCoalescingAndBaselineinmodbus
MB-035 no test yet
Requests on a source are strictly sequential: one outstanding request at a time, never pipelined.
MB-036 partly verified
`/api/drivers` reports Kind `modbus` with one device row per source, and the driver state steps connecting, error (nothing answers), degraded (a source dark or any block refused), connected, or waiting when every source is parked.
- pass
TestModbusStatusininternal/project - pass
TestLoadModbusDriverininternal/project
MB-037 partly verified
`Extra["sources"]` carries per-source rows (state, blocks, badBlocks, queuedWrites, rttMs, retries, exceptions) and a queued-writes gauge appears only when a command is parked.
- pass
TestModbusStatusininternal/project
MB-038 verified
`naut modbus serve` stands in for a plant from the manifest alone: a `--values` seed in engineering units is stored with the binding's scaling and word order inverted.
- pass
TestModbusServeSeedSmokeincmd/naut
MB-039 verified
The bench slave serves the whole manifest on one listener, multi-unit like a real gateway.
MB-040 no test yet
`--ramp` drifts the numeric inputs and `--listen` defaults to `127.0.0.1:5020`.
MB-041 no test yet
`naut modbus browse` reads a range from a device and prints every address raw and decoded, with every format side by side when `--format` is absent.
MB-042 verified
`drivers:` lists several drivers: reads fan out and merge, a write goes to the one driver that claims the tag, and names default to the type, deduped.
- pass
TestMultiRoutesAndMergesinio - pass
TestDriversBuildAMultiWithStatusRowsininternal/project
MB-043 partly verified
A tag delivered by two drivers is a load error naming both, reported offline by `naut check`; setting `driver:` and `drivers:` together is also an error.
- pass
TestMultiDuplicateTagIsAnErrorinio - pass
TestDriversDuplicateTagNamesBothDriversininternal/project - pass
TestDriverAndDriversBothSetIsAnErrorininternal/project - pass
TestCheckReportsDuplicateTagAcrossDriversincmd/naut
MB-044 partly verified
In a multi-driver set each driver keeps its own `/api/drivers` row, quality merges per tag, and on a `sparkplug:` edge node each driver is its own Sparkplug device, born and killed by its own connection, so one dead bus takes only its own tags offline.
- pass
TestDriversBuildAMultiWithStatusRowsininternal/project - pass
TestMultiQualityMergesinio - pass
TestDeviceBirthWaitsForFirstReadinsparkplug
MB-045 verified
The `memory` loopback cannot join a `drivers:` list.
- pass
TestDriversMemoryCannotShareAScanininternal/project - pass
TestMultiRefusesTheUnroutableinio
MB-046 partly verified
With a `redundancy:` section the write gate is wired to leadership so commands leave the driver only on the leader, reads are unaffected, and a closed gate also stops `rewrite:` re-asserts (resuming when reopened).
- pass
TestRewriteFiresOnPeriodAndStopsOnGateinmodbus
MB-047 no test yet
`naut test` with a `memory.yaml` loopback runs the acceptance suites without opening a socket, and `given:` writes the input image so the same `*_test.yaml` passes unchanged against the real driver.
MB-048 partly verified
Against a foreign pymodbus server the driver decodes every seeded value of every format in both word orders across all four tables, round-trips every writable format, marks an unimplemented range (exception 0x02) bad while siblings stay Good, and a slow response drives the reconnect path with values held.
MB-049 partly verified
Modbus RTU/ASCII, BCD, int64 and string formats, one-based addressing and pipelined requests are unsupported: unknown formats are rejected.
- pass
TestParseFormatinmodbus
MB-050 partly verified
`WithDialer` substitutes the TCP dial, `driver.Plan()` returns the computed block plan, and `Stop` closes every source's connection.