Node-RED Modbus Integration — Testing Guide
Node-RED shows up in more industrial data pipelines every year — as the edge logic layer on an IIoT gateway, as a quick Modbus-to-MQTT bridge, or as the flow engine behind a plant dashboard. The node-red-contrib-modbus package makes wiring up Modbus reads and writes fast, but "fast" also means it's easy to get byte order, polling intervals, or addressing wrong without noticing until a value looks subtly off. This guide covers how the package's nodes actually work and how to test a flow properly, against a simulator, before it touches a real PLC.
The Standard Package: node-red-contrib-modbus
node-red-contrib-modbus, maintained by BiancoRoyal, is the package almost every Node-RED Modbus flow is built on. Install it from the Node-RED palette manager (Menu → Manage palette → Install, search "modbus") or from the command line in your Node-RED user directory:
cd ~/.node-red
npm install node-red-contrib-modbus
The package (currently on the 5.x line, requiring Node-RED 4+ and a recent Node.js) ships 14 node types. The ones you'll use in almost every flow:
| Node | Role |
|---|---|
| modbus-client | Shared connection config (TCP or serial) — every other node references one of these |
| modbus-read | Polls a fixed address/quantity/function code on a timer |
| modbus-getter | On-demand single read, triggered by an input message |
| modbus-flex-getter | Read where address/quantity/function code/unit ID come from msg.payload |
| modbus-write / modbus-flex-write | Fixed or payload-driven writes |
| modbus-server | Runs Node-RED itself as a Modbus TCP slave |
| modbus-queue-info | Reports the client's internal request queue depth — useful for diagnosing a polling interval that's too aggressive |
Configuring the Modbus Client
Every read/write node points at a shared modbus-client configuration node, which owns the actual TCP socket or serial port and queues requests so multiple nodes sharing one connection don't collide on the wire. Set:
- Type: TCP or Serial/RTU
- Host / Port: for TCP, the device or gateway IP and port (502 by default)
- Unit ID: the slave address — irrelevant for a direct TCP connection to a single device, but critical when polling through a gateway that bridges to an RTU segment or multiple unit IDs on one TCP connection
- Response timeout: how long to wait before treating a request as failed — set generously for RTU/serial or a slow gateway, tighter for a fast local TCP device
- Reconnect / retry settings: how the client behaves after a connection drop — worth testing deliberately (see the testing section below) rather than trusting the defaults blindly
Read Nodes: modbus-read vs. modbus-flex-getter
The choice between these two nodes is one of the first real design decisions in a Modbus flow.
modbus-read polls a single, fixed address/quantity/function code on an interval configured inside the node itself. It's the right choice when you have a small, unchanging set of registers to poll — one node per logical read, wired to whatever processes the value downstream.
modbus-flex-getter instead takes its address, quantity, function code, and unit ID from the incoming msg.payload:
// Function node feeding a modbus-flex-getter, one message per register block
msg.payload = {
'fc': 3, // Read Holding Registers
'unitid': 1,
'address': 0,
'quantity': 10
};
return msg;
This makes one flex-getter node capable of servicing a whole list of reads driven by an upstream loop or a config array — the pattern to reach for once you're polling more than a handful of registers or multiple unit IDs, since managing a dozen separate modbus-read nodes gets unwieldy fast.
Write Nodes and Function Codes
Across the read and write nodes, the package supports function codes 1 (Read Coils), 2 (Read Discrete Inputs), 3 (Read Holding Registers), 4 (Read Input Registers), 5 (Write Single Coil), 6 (Write Single Register), 15 (Write Multiple Coils), and 16 (Write Multiple Registers). modbus-flex-fc additionally supports custom/vendor-specific function codes for devices that step outside the standard set.
// modbus-flex-write payload — write a setpoint to a single holding register
msg.payload = {
'fc': 6, // Write Single Register
'unitid': 1,
'address': 100,
'value': 750 // e.g. 75.0°C if the device scales x10
};
return msg;
Writes deserve more caution in testing than reads — a bad write can change a real process value. Always validate a new write flow against a simulator first, where a wrong value costs nothing.
Byte Order and 32-Bit Values
Modbus only defines 16-bit registers on the wire. A 32-bit float or integer is always two consecutive registers, and combining them correctly is one of the most common sources of silently wrong data in Node-RED Modbus flows. Use a buffer-parser node (or a function node with Buffer.readFloatBE/readFloatLE) downstream of the read, and check both byte order and word order against the device's documentation:
// Function node combining two big-endian holding registers into a 32-bit float
const buf = Buffer.alloc(4);
buf.writeUInt16BE(msg.payload.data[0], 0);
buf.writeUInt16BE(msg.payload.data[1], 2);
msg.payload = buf.readFloatBE(0);
return msg;
Most devices follow standard big-endian byte order for both bytes and words, but manufacturer firmware varies — some Schneider devices, for example, are documented as big-endian byte order with little-endian word order for 32-bit values. Don't assume; verify by reading a register you already know the correct decoded value for (a fixed firmware version number, a known setpoint) and confirm the parser produces the expected number before trusting it on live data.
Addressing: PLC Docs vs. Wire Addresses
PLC and device documentation traditionally uses 1-based, Modicon-style addressing — holding register 40001, coil 00001. The actual wire protocol, and the address field you enter in node-red-contrib-modbus nodes, is zero-based: 40001 in the manual is address 0 on the wire. If a read is consistently returning the value from one register over or under where you expect, this off-by-one is almost always the cause — subtract 1 from the documented register number before entering it in the node.
Running Node-RED as a Modbus Slave
The modbus-server node flips the usual direction: Node-RED itself becomes a buffer-backed Modbus TCP slave, exposing coils, discrete inputs, holding registers, and input registers that other masters can poll. This is a useful pattern for bridging data the other way — pulling values from an MQTT topic, a REST API, or a database and exposing them as Modbus registers for a legacy SCADA system or HMI that only speaks Modbus and can't be changed to consume anything else.
Common Errors and Fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
| modbus-response node shows "Timed out" | Device unreachable, wrong IP/port, or firewall blocking port 502 | Verify connectivity independently (e.g. telnet to the port) before assuming the flow logic is wrong |
| Read succeeds but every value looks scaled wrong (e.g. 10x too small) | Device applies a scaling factor in firmware that the flow isn't reapplying, or a decimal-point convention mismatch | Check the device's register map documentation for a scaling factor and apply it explicitly in a function node |
| Decoded float is wildly wrong or NaN | Byte order/word order mismatch, or reading the wrong number of registers for the data type | Confirm the value type (2 registers for a 32-bit float) and try both byte-order options against a known value |
| Requests pile up / queue grows (modbus-queue-info) | Polling interval shorter than the device's actual response time, especially on RTU/serial | Increase the polling interval or reduce the quantity read per request |
| Flow works locally but fails after deploy to a gateway/edge device | Network path or firewall differs between dev machine and the deployed environment | Re-verify the modbus-client's host/port reachability from the actual deployed device, not just your dev machine |
Testing a Flow Against a Simulator
Point the flow's modbus-client node at a Modbus TCP slave simulator instead of a real device while you're building and debugging:
Node-RED (modbus-client) → TCP 502 → Modbus Slave Simulator
[flex-getter / flex-write nodes] [ModbusSimulator on a bench machine]
Configure ModbusSimulator to run as a Modbus TCP slave on the port and unit ID your flow expects, and preload registers with distinctive, non-zero test values so you can immediately verify the flow is reading (and decoding) the right data. Then work through the cases that are hard to reproduce against a live PLC on demand:
- Byte order verification: set a register pair to a value with a known 32-bit decoded result and confirm your buffer-parser logic matches
- Addressing verification: set a distinctive value in each register of a block read and confirm each lands in the expected array position
- Exception handling: configure the flow to read an address the simulator doesn't have, and confirm it handles the resulting exception (typically Illegal Data Address) without crashing the flow
- Connection loss: stop the simulator mid-poll and confirm the modbus-client's reconnect/retry behavior does what you expect, then restart it and confirm polling resumes cleanly
- Write verification: write a test value through modbus-write/flex-write and read it back to confirm it landed correctly, before ever pointing the flow at a live setpoint
Running this checklist on a bench against a simulator, with the exact node configuration you intend to deploy, surfaces byte-order and addressing bugs while they're a five-minute fix — not after the flow is live and quietly logging wrong values. It's also the fastest way to validate polling interval choices: watch the modbus-queue-info node's output while increasing the read frequency against the simulator, and you'll see exactly where request backlog starts building before you ever find out the hard way against a device with a slower real-world response time.
If the flow will eventually run on an edge gateway rather than a development machine, do this testing on that same gateway hardware once the basic logic checks out on a workstation — CPU-constrained gateways can behave differently under load than a full-power dev laptop, and a polling interval that's fine on your desktop isn't automatically fine on the device it will actually run on in production.
Download ModbusSimulator to Test Your Node-RED Flow →
Frequently Asked Questions
What is the standard package for Modbus in Node-RED?
node-red-contrib-modbus (BiancoRoyal) is the de facto standard. It provides a modbus-client config node plus read nodes (modbus-read, modbus-getter, modbus-flex-getter), write nodes (modbus-write, modbus-flex-write), a Modbus TCP slave node (modbus-server), and diagnostics (modbus-response, modbus-queue-info). Install via the palette manager or npm.
What's the difference between modbus-read and modbus-flex-getter?
modbus-read polls a fixed address/quantity/function code on an internal timer. modbus-flex-getter takes those parameters from the incoming msg.payload instead, letting one node service a varying list of reads or multiple unit IDs from an upstream loop.
How do I handle byte order and 32-bit values in Node-RED Modbus flows?
Combine the two 16-bit registers with a buffer-parser node or Buffer.readFloatBE/LE, and verify both byte order and word order against a known value — manufacturer firmware varies, so never assume standard big-endian without checking.
Why does my Node-RED flow read the wrong register?
Usually the 1-based-vs-zero-based addressing offset — PLC documentation lists 40001-style addresses, but the wire protocol and the node's address field are zero-based, so 40001 is address 0. Subtract 1 from the documented register number.
How do I test a Node-RED Modbus flow without a real PLC?
Point the modbus-client node at a Modbus TCP slave simulator instead. Full control over register values and error conditions lets you validate byte order, addressing, and error handling before the flow ever touches a PLC.
Can node-red-contrib-modbus act as a Modbus TCP slave, not just a master?
Yes, via the modbus-server node, which exposes Node-RED itself as a buffer-backed Modbus TCP slave — useful for bridging data from MQTT, REST, or a database into Modbus registers for a legacy SCADA system.
What Modbus function codes does node-red-contrib-modbus support?
Function codes 1, 2, 3, 4 (reads), 5, 6, 15, 16 (writes) across the standard nodes, plus custom/vendor-specific function codes via modbus-flex-fc.
How many Modbus connections can one Node-RED instance manage?
As many as you define modbus-client nodes for — typically one per device or gateway, each with its own internal request queue. Watch modbus-queue-info if requests start backing up, usually a sign the polling interval is too aggressive.
Test Your Node-RED Modbus Flow Without Real Hardware
ModbusSimulator acts as a fully configurable Modbus TCP or RTU slave — preload registers, simulate multiple unit IDs, and force exception responses to validate your Node-RED flow's read/write and error handling before it touches a PLC. Free tier available.
Download Free tier → Learn More