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:

NodeRole
modbus-clientShared connection config (TCP or serial) — every other node references one of these
modbus-readPolls a fixed address/quantity/function code on a timer
modbus-getterOn-demand single read, triggered by an input message
modbus-flex-getterRead where address/quantity/function code/unit ID come from msg.payload
modbus-write / modbus-flex-writeFixed or payload-driven writes
modbus-serverRuns Node-RED itself as a Modbus TCP slave
modbus-queue-infoReports 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

SymptomLikely CauseFix
modbus-response node shows "Timed out"Device unreachable, wrong IP/port, or firewall blocking port 502Verify 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 mismatchCheck the device's register map documentation for a scaling factor and apply it explicitly in a function node
Decoded float is wildly wrong or NaNByte order/word order mismatch, or reading the wrong number of registers for the data typeConfirm 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/serialIncrease the polling interval or reduce the quantity read per request
Flow works locally but fails after deploy to a gateway/edge deviceNetwork path or firewall differs between dev machine and the deployed environmentRe-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