Modbus TCP
Read live Miravo values as Modbus registers from a polling client. Miravo generates the address map from the running factory.
Connect
| Item | Default |
|---|---|
| Endpoint | 127.0.0.1:1502 (--modbus-port, --modbus-host) |
| Registers | Holding registers, zero-based. Float32 and 32-bit integers take two registers, big-endian AB CD. |
Example from smart-factory, taken from miravo adapter inspect modbus --json:
| Asset | Member | Address | Registers | Type | Access |
|---|---|---|---|---|---|
cnc-001 | run_command | 0 | 1 | boolean | read/write |
cnc-001 | spindle_speed_setpoint (rpm) | 1 | 2 | float32 | read-only |
cnc-001 | spindle_speed_feedback (rpm) | 5 | 2 | float32 | read-only |
cnc-001 | running | 7 | 1 | boolean | read-only |
wash-001 | run_command | 64 | 1 | boolean | read/write |
The cnc-001 block is 64 registers wide, so the next asset starts at address 64. Read two holding registers at address 1 and decode them as one float32: 0x45FA 0x0000 is 8000.0 rpm.
Built-in templates enable Modbus TCP. A custom template enables it with protocols.modbus, or pass --modbus to miravo start.
Read-only members update as the factory runs. readwrite and write members accept Modbus writes, which change the same running factory the Console shows. The next read returns the new value.
Protocol behavior
| Item | Behavior |
|---|---|
| Function codes | FC03 read holding, FC04 read input, FC06 write single, FC16 write multiple. Anything else (coils, discrete inputs, diagnostics) returns exception 01 Illegal Function. |
| FC04 | Mirrors FC03. Input registers return the same values as holding registers at the same address. Litmus Edge and most clients use holding registers only. |
| Unit ID | Any unit ID (0 to 255) is accepted and echoed back. |
| Float32 and 32-bit integers | Two registers, big-endian word and byte order (AB CD). Set your client to big-endian with no word swap. |
| Read quantity | 1 to 125 registers. Otherwise exception 03 Illegal Data Value. |
| Read range | The whole request must lie inside the allocated map, from address 0 to the end of the last block, padding included. A request beyond it, or sent before the first tick, returns exception 02 Illegal Data Address. |
| Write quantity | FC16 accepts 1 to 123 registers, and the byte count must match. Otherwise exception 03. |
| Write target | Every register must belong to a readwrite or write member and cover it fully (both registers of a float32). A write to a read-only, padding, unmapped or half-covered register returns exception 02 and applies nothing. |
| Write value | A value the engine rejects (NaN, an infinite float, or a value outside the member eu_range) returns exception 03. The register keeps its old value. |
| Server failure | Exception 04 Server Device Failure is an internal adapter error. Report it with the output of miravo logs. |
Every rejected write (exception 02 or 03) is the client's error, not an engine error. The adapter counts it in its rejectedWrites metric and leaves errorsCount and the engine error count alone. The log shows the first rejection per member (or register) at warn level, then at most one summary line per 10 s.
Production-line stations add members after the model members in the asset block, in a fixed order:
- Numeric
stateexposes codes 0 to 6. - Integer counts use UInt32. State durations are doubles.
- String labels, stop reasons and run IDs are skipped by Modbus. They stay available over MQTT and OPC UA.
Export the manifest to find the appended addresses. Block sizing includes them. See Production lines.
Find a register
The Console Tags view shows the Modbus register number of each member. Export the filtered list as CSV from the same view.
Stable addressing
Addresses are zero-based. Assets get blocks in template declaration order, not alphabetical ID order. Members inside a block follow model declaration order, and String members are skipped. Each numeric or Boolean member takes 1 or 2 registers. An asset's size is rounded up to a multiple of 16, and the next block starts at its end. Assets with only string members reserve no registers.
For example, two Float members use addresses 0 and 2, and the next asset starts at 16. Existing blocks never move during an adapter run. Removing an asset leaves a gap that reads 0, and new assets, including re-added IDs, go after the highest allocated block. A restart or reset builds a fresh map, the same one for the same template and models. Editing declarations can change that layout.
Inspect the live map with miravo adapter inspect modbus (or --json). The same addresses appear in GET /api/manifest, the Console and Litmus Edge exports. Offline Litmus Edge planning uses the same allocator, and live planning uses the manifest after runtime changes. The map is capped at 65,536 registers. When it overflows, Miravo logs a warning and suggests fewer assets, or MQTT and OPC UA for full coverage.
How to know it worked
Verified
- Your client polls a holding register and the value keeps changing.
- Register numbers in the Console Tags view match what your client polls.
- After you write to a
readwriteorwritemember, the next read returns the new value. - A write outside the member's range, or to a read-only register, returns a Modbus exception instead of an acknowledgement.
Check that a master is reading
connected in adapters[].metrics (/api/metrics, miravo status --json) only says the Modbus server is listening. It does not say a master is polling. adapters[].metrics.clients does: active is the number of open TCP connections, reads and writes count requests answered without an exception since start. miravo status prints them as Modbus: connected | 3 clients, 1,204 reads | .... 0 clients after you pointed a master at Miravo means it never connected, or it connects only for each poll.
Next
Choose a template
Point the same client at another template.
Litmus Edge
Map the running factory into Litmus Edge.
Was this page useful?
Last updated on
Miravo