doc/help/Drivers-Modbus.md
Modbus is a long-standing industrial protocol. Designed in 1979 by Modicon for their PLCs, it is now a de-facto standard across factory automation, building management, energy metering, and process control. PLCs, RTUs, and SCADA equipment overwhelmingly speak Modbus, so Serial Studio uses it to read most factory-floor devices.
Serial Studio Pro implements both Modbus RTU (over serial) and Modbus TCP (over Ethernet), and includes a register-map importer that turns vendor CSV/XML/JSON files into a working project automatically.
Modbus is a request/response, master/slave protocol for reading and writing memory locations on a remote device. The model is intentionally simple:
There is no streaming, no events, and no push notifications. The master polls; the slave answers. Continuous data requires continuous polling.
sequenceDiagram
participant M as Master (Serial Studio)
participant S as Slave (PLC, sensor)
M->>S: Read Holding Registers (start=100, count=10)
S-->>M: 10 register values
M->>S: Read Input Registers (start=0, count=4)
S-->>M: 4 register values
M->>S: Write Single Register (addr=200, value=42)
S-->>M: Echo confirmation
Note over M,S: continuous polling at configured interval
Modbus organises data into four tables, distinguished by read/write capability and bit width:
| Table | Width | Access | Typical use |
|---|---|---|---|
| Coils | 1 bit | R/W | Digital outputs (relays, valves) |
| Discrete inputs | 1 bit | R only | Digital inputs (switches, sensors) |
| Input registers | 16 bits | R only | Analog inputs (sensor readings) |
| Holding registers | 16 bits | R/W | General-purpose storage (setpoints, configuration, scratch values) |
Real devices are fuzzier than the table suggests. A modern temperature transmitter might expose its current reading as a holding register (writable in principle, but writing has no effect) because that is what the firmware engineer chose. Always check the device's documentation rather than assume.
Addresses inside each table go from 0 to 65535. Vendors document them in two ways, which is a frequent source of confusion:
40001-49999 for holding registers, 30001-39999 for input registers, and so on.0-65535 per table.Modbus on the wire uses protocol numbering. PLC numbering is a vendor convention. Holding register 40100 in PLC numbering is address 99 in protocol numbering. Off-by-one is the single most common Modbus debugging story.
Every Modbus request carries a one-byte function code identifying what to do. The common ones:
| Code | Function | Tables it touches |
|---|---|---|
| 1 | Read Coils | Coils |
| 2 | Read Discrete Inputs | Discrete inputs |
| 3 | Read Holding Registers | Holding registers |
| 4 | Read Input Registers | Input registers |
| 5 | Write Single Coil | Coils |
| 6 | Write Single Register | Holding registers |
| 15 | Write Multiple Coils | Coils |
| 16 | Write Multiple Registers | Holding registers |
More function codes exist (read/write combined, file records, diagnostics), but those eight cover 95% of real-world traffic.
A Modbus register is 16 bits. Anything wider spans multiple consecutive registers:
uint32, int32, float32: 2 registers (4 bytes).uint64, int64, float64: 4 registers (8 bytes).Byte and word order are device-specific:
0x12345678 in two registers reads as register A = 0x1234, register B = 0x5678.0x5678, register B = 0x1234. Common on some legacy gear.Vendor documentation always specifies the order. Serial Studio's register-map importer assumes big-endian by default (the convention used by most modern devices); for anything else, edit the generated frame parser.
Modbus rides on top of two transports:
The original. Runs over RS-232, RS-485, or RS-422. Each frame is wrapped with a slave address (1 byte, identifies which device on a multi-drop bus), the function code, the data, and a CRC-16 checksum. Frames are separated by a 3.5-character idle gap on the line.
flowchart LR
A[Slave Address
1 byte] --> B[Function Code
1 byte]
B --> C[Data
0-252 bytes]
C --> D[CRC-16
2 bytes]
RTU usually runs on RS-485, which supports up to 247 slaves on a single pair of wires. Each slave has a unique address from 1 to 247; address 0 is reserved for broadcast.
The Ethernet variant. Wraps Modbus PDUs in a TCP stream. The frame format is different:
flowchart LR
subgraph MBAP[MBAP header]
T[Transaction ID
2 bytes] --> P[Protocol ID
2 bytes, always 0]
P --> L[Length
2 bytes]
L --> U[Unit ID
1 byte]
end
U --> F[Function Code
1 byte]
F --> D[Data]
There is no CRC because TCP already handles error detection. The Unit ID is equivalent to the slave address; it identifies the target when a TCP-to-RTU gateway fronts a multi-drop RS-485 bus. Native Modbus TCP devices typically use Unit ID 1 or 255.
The standard Modbus TCP port is 502.
Serial Studio acts as the master (Modbus client). One connection polls one slave address; every configured register group is read from that slave. Writes from Output Controls target holding registers on the same slave (one or two consecutive registers per write).
Setup is a hierarchy:
127.0.0.1) and Port. Serial Studio defaults to port 5020, the unprivileged port most local simulators bind; real devices almost always listen on 502.On each poll tick, Serial Studio reads the groups sequentially: it sends the request for the first group, waits for the reply, then moves to the next. Each reply is published to the frame parser as its own binary frame in RTU layout, [slave address, function code, byte count, data...], with no CRC appended; the same layout is used on TCP connections. Register data arrives big-endian (high byte first); coil and discrete-input data arrives as packed bits, least-significant bit first. A reply that reports an error produces no frame. If a reply is still outstanding when the timer fires again, that cycle is skipped, so a slow slave lowers the effective poll rate instead of queueing requests. Requests time out after 1000 ms with 3 retries.
The frame parser extracts named datasets from those bytes through its parse(frame) entry point, where frame is the byte array above (use the Binary decoder and no frame delimiters). Because the groups arrive as separate frames, a hand-written parser must track which group each frame belongs to; the auto-generated Lua parser counts frames through the cycle. See Frame Parser Scripting.
For devices with documented register maps, the Modbus map importer (Import Register Map… in the setup panel) reads vendor CSV/XML/JSON files and generates the register groups, datasets, and a complete Lua frame parser automatically. This is the recommended starting point. Without a vendor file, the Generate Project button in the register-groups dialog builds an equivalent project from the groups configured by hand.
The Modbus driver wraps Qt's QModbusClient and runs on the main thread. Polling is event-driven (no busy loop); Qt's async I/O delivers responses via signals. See Threading and Timing Guarantees.
The Socket API and the in-app AI Assistant configure this driver through the io.modbus.* command scope. Mutations: setProtocolIndex (param protocolIndex: 0 = RTU, 1 = TCP), setSlaveAddress (address: 1-247), setPollInterval (intervalMs: minimum 10), setHost (host), setPort (port), setSerialPortIndex (portIndex), setBaudRate (baudRate), setParityIndex (parityIndex), setDataBitsIndex (dataBitsIndex), setStopBitsIndex (stopBitsIndex), addRegisterGroup (type: 0 = Holding Registers, 1 = Input Registers, 2 = Coils, 3 = Discrete Inputs; startAddress: 0-65535; count: 1-125), removeRegisterGroup (groupIndex), clearRegisterGroups. Read-only: getConfig, listProtocols, listSerialPorts, listParities, listDataBits, listStopBits, listBaudRates, listRegisterTypes, listRegisterGroups. For the AI Assistant the setters are device-gated: blocked until the user ticks Allow device control, and each call still requires confirmation.
For step-by-step setup, see the Protocol Setup Guides, Modbus section.
1.234e-23 is being decoded with the wrong endianness.