Skip to Content

Introduction to Modbus and Raspberry PLC. Part 2

Write and read a coil over Modbus TCP in Node-RED with node-red-contrib-modbus
February 21, 2024 by
Introduction to Modbus and Raspberry PLC. Part 2
Boot & Work Corp. S.L, Bernat Brunet Pedra

Complete guide: Modbus RTU and TCP with Python and Node-RED

To write and read a coil over Modbus TCP from a Raspberry PLC in Node-RED, install the node-red-contrib-modbus nodes and create a Modbus-Client with Type TCP, the IP address of the slave and port 502. Then use a Modbus Write node with FC 5 and a Modbus Read node with FC 1. Give both nodes the same Unit-Id, the one that the device expects (often 1 or 255). The flow of this post was tested with Node-RED 4.1 and node-red-contrib-modbus 5 against a simulated slave.

This is part 2 of the Modbus and Raspberry PLC series. Parts 1 and 2 use Node-RED, and parts 3 and 4 use Python. All the details, such as timeouts, addressing, 32-bit values and troubleshooting, are in the Modbus guide for Raspberry Pi PLCs.

PartProtocolTool
Part 1Modbus RTUNode-RED
Part 2 (this post)Modbus TCPNode-RED
Part 3Modbus RTUPython
Part 4Modbus TCPPython

What you need

  • A Raspberry PLC with Node-RED. It is the Modbus TCP client (master). If Node-RED is not installed yet, see how to install Node-RED on a Raspberry PLC.
  • A Modbus TCP server (slave): a meter, a drive, another PLC or a gateway. To test without a device, run the Python Modbus TCP server of the Modbus guide on the same PLC and use the address 127.0.0.1 and port 5020.
  • An Ethernet connection between the PLC and the slave, with both in the same subnet. By default, eth0 of the Raspberry PLC has the address 10.10.10.20. To change it, see how to configure a static IP on a Raspberry PLC.
  • The IP address, the TCP port and the Unit-Id of the slave. This post uses a slave at 10.10.10.4, port 502, Unit-Id 1.

Start Node-RED and install the Modbus nodes

Start Node-RED in a terminal of the PLC:

node-red-start

Open the editor in a browser. From another computer, use http://PLC_IP_ADDRESS:1880. On the PLC itself, with a screen connected, use http://127.0.0.1:1880.

Open Menu, Manage palette (or press Alt+Shift+P), go to the Install tab, search for node-red-contrib-modbus and install it. Version 5.62 of these nodes needs Node-RED 4 or later and Node.js 22 or later, so check node -v first. If you did part 1, the nodes are already installed.

Configure the Modbus-Client for TCP

Drag a Modbus Write node to the flow, double click it, and click the pencil icon next to Server. This opens the Modbus-Client: the connection that all Modbus nodes of the flow share.

FieldValueNote
TypeTCPModbus TCP goes over Ethernet
Host10.10.10.4IP address of the slave
Port502The Modbus TCP port of most devices. Use 5020 for the Python test server of the guide
TCP TypeDEFAULT
Unit-Id1Default Unit-Id for the nodes that leave their own Unit-Id empty

Give the configuration a name and click Update. For each slave with a different IP address, create one more Modbus-Client.

Unit-Id in Modbus TCP

On Modbus TCP, the IP address selects the device. The Unit-Id still travels in the header of every request, and it selects a device behind a gateway, for example an RTU slave behind a Modbus TCP to RTU gateway. Many devices ignore it, and others answer only to 1 or to 255. Use the value in the manual of the device, and the same value in all the nodes.

The first version of this flow had Unit-Id 1 in Modbus Write and 31 in Modbus Read. Against our simulated slave, which checks the Unit-Id, the write worked, the read got an exception response, and the Debug panel showed nothing. The flow of this post uses Unit-Id 1 in both nodes.

Write a coil with Modbus Write (FC 5)

Configure the Modbus Write node:

  • Unit-Id: 1.
  • FC: FC 5: Force Single Coil.
  • Address: 0. Coil 00001 in the manual of the device is address 0.
  • Quantity: 1.
  • Server: the Modbus-Client of the previous step.

Add an Inject node, set msg.payload to the boolean true and connect it to Modbus Write. Add a second Inject node with the boolean false. Each click on an Inject node sends one write request over Ethernet: true turns the coil on and false turns it off.

Node-RED flow: an Inject node with the value true connected to a Modbus Write node with the status active
An Inject node with the value true connected to Modbus Write.

Read the coil with Modbus Read (FC 1)

Add a Modbus Read node with the same Modbus-Client:

  • Unit-Id: 1, the same as in Modbus Write.
  • FC: FC 1: Read Coil Status.
  • Address: 0. Quantity: 1.
  • Poll Rate: 1 second.

Connect a Debug node to the first output of Modbus Read, which gives the data array, and click Deploy. The Debug panel shows a new message every second. The array has 8 values, because the answer carries the coils in whole bytes. The first value is coil 0.

In our test, after a click on the true Inject node, the next reads showed an array that starts with true, and after a click on false, an array that starts with false.

Complete Node-RED flow: Inject nodes true and false connected to Modbus Write, and Modbus Read connected to a debug node
The complete flow: two Inject nodes (true and false) feed Modbus Write, and Modbus Read polls the coil every second into a Debug node. The flow is the same as in part 1, only the Modbus-Client changes.

Import the complete flow

To skip the manual steps, open Menu, Import, paste this flow and click Import. Then change the IP address, the port and the Unit-Id to the values of your slave.

[{"id":"3c784f724b6bb9da","type":"modbus-write","z":"66f823dbe170e15a","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"unitid":"1","dataType":"Coil","adr":"0","quantity":"1","server":"a0cecbb6e2cd25e8","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":560,"y":220,"wires":[[],[]]},{"id":"3459adaa212c8dd3","type":"inject","z":"66f823dbe170e15a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":390,"y":200,"wires":[["3c784f724b6bb9da"]]},{"id":"526661ccfde56f87","type":"inject","z":"66f823dbe170e15a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":390,"y":240,"wires":[["3c784f724b6bb9da"]]},{"id":"f5c9e191b12f95d7","type":"modbus-read","z":"66f823dbe170e15a","name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"Coil","adr":"0","quantity":"1","rate":"1","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"a0cecbb6e2cd25e8","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":430,"y":320,"wires":[["3ff9cb94cb91b7e8"],[]]},{"id":"3ff9cb94cb91b7e8","type":"debug","z":"66f823dbe170e15a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":320,"wires":[]},{"id":"a0cecbb6e2cd25e8","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"10.10.10.4","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"0x3A","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true,"showWarnings":true,"showLogs":true}]

When the slave does not answer

SymptomWhat to check
Connection refusedThe server does not run, the port is wrong or a firewall blocks it
No connection, timeoutIP address of the slave, cable, subnet. Two new Raspberry PLCs on one network both have 10.10.10.20 on eth0: change one of them. Check with ip a and ping
The write works but Debug shows nothingThe Unit-Id of Modbus Read. Use the same Unit-Id as in Modbus Write, the one of the device manual
Exception code 02 (illegal data address)The coil address does not exist in the slave. Check the coil table in the manual of the slave
Exception code 0B (gateway target device failed to respond)A Modbus TCP to RTU gateway got no answer from the RTU slave with that Unit-Id

To see errors in the Debug panel, enable Show Errors in the Modbus Read and Modbus Write nodes.

What we tested. Node-RED 4.1.17 on Node.js 22 (Linux) with node-red-contrib-modbus 5.62.1 and 5.23.3. Version 5.62.1 ran with the modbus-serial 8.0.25 library from npm in place of its own fork of that library. The flow of this post, with only the host and the port changed to 127.0.0.1 and a free port, ran against a simulated Modbus TCP slave (pymodbus 3.16.1, Unit-Id 1). The true and false Inject nodes turned coil 0 on and off, and Modbus Read showed the new value within one second. The first version of the flow, with Unit-Id 31 in Modbus Read, gave no data in Debug against the same slave. We did not run this update on Raspberry PLC hardware: the two screenshots come from the first version of the post.

Find the fault in a Modbus RTU and TCP network. In the simulator, an M-Duino 21 polls four Modbus RTU slaves on RS-485 and serves the data on Modbus TCP. Switch a slave off or change the baud rate, and find the fault in the frames.

Open the Modbus network simulatorOpen the complete Modbus guide

FAQ about Modbus TCP in Node-RED

Do I need the Unit-Id with Modbus TCP?

The IP address selects the device, but the Unit-Id is still in every request. Many devices ignore it, some answer only to 1 or 255, and a gateway uses it to select the RTU slave behind it. Use the value of the device manual in all the nodes.

Which TCP port does Modbus TCP use?

Port 502 is the standard. Some devices let you change it, and test servers on Linux often use 5020, because only root can open ports under 1024.

Can several clients read the same Modbus TCP slave?

Yes, unlike Modbus RTU, where there is one master per line. A SCADA system and the Node-RED flow can read the same server at the same time, if the device accepts several connections.

Why does the Debug panel show 8 values for one coil?

Because the Modbus answer carries the coils in whole bytes, and the node gives all the bits of those bytes. Use only the first value.

How do I do the same in Python?

With ModbusTcpClient of pymodbus. See part 4: Modbus TCP and Python.

​Search in our Blog

Introduction to Modbus and Raspberry PLC. Part 2
Boot & Work Corp. S.L, Bernat Brunet Pedra February 21, 2024
Share this post

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.

PLC Comparison