Modbus / PLC integration¶
This gateway ships Node-RED with node-red-contrib-modbus pre-installed —
the node set for talking to PLCs and other devices over Modbus (serial or
TCP). Filter the palette for "modbus" to see the full set:

| Node | Role |
|---|---|
| Modbus - Read | Reads one or more registers/coils on a schedule or trigger |
| Modbus - Write | Writes a value to a register/coil |
| Modbus - Getter | Reads a register/coil in response to an incoming message |
| Modbus - Flex - Getter | Like Getter, with more flexible addressing per-message |
| Modbus - Flex - Write | Like Write, with more flexible addressing per-message |
| Modbus - Response | Formats/handles the raw response from a read |
| Modbus - Response - Filter | Filters responses, e.g. only passing on changed values |
| Modbus - Server | Runs this gateway as a Modbus server other devices can query |
| Modbus - Queue - Info | Reports on the internal request queue for a Modbus connection |
| Modbus - Flex - Connector | A configurable low-level connector for custom polling patterns |
| Modbus - Flex - Sequencer | Sequences multiple Modbus operations |
| Modbus - Flex - FC | Sends a specific Modbus function code directly |
Typical pattern¶
A basic PLC-read flow looks like: an inject node (or a timestamp/timer) triggers on an interval → a Modbus - Read node polls a register range from the PLC → a function or change node reshapes the result → an output node (an http request, mqtt out, or a write into SCADash via its API) sends the value onward.
Double-click a Modbus node to configure:
- Connection — the PLC's address, port (502 for Modbus TCP), and unit ID. Connections are shared Configuration Nodes — set one up once and reuse it across multiple Modbus nodes.
- Register type and address — which Modbus data area (coil, discrete input, holding register, input register) and the starting address to read or write.
- Polling / trigger behavior — varies by node; Read nodes typically poll on an interval, Getter nodes respond to an incoming message instead.
Note
The exact addresses and register types depend entirely on your PLC's own Modbus map — consult the PLC's documentation for what's available at which address.
Connecting to a Modbus RTU device¶
Every Modbus node's Server field points at a shared connection
(modbus-client) config node. Click the pencil icon next to Server to edit
an existing one, or + to add a new one, then set Type to Serial
or Serial Expert:

| Field | Notes |
|---|---|
| Serial port | The gateway's serial device path, e.g. /dev/ttyUSB0 |
| Serial type | RTU, RTU-BUFFERD, or ASCII |
| Baud rate | 115200 down to 75 — must match the device |
| Data Bits | 8, 7, 6, or 5 |
| Stop Bits | 1, 1.5, or 2 |
| Parity | None, Even, Mark, Odd, or Space |
| Connection delay (ms) | Serial Expert only — a pause after opening the port before communicating, useful for RS-485 converters that need time to switch direction |
| Unit-Id | Default slave address for nodes that don't override it per-message |
| Timeout (ms) | How long to wait for a response before failing |
| Reconnect on timeout | Automatically reopen the port after a timeout |
For a TCP-connected device (or a Modbus TCP-to-RTU gateway), use Type:
TCP instead — Host, Port (502 by default), and a TCP Type of
RTU-BUFFERED if it's actually bridging to RTU devices on the other side:

Every serial RTU device sharing one port must use the same modbus-client
config node — that's what makes the connection shared and serializes access
to the bus, instead of two nodes fighting over the same serial port.
Example: reading and writing by function code with the Flex nodes¶
The Flex - Getter and Flex - Write nodes don't have fixed
register/function-code settings in their edit dialog at all — they read the
Modbus function code, address, and quantity from msg.payload on every
incoming message instead. That makes them the right choice when you want to
pick the function code dynamically (different registers per poll, a
generic "read anything" flow, etc.) rather than hard-coding one read per
node.
Reading (Flex - Getter)¶
A function node builds the request, a Modbus - Flex - Getter reads it, and a debug node shows the result:

The function node's code:
msg.payload = {
fc: 3, // function code: 3 = Read Holding Registers
unitid: 5, // RTU slave address of the device on the bus
address: 0, // starting register address
quantity: 10 // number of registers to read
};
return msg;
Function codes accepted by Flex - Getter (all read operations):
fc |
Meaning |
|---|---|
| 1 | Read Coil Status |
| 2 | Read Input Status |
| 3 | Read Holding Registers |
| 4 | Read Input Registers |
Double-click the Flex - Getter node itself and set its Server to the
modbus-client connection for your RTU device (see above) — the node only
needs that connection plus whatever comes in on msg.payload.
Writing (Flex - Write)¶
Same pattern, with a Modbus - Flex - Write node instead:

// Single coil (FC5) — value must be true/false or 1/0
msg.payload = { fc: 5, unitid: 5, address: 0, quantity: 1, value: true };
// Single register (FC6) — value is one number, 0-65535
// msg.payload = { fc: 6, unitid: 5, address: 0, quantity: 1, value: 1234 };
// Multiple registers (FC16) — value is an array of numbers, 0-65535 each
// msg.payload = { fc: 16, unitid: 5, address: 0, quantity: 3, value: [100, 200, 300] };
return msg;
Function codes accepted by Flex - Write (all write operations):
fc |
Meaning | value |
|---|---|---|
| 5 | Force Single Coil | true/false or 1/0 |
| 6 | Preset Single Register | one number, 0–65535 |
| 15 | Force Multiple Coils | array of booleans |
| 16 | Preset Multiple Registers | array of numbers, each 0–65535 |
As with Flex - Getter, point the node's Server field at your device's
modbus-client connection, then trigger the function node (an inject
node, an incoming value from elsewhere in the flow, etc.) whenever you need
to write.
Note
unitid in the message overrides the connection's default Unit-Id —
handy when several RTU devices share the same serial port and you're
picking which one to talk to per-message.