MiravoMiravo

Modbus TCP

Read live Miravo values as Modbus registers from a polling client. Miravo generates the address map from the running factory.

Modbus TCP

Connect

ItemDefault
Endpoint127.0.0.1:1502 (--modbus-port, --modbus-host)
RegistersHolding 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:

AssetMemberAddressRegistersTypeAccess
cnc-001run_command01booleanread/write
cnc-001spindle_speed_setpoint (rpm)12float32read-only
cnc-001spindle_speed_feedback (rpm)52float32read-only
cnc-001running71booleanread-only
wash-001run_command641booleanread/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

ItemBehavior
Function codesFC03 read holding, FC04 read input, FC06 write single, FC16 write multiple. Anything else (coils, discrete inputs, diagnostics) returns exception 01 Illegal Function.
FC04Mirrors FC03. Input registers return the same values as holding registers at the same address. Litmus Edge and most clients use holding registers only.
Unit IDAny unit ID (0 to 255) is accepted and echoed back.
Float32 and 32-bit integersTwo registers, big-endian word and byte order (AB CD). Set your client to big-endian with no word swap.
Read quantity1 to 125 registers. Otherwise exception 03 Illegal Data Value.
Read rangeThe 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 quantityFC16 accepts 1 to 123 registers, and the byte count must match. Otherwise exception 03.
Write targetEvery 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 valueA 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 failureException 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 state exposes 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 readwrite or write member, 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

Was this page useful?

Last updated on