A better way to Modbus?

I'm trying to communicate with 12 different devices over modbus TCP. I have an ethernet connection to each device and I'm running Node Red on a Raspberry Pi which is also hardwired to the switch.

I've managed to get it to communicate with 1 or maybe 2 devices at a time, but eventually the modbus communications just stop all together.

I'm currently using node-red-contrib-modbus-tcp-ip as it was the only module I could find that would allow me to modify the IP address with code when sending the connection information to the node.

Is there a smarter way to handle the flow? I suspect that having so many of the modbus TCP nodes is the issue. This particular node opens and closes the socket connection for every communication attempt. I'm not sure if there is anyway to utilize less of the modbus nodes and still achieve the same results. Unfortunately, the modbus registers are not close enough in the maps to just grab 200 registers in one go. The one device in particular has over 20,000 registers and the ones I need aren't even remotely close.

Can anyone see anything glaringly wrong with the way I'm handling this?

flows (3).json (50.9 KB)

Hello

I don't know if this will help you, but this is what I'm using for pooling data from my Modbus devices over RS485. I only have couple devices so creating an array with parameters is acceptable. For your application further improvement will be needed.

// arr is a list of the devices, addresses and length of data 
//msg.payload = { 'unitid': 1,'fc': 3,'address': 0 , 'quantity': 1 } return msg
var arr = [["1","3","0","1"],["1","3","1","1"],["1","3","2","1"],["1","3","3","1"],["1","3","4","1"],["1","3","5","1"]]
var j = 1;                  //  set your counter to 1
function myLoop() {         //  create a loop function
  setTimeout(function() {   //  call a setTimeout when the loop is called
    let unitid = arr[j-1][0];
    let fc = arr[j-1][1];
    let address = arr[j-1][2];
    let quantity = arr[j-1][3];
    msg.payload = {unitid:unitid,fc:fc,address:address,quantity:quantity};
    node.send(msg);
    j++;                    //  increment the counter
    if (j <= arr.length) {           //  if the counter < than arr.length, call the loop function
      myLoop();             //  ..  again which will trigger another loop 
    }                       //  ..  setTimeout()
  }, 100) //execute every xxx miliseconds 
}

myLoop();                   //  start the loop

after Modbus node you will need another function to pharse readings.

There is also queue delay option in modbus settings, which may help:

I remember there was something on here recently about size of Modbus reads and over running some buffers - do some searching on the forum - i would not have thought that 12 devices would have been an issue though

There were some others on here recently discussing accessing many more than that i feel

I think @Steve-Mcl is the resident guru on Modbus as he seems to have most of the answers when the going gets tough - i am still just in the playing around stage

Craig

My modbus TCP know how is fairly small, since I have just one device connected.
What I have more experience with is in the networking layer.

For many manufacturers modbus-TCP is just an afterthought, an added goodie to their device. Therefore a number of details are purely developed.
One topic to have a close look in the manual is the speed with which you can fetch data from the device.
Another topic is the opening and closing of TCP sessions. In some devices it takes time to open the TCP session, and in some devices the closing is purely implemented.

In your flow you loop through your devices changing the IP address. Depending on the quality of the implementation of your modbus devices it might be better to hard-wire everything, keep the connections open and stable.

Just a few thoughts to look at.
Urs.

@Urs-Eppenberger

What do you mean by hard wiring the devices? Are you referring to serial modbus? The controllers hooked to this Pi are changed out on a regular basis, so hard wiring a serial connection to each is out of the question. This needs to be seamless for our guys in quality control who are testing our equipment prior to shipment. They already hook all units to the LAN so to prevent making more work for them I need to work with what I already have.

@krzydec

I'll have to look a little closer into this code snippet you posted. Unfortunately I don't know how well it'll work for me given that sometimes the units are hooked up, other times there aren't. I will have to add something to prevent it from trying to send modbus requests when there is no unit present.

Is there an easy way to ping a device through node red to confirm a connection?

Also, I am using a different modbus module than you. Unfortunately the one you show there does not allow me to inject the IP address for the device which makes replicating the code to multiple units painful.

I tried to use a node.js modbus-tcp-ip package, however I'm struggling with that too. Every time I try to run the function it fails and shuts down node-red.

I was trying to follow this here, but it doesn't seem to work for me. EXAMPLE - using an external javascript in a function node (flow) - Node-RED

1 Jul 08:36:00 [red] Uncaught Exception:
1 Jul 08:36:00 Error [ERR_SOCKET_CLOSED]: Socket is closed at Socket._writeGeneric (net.js:777:8)
at Socket._write (net.js:799:8)
at writeOrBuffer (internal/streams/writable.js:358:12)
at Socket.Writable.write (internal/streams/writable.js: 303:10) at sendTCP (/home/pi/.node-red/node_modules/modbus-tcp-ip/script.js:27:20)
at Object.Modbus Tcp.rtn.read (/home/pi/.node-red/node_modules/modbus-tcp-ip/script.js:90:9)
at Function node: 45b79546.c60dbc:7:26
at Function node: 45b79546.c60dbc:19:3 at Script.runInContext (vm.js:144:12)
at processMessage (/usr/lib/node_modules/node-red/node_modules/@node-red/nodes/core/function/10-function.js: 386:29) nodered.service: Main process exited, code=exited, status=1/FAILURE
nodered.service: Failed with result 'exit-code'.

node-red-node-ping can be installed from manage palette.

Ah I see, you have a test-rig with changing modbus devices.
With hard-wiring I meant the wires of node-red. But this does not help in your use case.

If you do know the IP addresses of your devices, then you can certainly use the ping node.
I use nmap to figure out what devices are connected on my network with the following command (as root, via crontab):

nmap -sn --oX /tmp/nmap.xml --privileged --webxml 192.168.11.0/24

And then I run the nmap.xml file through the xml node and figure out afterwards which MAC addresses are new and the corresponding IP addresses.

@Urs-Eppenberger

I appreciate the responses. I think I may have made it a little more robust at this point. I added the ping in and used a routing node to cut down on how many different modbus nodes I use. This still has the issue of constantly opening and closing the connection. I need to try to scale it up again and see if I run into the same problems as before.

The IP addresses are static, there are 12 stations each with a separate IP assigned starting at 10.10.1.1 up to 10.10.1.12.

For those interested, please see the flow I am using now. So far I have 3 devices connected and this is still working. Not seeing any of the issues I had when I had 14 modbus nodes per flow.

@dleberfinger

Could you share that flow - I think it could be more compact & maintainable (think being the operative word)

To share the flow, select all the nodes in that picture, press CTRL+E , copy to clipcoard, paste into a reply like this

```
[copied flow pasted between backticks like this]
```

Based on your first flow posted, I generated this somewhat simplified flow...

[{"id":"12f772e9.2dbc7d","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":16,"width":3,"height":3,"gtype":"gage","title":"CH1 SP","label":"°C","format":"{{value}}","min":"-75","max":"205","colors":["#000cb3","#e6e600","#ca3838"],"seg1":"25","seg2":"25","className":"","x":1640,"y":100,"wires":[]},{"id":"b6477711.d78288","type":"ui_chart","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":17,"width":25,"height":9,"label":"CH1 PV & SP","chartType":"line","legend":"true","xformat":"HH:mm:ss","interpolate":"linear","nodata":"Please wait...","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"className":"","x":1660,"y":140,"wires":[[]]},{"id":"cd0fe956.b569f8","type":"inject","z":"79482abb.5adbc4","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":130,"y":220,"wires":[["6f7d19.ae5762e8"]]},{"id":"6f7d19.ae5762e8","type":"function","z":"79482abb.5adbc4","name":"send ModbusGetter Messages","func":"const controller = flow.get(\"controller\") \nconst ip = flow.get(\"ip\")\n\nif (controller == 1) {\n    sendModbusGetterMsg(\"CH1 SP\", ip, 198, 2811);\n    sendModbusGetterMsg(\"CH1 PV\", ip, 198, 2821);\n    sendModbusGetterMsg(\"CH1 PID\", ip, 198, 2809);\n    sendModbusGetterMsg(\"CH2 SP\", ip, 198, 2971);\n    sendModbusGetterMsg(\"CH2 PV\", ip, 198, 2981);\n    sendModbusGetterMsg(\"CH2 PID\", ip, 198, 2969);\n} else if (controller == 2) {\n    sendModbusGetterMsg(\"CH1 SP\", ip, 1, 40101);\n    sendModbusGetterMsg(\"CH1 PV\", ip, 1, 40104);\n    sendModbusGetterMsg(\"CH1 PID\", ip, 1, 30144);\n    sendModbusGetterMsg(\"CH2 SP\", ip, 1, 40201);\n    sendModbusGetterMsg(\"CH2 PV\", ip, 1, 40204);\n    sendModbusGetterMsg(\"CH2 PID\", ip, 1, 30244);\n} else if (controller == 3) {\n    sendModbusGetterMsg(\"CH1 SP\", ip, 2, 300);\n    sendModbusGetterMsg(\"CH1 PV\", ip, 2, 302);\n    sendModbusGetterMsg(\"CH1 PID\", ip, 2, 304);\n    sendModbusGetterMsg(\"CH2 SP\", ip, 2, 309);\n    sendModbusGetterMsg(\"CH2 PV\", ip, 2, 311);\n    sendModbusGetterMsg(\"CH2 PID\", ip, 2, 313);\n} else {\n    node.warn(`controller '${controller}' not valid.`);\n    return null;\n}\n\nfunction sendModbusGetterMsg(topic, ip, unit, addr) {\n    const m = {\n        topic: topic,\n        payload: {\n            \"modbus_ip\": ip,\n            \"unitid\": unit,\n            \"address\": addr,\n            \"functioncode\": 3,\n            \"quantity\": 2\n        }\n    };\n    node.send(m);\n}\n\n/*\ncontroller | unit | CH1 SP   | CH1 PV   | CH1 PID  | CH2 SP   | CH2 PV   | CH2 PID  |\n-----------|------|----------|----------|----------|----------|----------|----------|\n1          | 198  | 2811     | 2821     | 2809     | 2971     | 2981     | 2969     |\n2          | 1    | 40101    | 40104    | 30144    | 40201    | 40204    | 30244    |\n3          | 2    | 300      | 302      | 304      | 309      | 311      | 313      |\n*/\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":220,"wires":[["0a2b0250f26a9462"]]},{"id":"3321436c.0fedec","type":"buffer-parser","z":"79482abb.5adbc4","name":"","data":"responseBuffer.buffer","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"floatbe","name":"value","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":1050,"y":220,"wires":[["bfa23f8c.2405a"]]},{"id":"bfa23f8c.2405a","type":"function","z":"79482abb.5adbc4","name":"","func":"\nconst flowvar = msg.topic.replace(\" \", \"\");\n\nvar i = msg.payload.value;\n\nmsg.payload = parseFloat(i).toFixed(1)\n\nflow.set(flowvar, msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1220,"y":220,"wires":[["849190b691624592"]]},{"id":"a4072613.7124c8","type":"modbus-tcp-ip","z":"79482abb.5adbc4","name":"Modbus-TCP Flexi Getter","ip":"","port":"10502","error":true,"x":830,"y":220,"wires":[["3321436c.0fedec"]]},{"id":"0a2b0250f26a9462","type":"delay","z":"79482abb.5adbc4","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"x":600,"y":220,"wires":[["a4072613.7124c8"]]},{"id":"849190b691624592","type":"switch","z":"79482abb.5adbc4","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"CH1 SP","vt":"str"},{"t":"eq","v":"CH1 PV","vt":"str"},{"t":"eq","v":"CH1 PID","vt":"str"},{"t":"eq","v":"CH2 SP","vt":"str"},{"t":"eq","v":"CH2 PV","vt":"str"},{"t":"eq","v":"CH2 PID","vt":"str"}],"checkall":"true","repair":false,"outputs":6,"x":1370,"y":220,"wires":[["12f772e9.2dbc7d"],["b6477711.d78288","53e1130a54788a87"],["7f24adaf81426bb6"],["76b0dcd2bde00df0","850d14e4b44ed963"],["850d14e4b44ed963","99da0eb685f22a41"],["4046ba139c8cd2ad"]]},{"id":"53e1130a54788a87","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":18,"width":3,"height":3,"gtype":"gage","title":"CH1 PV","label":"°C","format":"{{value}}","min":"-75","max":"205","colors":["#0c00b3","#e6e600","#ca3838"],"seg1":"25","seg2":"25","className":"","x":1640,"y":180,"wires":[]},{"id":"7f24adaf81426bb6","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":19,"width":3,"height":3,"gtype":"gage","title":"CH1 PID Output","label":"%","format":"{{value}}","min":"-100","max":"100","colors":["#002aff","#e6e600","#ff0000"],"seg1":"0","seg2":"0","x":1660,"y":220,"wires":[]},{"id":"76b0dcd2bde00df0","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":20,"width":3,"height":3,"gtype":"gage","title":"CH2 SP","label":"% RH","format":"{{value}}","min":"0","max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":1640,"y":260,"wires":[]},{"id":"850d14e4b44ed963","type":"ui_chart","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":21,"width":25,"height":9,"label":"CH2 PV & SP","chartType":"line","legend":"true","xformat":"HH:mm:ss","interpolate":"linear","nodata":"Please wait...","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1660,"y":300,"wires":[[]]},{"id":"99da0eb685f22a41","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":22,"width":3,"height":3,"gtype":"gage","title":"CH2 PV","label":"% RH","format":"{{value}}","min":"0","max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":1640,"y":340,"wires":[]},{"id":"4046ba139c8cd2ad","type":"ui_gauge","z":"79482abb.5adbc4","name":"","group":"792610eb.9e99f","order":23,"width":3,"height":3,"gtype":"gage","title":"CH2 PID Output","label":"%","format":"{{value}}","min":"-100","max":"100","colors":["#1500b3","#e6e600","#ff0000"],"seg1":"0","seg2":"0","x":1660,"y":380,"wires":[]},{"id":"792610eb.9e99f","type":"ui_group","name":"10.10.1.1","tab":"233d7cdb.cb6c34","order":1,"disp":true,"width":28,"collapse":false},{"id":"233d7cdb.cb6c34","type":"ui_tab","name":"10.10.1.1","icon":"dashboard","order":2,"disabled":false,"hidden":false}]
1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.