First project. Is Node-red the way to go

Hello friends.

I wanted to work on my own private project. Through my research i came to the conclusio that node red could be the tool to sucsess. My question is more like a „can it be done and is node red the right tool“ kinda question. So dont be to hard to me if i ask stupid questions. I try to
scetch my project in a simple way:

I have a dosing pump at my pool. I always wanted to make it readable and controlable via a webserver. For this, the dosing-pump producer has a module for the pump to plug in. This module „uses“ modbus tcp.

My plan: create via node red (if possible) a flow to read the modbus lines (registers) that i need, and push them as a json file on my webserver. Visa versa to start my pump via webserver.

How i will do it is another question:). My question is more if i am on the right track and if so, if someone can draw a rough „togo“ path how i can achieve that.

Thank you and best regards

Many people are using modbus with Node-RED and as long as you can get and understand the data you need and the module supports the control you need then Node-RED will help you achieve your project with minimal coding.

@TotallyInformation has a good point. You can, but do not look for someone to code it for you.
I'll take a stab at your rough "togo" path. I'm not sure what that is though. Lets start with the basics and pardon my assumptions.

  1. Javascript, programming concepts, and programming experience is a must. Familiarize yourself with Node-RED and what it does and how it works. Simple programs and features you want.
  2. Modbus tcp understanding a must. Find documentation on this from your pool system etc.
  3. Interface Node-RED and your Modbus tcp system with the existing nodes for Modbus TCP.
  4. Establish the basic Node-RED <-> Pool Modbus tcp. Send commands/return data on a single point/bit/item. I won't pretend to know anything other than there must be commuincation you need to test and verify it worksl
  5. Build on the baisics Add to your program how you intend to control the pool pump. Add to your program how the pool pump will give you data to Node-RED.
  6. Once you've established functional control and communication, you can build a program to execute these simple concepts.
  7. Use the editor and the tools in the editor, You'll need to get familiar with Node-RED on basic programming and interface concepts.
  8. Once you've got that and can monitor your program, then you need to build a web portal.
  9. Familiarize yourself with how to build a page that will give you what you want.
  10. Design an interface, find out what you want it to do. What do you expect from it.
  11. Connect the parts of the interface to your program elements. Display data and data inputs and how you'll send the values/parameters from your function to your pool pump control.

Good luck.

Yeah - as Julian has said - easy to do.

The question is more do you need to go to Modbus to get this done ?

I have a dosing pump for my pool - it has a simple on/off function that i control through a smart plug - when the pump is running and the PH level in my pool is too high - i run the acid doser and dribble a small amount of acid into the water stream

Most of these dosing pumps are the same they are a Peristaltic pump - usually with a calinbration function somewhere on the unit - to enable a certain level of dosing as required (i.e. how much acid will be dispensed with each rotation of the motor). They work best with small doses per cycle and run frequently so you can check on the level.

I have a floating monitor in my pool that reports back as to the PH level (and other information) so i can adjust the run time of the dosing pump.

How are you getting the information as to the PH and/or ORP levels etc in your pool - is this reported to you by the main control unit (which is what you would then want to talk Modbus to ?)

Craig

Thank you for all the answers.

I am already at the learning curve and will open an own thread after a while about the progress of my project. many things still unclear and much to learn=)

One short question already. i see in the docu of my modbus modul, that the values i want to read sharing one adresse: 204.0 (dosing-status), 204.1 (warning), 204.2 (fault).
Now i am a bit confused. How can i read an adress that is splitted into "subadress"?

Thank you

node-red-contrib-buffer-parser will do this for you.

Check the built in examples (CTRL-I --> examples --> node-red-contrib-buffer-parser)

Read the read me

and read the built in node-help inside node-red.

Also, there are a ton of posts on the forum (search buffer parser)

Small demo...

image

Flow (use CTRL+I to import)

[{"id":"21ba1caf6c9f205b","type":"inject","z":"7eecf5f1d763605a","name":"Faking ModBus registers 200 ~ 204","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[39321,0,65535,30583,43690]","payloadType":"json","x":2092,"y":736,"wires":[["77145b630e3feb9d"]]},{"id":"77145b630e3feb9d","type":"buffer-parser","z":"7eecf5f1d763605a","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"bool","name":"H200_00","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"bool","name":"H201_15","offset":2,"length":1,"offsetbit":15,"scale":"1","mask":""},{"type":"bool","name":"H204_00_dosing_status","offset":8,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"bool","name":"H204_01_warning","offset":8,"length":1,"offsetbit":1,"scale":"1","mask":""},{"type":"bool","name":"H204_02_fault","offset":8,"length":1,"offsetbit":2,"scale":"1","mask":""},{"type":"bool","name":"H204_15_something_else","offset":8,"length":1,"offsetbit":15,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"return","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":2018,"y":784,"wires":[["8fdf1e0704b32e91"]]},{"id":"8fdf1e0704b32e91","type":"debug","z":"7eecf5f1d763605a","name":"payload","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":2180,"y":784,"wires":[]}]

Thank you for the help.

I try to get this "split" of the reg.adress 204 to run but with no sucsess.

So as far as i understood. In the adress.reg 204 there are multiple infotmations. So i need this bufferparser to "split" this adress with a bit-offset of 1. (for 204.0 , 204.1 , 204.2 and so on). right until here?

I tried the following:

i get this result:

"RangeError: The value of "offset" is out of range. It must be >= 0 and <= 1. Received 8"
msg : error
"RangeError: The value of "offset" is out of range. It must be >= 0 and <= 1. Received 8"

thank you

As you havent shared the flow I suspect you are only requesting 1 register (1 register == 1 WORD == 2 bytes) BUT you have an offset of 8 set in the buffer parser.

The demo I gave you simulated reading 5 registers (5 registers = 5 WORDS = 10 bytes) and therefore a byte offset of 8 is fine.

Go back to my demo and see how it fits together.

Sorry to bother you, but i still dont get it.

I try to read this adresses:

I still stuck at this: Here is my flow

[
    {
        "id": "0c822967e78b69a8",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "358b6aa83ce6ea0d",
        "type": "inject",
        "z": "0c822967e78b69a8",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "1",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "str",
        "x": 130,
        "y": 260,
        "wires": [
            [
                "e230adbc9ea862c4"
            ]
        ]
    },
    {
        "id": "e230adbc9ea862c4",
        "type": "function",
        "z": "0c822967e78b69a8",
        "name": "",
        "func": "msg.payload = \n{ value: msg.payload, \n'fc': 3, \n'unitid': 1, \n'address': 203, \n'quantity': 5 \n};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 340,
        "y": 260,
        "wires": [
            [
                "5b2aeaf54161d66b"
            ]
        ]
    },
    {
        "id": "5b2aeaf54161d66b",
        "type": "modbus-flex-getter",
        "z": "0c822967e78b69a8",
        "name": "",
        "showStatusActivities": false,
        "showErrors": false,
        "logIOActivities": false,
        "server": "0e3ec676f3e39293",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "x": 580,
        "y": 260,
        "wires": [
            [
                "6c224f852a5edb5c",
                "407924f80597fb2a"
            ],
            []
        ]
    },
    {
        "id": "6c224f852a5edb5c",
        "type": "modbus-response",
        "z": "0c822967e78b69a8",
        "name": "",
        "registerShowMax": 20,
        "x": 950,
        "y": 100,
        "wires": []
    },
    {
        "id": "407924f80597fb2a",
        "type": "buffer-parser",
        "z": "0c822967e78b69a8",
        "name": "",
        "data": "payload",
        "dataType": "msg",
        "specification": "spec",
        "specificationType": "ui",
        "items": [
            {
                "type": "bool",
                "name": "R203_0_Dosing",
                "offset": 0,
                "length": 1,
                "offsetbit": 0,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "R203_1_Warning",
                "offset": 2,
                "length": 1,
                "offsetbit": 15,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "R203_2_Fault",
                "offset": 8,
                "length": 1,
                "offsetbit": 0,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "R203_3_BUSCONTROLL",
                "offset": 8,
                "length": 1,
                "offsetbit": 1,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "H204_Start/Stop",
                "offset": 8,
                "length": 1,
                "offsetbit": 2,
                "scale": "1",
                "mask": ""
            }
        ],
        "swap1": "",
        "swap2": "",
        "swap3": "",
        "swap1Type": "swap",
        "swap2Type": "swap",
        "swap3Type": "swap",
        "msgProperty": "payload",
        "msgPropertyType": "str",
        "resultType": "keyvalue",
        "resultTypeType": "return",
        "multipleResult": false,
        "fanOutMultipleResult": false,
        "setTopic": true,
        "outputs": 1,
        "x": 790,
        "y": 480,
        "wires": [
            [
                "1166e304f40ba8b8"
            ]
        ]
    },
    {
        "id": "1166e304f40ba8b8",
        "type": "debug",
        "z": "0c822967e78b69a8",
        "name": "payload",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 1080,
        "y": 360,
        "wires": []
    },
    {
        "id": "0e3ec676f3e39293",
        "type": "modbus-client",
        "name": "CIM500",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "tcpHost": "10.240.6.36",
        "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
    }
]

Noted. i Think the register in the docu is false. So instead of 204 is adress 203

What i found out through trial and error: the adress 203 (in the docu listed at 204) can get following numbers: 8,9,10,11,12. Is it possible to work the parse buffer following: if 8 = pump off , if 9 = pump on, if 10 = pump off with warning, if 11 = pump on with warning and so on.

sorry for my stupid questions

Thank you

@Manuel_o

As @hp_apcc already has said:

You have hit something, that is sometimes called Zero vs. One Based Numbering.

Modbus register addressing can be confusing because of a Modbus specification and a common convention. I will try to explain this and will also provide an example.

The Modbus specification says: “Registers are addressed starting at zero. Therefore input registers numbered 1-16 are addressed as 0-15.” Unfortunately, that means a register documented with an address of 204 must be requested by sending an address of 203 (CB hex). Most application software will automatically handle this subtraction, but if you are writing your own code or watching raw packet bytes, you need to be aware of this offset by one.

It is a common convention (but not mentioned in the specifications) to describe a register address with a leading digit to indicate the Modbus function code required to access the register. (sometimes called Function Code Prefix)

This results in a format like the following: XNNNN, where X” is one of the following Modbus function codes:

1 – Read coils
2 – Read discrete inputs
3 – Read holding registers
4 – Read input registers

And where “NNNN” is the register number and may range from 1 to 9999 (these will become 0 to 9998 when converted to Modbus register addresses).

Example:

If you see a Modbus register number 40204, this is really register number 204 and should be accessed using the Modbus function 04 “Read Input Registers” and requested by sending an address of 203

This information I collected some years ago and the original has been found at Modbus Register Addressing - Continental Control Systems, LLC

Perhaps this information will help you, but I do not think your documentation is "false"

Regards

Hello friends.

I was still not able to solve this:

In the documentations stands this for the adress 201:

I know how to read values from adresses. But i dont know how to "split" one adress in 5 subadresses
For example. When i read the adress 201 i get the result "5" back

Can anyone help

Thank you very much

[{"id":"17ed74bbe681fead","type":"inject","z":"7eecf5f1d763605a","name":"PLC Data: Addresses 200, 201 and 202","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[65535,5,0]","payloadType":"json","x":2038,"y":240,"wires":[["088b339a2e71f02c","1dea874c919046cc"]]},{"id":"088b339a2e71f02c","type":"buffer-parser","z":"7eecf5f1d763605a","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16be","name":"Addr200_Value","offset":0,"length":1,"offsetbit":1,"scale":"1","mask":""},{"type":"16bitbe","name":"Addr200_Bits","offset":0,"length":1,"offsetbit":1,"scale":"1","mask":""},{"type":"uint16be","name":"Addr201_Value","offset":2,"length":1,"offsetbit":1,"scale":"1","mask":""},{"type":"16bitbe","name":"Addr201_Bits","offset":2,"length":1,"offsetbit":5,"scale":"1","mask":""},{"type":"bool","name":"ActRemoteAccess","offset":3,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"bool","name":"ActDeariating","offset":3,"length":1,"offsetbit":1,"scale":"1","mask":""},{"type":"bool","name":"ActAnalogMode","offset":3,"length":1,"offsetbit":2,"scale":"1","mask":""},{"type":"bool","name":"ActTimerMode","offset":3,"length":1,"offsetbit":3,"scale":"1","mask":""},{"type":"bool","name":"ActSlowMode","offset":3,"length":1,"offsetbit":4,"scale":"1","mask":""},{"type":"uint16be","name":"Addr202_Value","offset":4,"length":1,"offsetbit":5,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"return","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":1942,"y":304,"wires":[["3dcd28ba2cd1f97d"]]},{"id":"3dcd28ba2cd1f97d","type":"debug","z":"7eecf5f1d763605a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2118,"y":304,"wires":[]},{"id":"1dea874c919046cc","type":"debug","z":"7eecf5f1d763605a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2118,"y":192,"wires":[]}]

thank you for the answer. sorry but i still dont get it.

Here is my code:

[
    {
        "id": "d791173bb75da01b",
        "type": "function",
        "z": "0c822967e78b69a8",
        "name": "ADR_25/FC_3/TX",
        "func": "msg.payload = \n{ value: msg.payload, \n'fc': 3, \n'unitid': 0, \n'address': 200, \n'quantity': 6 \n};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 270,
        "y": 1660,
        "wires": [
            [
                "615b01036abec05d"
            ]
        ]
    },
    {
        "id": "615b01036abec05d",
        "type": "modbus-flex-getter",
        "z": "0c822967e78b69a8",
        "name": "",
        "showStatusActivities": false,
        "showErrors": false,
        "logIOActivities": false,
        "server": "0e3ec676f3e39293",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "x": 520,
        "y": 1660,
        "wires": [
            [
                "87d7f4913008d92f",
                "2938186e108b95b5"
            ],
            []
        ]
    },
    {
        "id": "87d7f4913008d92f",
        "type": "modbus-response",
        "z": "0c822967e78b69a8",
        "name": "",
        "registerShowMax": 20,
        "x": 510,
        "y": 1740,
        "wires": []
    },
    {
        "id": "3dcd28ba2cd1f97d",
        "type": "debug",
        "z": "0c822967e78b69a8",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 950,
        "y": 1660,
        "wires": []
    },
    {
        "id": "85701535883a75ef",
        "type": "inject",
        "z": "0c822967e78b69a8",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 240,
        "y": 1520,
        "wires": [
            [
                "d791173bb75da01b"
            ]
        ]
    },
    {
        "id": "2938186e108b95b5",
        "type": "buffer-parser",
        "z": "0c822967e78b69a8",
        "name": "",
        "data": "payload",
        "dataType": "msg",
        "specification": "spec",
        "specificationType": "ui",
        "items": [
            {
                "type": "uint16be",
                "name": "Addr201_Value",
                "offset": 0,
                "length": 1,
                "offsetbit": 1,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "16bitbe",
                "name": "Addr201_Bits",
                "offset": 0,
                "length": 1,
                "offsetbit": 5,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActRemoteAccess",
                "offset": 3,
                "length": 1,
                "offsetbit": 0,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActDeariating",
                "offset": 3,
                "length": 1,
                "offsetbit": 1,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActAnalogMode",
                "offset": 3,
                "length": 1,
                "offsetbit": 2,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActTimerMode",
                "offset": 3,
                "length": 1,
                "offsetbit": 3,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActSlowMode",
                "offset": 3,
                "length": 1,
                "offsetbit": 4,
                "scale": "1",
                "mask": ""
            },
            {
                "type": "bool",
                "name": "ActVelocity",
                "offset": 3,
                "length": 1,
                "offsetbit": 5,
                "scale": "1",
                "mask": ""
            }
        ],
        "swap1": "",
        "swap2": "",
        "swap3": "",
        "swap1Type": "swap",
        "swap2Type": "swap",
        "swap3Type": "swap",
        "msgProperty": "payload",
        "msgPropertyType": "str",
        "resultType": "keyvalue",
        "resultTypeType": "return",
        "multipleResult": false,
        "fanOutMultipleResult": false,
        "setTopic": true,
        "outputs": 1,
        "x": 750,
        "y": 1660,
        "wires": [
            [
                "3dcd28ba2cd1f97d"
            ]
        ]
    },
    {
        "id": "0e3ec676f3e39293",
        "type": "modbus-client",
        "name": "CIM500",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "tcpHost": "10.240.6.23",
        "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
    }
]

I see the value of adress 201, but the "splits" are not changing (true,false) when i change values on my pump.

So show us a debug window from the incoming packets so we can see if the packets actually do change value when you change states on the pump

Craig

In your flow, you request 6 WORDS from address 200...

msg.payload = 
{ value: msg.payload, 
'fc': 3, 
'unitid': 0, 
'address': 200, 
'quantity': 6 
};

So if you request 6 WORDS (12 bytes) from ADDR 200, then ...

Index ModBus Address Type Byte Size First byte
0 200 UTIN16 2 0
1 201 UTIN16 2 2
2 202 UTIN16 2 4
3 203 UTIN16 2 6
4 204 UTIN16 2 8
5 205 UTIN16 2 10

In simple terms, you want data from ADDR 201 (AKA data from bytes 2 & 3)

But in buffer parser you have...

Change these items to offset byte 2 & check what values you get.


Lastly, if that does not work, then show us the values going INTO and OUT OF the buffer parser node.