MODBUS RTU: Respond on read telegram using node-red-contrib-modbus

Hallo!

I want to simulate a slave and serving different values to the master. Master / Slave (Pi with Node-red and Modbus interface).

I know the sample which is included in the node-red package but I think I don't understand it.

I get the request form server "0103000b0001f5c8" which means:
Device 01
Read Holding Register
Register 11
Amount: 1

I want to anser with a value for example: 1500.

Thanks in advance!

BR

Hi.

Before I go on to offer potential solutions can I ask firstly, what is the end goal?

Are you simply trying to get two instances of node-red to communicate to each other over modbus? If so then have you considered using mqtt or maybe http?

Also you don't mention which node-red package you are using nor what samples you have tried.

Hi!

Thanks for your quick reply.

I have an inverter (Fronius) which get's some information from the smart meter (escpecially the current consumption). But i don't have the type of smatmeter needed (Fronius).

But i have all the information in node red because i gather the information from my smartmeter (Siemens) in real time via IR head.

So i need to simulate the Fronius smart meter. The inverter is master and sends the request telegrams.
It MUST be MODBUS RTU, there is no other way.

Package I'am using is node-red-contrib-modbus

BR,
Alex

So in effect you want to create a modbus RTU slave out of node-red where specific registers hold the values you collected from various sensors - is that an accurate description?

Yes, correct!

Hi @americanium

I got some time this afternoon to knock up a flow based on a previous modbus demo i did.

I wont go on to explain fully how it works but just say, if you feed serial (from master) into Modbus RTU Request and feed the reply from the output of "Modbus RTU Response" to the serial port it should work. (untested - I dont have a master RTU device to test it)

Example...

Demo Flow (CTRL+I to import)...

[{"id":"6e1e28ac4d35a2dd","type":"tab","label":"Modbus RTU","disabled":false,"info":"","env":[]},{"id":"f74dea32b8b4d9e1","type":"group","z":"6e1e28ac4d35a2dd","name":"Fake serial data","style":{"fill":"#ffff7f","fill-opacity":"0.48","label":true,"color":"#6f2fa0"},"nodes":["1cd1713fe1722efa","f8aa7af96609be7d","0fad909feda85126","9a325eaccf4c1541","0322f5b6e2b481be","2154c4784166083b","04a7e5f7c68e3aef","644d7830ec0a43ed","d36eb2339c1e1e82","8082cd9177f73f26"],"x":154,"y":59,"w":572,"h":282},{"id":"4054b1f7b0e34392","type":"group","z":"6e1e28ac4d35a2dd","name":"Modbus RTU Process Request","style":{"label":true,"fill":"#0070c0","fill-opacity":"0.65","color":"#000000","stroke":"#000000"},"nodes":["8a972cd051b0d47e","748ddcb55414bbe1","8db591992a306278","d0ce43fb6366e810","881023a46a2f14c3","3e40ad8216818d3a","d39c1920c6fd9d17","21275d8539c6eed2","7360eb3d8795bbf5","91dadaf4f686faed","f44b8a2543185577"],"x":194,"y":759,"w":1302,"h":122},{"id":"844b7b3f45a0a740","type":"group","z":"6e1e28ac4d35a2dd","name":"Modbus RTU Generate Response","style":{"stroke":"#000000","fill":"#0070c0","label":true,"color":"#000000"},"nodes":["4fac1cddc2127af3","428912e65adde3ea","8d45c412eccb5f90","704a2e8edfe42a1f","d3d939410d454bb3","9a1e45ea2f8466fe","448e52d2741dca1e","df9fad3e0c8d8ce6"],"x":194,"y":919,"w":1162,"h":162},{"id":"644d7830ec0a43ed","type":"buffer-parser","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Parse Request","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint8","name":"SLAVEID","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"FC","offset":1,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"ADDRESS","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"LENGTH","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"CRC","offset":6,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"raw","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":620,"y":140,"wires":[["7c1c1b0cdbcc02b9"]]},{"id":"110a3c6d127bc110","type":"debug","z":"6e1e28ac4d35a2dd","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"request","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":260,"wires":[]},{"id":"d1d4fe90d69c11d0","type":"inject","z":"6e1e28ac4d35a2dd","name":"set Slave1, AO11 to 55","props":[{"p":"payload"},{"p":"payload.SLAVEID","v":"1","vt":"num"},{"p":"payload.FC","v":"6","vt":"num"},{"p":"payload.ADDRESS","v":"11","vt":"num"},{"p":"payload.DATA","v":"55","vt":"num"},{"p":"_no_reply_serial","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":300,"y":640,"wires":[["6eeed409038d7f7f"]]},{"id":"0bf58f76ffe370e4","type":"debug","z":"6e1e28ac4d35a2dd","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":600,"wires":[]},{"id":"7c1c1b0cdbcc02b9","type":"link call","z":"6e1e28ac4d35a2dd","name":"","links":["7360eb3d8795bbf5"],"timeout":"30","x":900,"y":220,"wires":[["110a3c6d127bc110","63d3d00d2673f02e"]]},{"id":"6eeed409038d7f7f","type":"link call","z":"6e1e28ac4d35a2dd","name":"","links":["7360eb3d8795bbf5"],"timeout":"30","x":620,"y":540,"wires":[["0bf58f76ffe370e4"]]},{"id":"62a43f5d70abe8f8","type":"inject","z":"6e1e28ac4d35a2dd","name":"set Slave1, AO11 to 666","props":[{"p":"payload"},{"p":"payload.SLAVEID","v":"1","vt":"num"},{"p":"payload.FC","v":"6","vt":"num"},{"p":"payload.ADDRESS","v":"11","vt":"num"},{"p":"payload.DATA","v":"666","vt":"num"},{"p":"_no_reply_serial","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":300,"y":680,"wires":[["6eeed409038d7f7f"]]},{"id":"f81921a6ab17553a","type":"catch","z":"6e1e28ac4d35a2dd","name":"","scope":null,"uncaught":false,"x":860,"y":400,"wires":[["218d146c225dabed"]]},{"id":"218d146c225dabed","type":"debug","z":"6e1e28ac4d35a2dd","name":"error!","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":1350,"y":400,"wires":[]},{"id":"4fac1cddc2127af3","type":"switch","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"FC 1 ~6?","property":"request.FC","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"},{"t":"eq","v":"4","vt":"num"},{"t":"eq","v":"5","vt":"num"},{"t":"eq","v":"6","vt":"num"}],"checkall":"true","repair":false,"outputs":6,"x":380,"y":1000,"wires":[["704a2e8edfe42a1f"],["704a2e8edfe42a1f"],["428912e65adde3ea"],["428912e65adde3ea"],["9a1e45ea2f8466fe"],["9a1e45ea2f8466fe"]]},{"id":"f44b8a2543185577","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Check CRC","func":"function CalculateCRC(buffer) {\n    const POLY = 0xA001;\n    const SEED = 0xFFFF;\n    function CRC(buffer) {\n        let crc = SEED;\n        for (let i = 0; i < buffer.length; i++) {\n            crc = Calc_CRC(buffer[i], crc);\n        }\n        return crc;\n    }\n    function Calc_CRC(b, crc) {\n        crc ^= b & 0xFF;\n        for (let i = 0; i < 8; i++) {\n            let carry = crc & 0x0001;\n            crc >>= 1;\n            if (carry) crc ^= POLY;\n        }\n        return crc;\n    }\n    return CRC(buffer);\n}\n\nconst dataNoCRC = msg.request.raw.slice(0, -2);\nlet crc = Buffer.from([0, 0]);\ncrc.writeUInt16LE(CalculateCRC(dataNoCRC));\nlet calcCRC = crc.readUInt16BE();\n\nif (calcCRC !== msg.request.CRC) {\n    node.error(`CRC Error: Received CRC ${msg.request.CRC}, calculated CRC ${calcCRC}`, msg);\n    return null;//halt flow\n}\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":800,"wires":[["748ddcb55414bbe1"]]},{"id":"428912e65adde3ea","type":"buffer-maker","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"","specification":"spec","specificationType":"ui","items":[{"name":"SLAVEID","type":"byte","length":1,"dataType":"msg","data":"request.SLAVEID"},{"name":"FC","type":"byte","length":1,"dataType":"msg","data":"request.FC"},{"name":"BYTECOUNT","type":"byte","length":1,"dataType":"jsonata","data":"$count(payload) * 2"},{"name":"DATA","type":"uint16be","length":-1,"dataType":"msg","data":"payload"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":790,"y":1000,"wires":[["8d45c412eccb5f90"]]},{"id":"8d45c412eccb5f90","type":"function","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"Add CRC","func":"function CalculateCRC(buffer) {\n    const POLY = 0xA001;\n    const SEED = 0xFFFF;\n    function CRC(buffer) {\n        let crc = SEED;\n        for (let i = 0; i < buffer.length; i++) {\n            crc = Calc_CRC(buffer[i], crc);\n        }\n        return crc;\n    }\n    function Calc_CRC(b, crc) {\n        crc ^= b & 0xFF;\n        for (let i = 0; i < 8; i++) {\n            let carry = crc & 0x0001;\n            crc >>= 1;\n            if (carry) crc ^= POLY;\n        }\n        return crc;\n    }\n    return CRC(buffer);\n}\nlet crc = Buffer.from([0,0]); \ncrc.writeUInt16LE(CalculateCRC(msg.payload));\n\nmsg.payload = Buffer.from([...msg.payload, ...crc])\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":1000,"wires":[["df9fad3e0c8d8ce6"]]},{"id":"1cd1713fe1722efa","type":"buffer-parser","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"int16be","name":"item1","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"buffer","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":570,"y":100,"wires":[["644d7830ec0a43ed"]]},{"id":"f8aa7af96609be7d","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 3: 1103006B00037687","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1103006B00037687","payloadType":"str","x":310,"y":180,"wires":[["1cd1713fe1722efa"]]},{"id":"0fad909feda85126","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 5: 110500ACFF004E8B","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"110500ACFF004E8B","payloadType":"str","x":320,"y":260,"wires":[["d36eb2339c1e1e82"]]},{"id":"8a972cd051b0d47e","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"process FC","func":"const slave = msg.payload.SLAVEID;\nconst fc = msg.payload.FC;\nconst address = msg.payload.ADDRESS;\nconst length = msg.payload.LENGTH || 1;\nconst CRC = msg.payload.CRC;\n\n/* Address Ranges:\n1-9999\t    0000 to 270E\tread-write\tDiscrete Output Coils\tDO\n10001-19999\t0000 to 270E\tread\t    Discrete Input Contacts\tDI\n30001-39999\t0000 to 270E\tread\t    Analog Input Registers\tAI\n40001-49999\t0000 to 270E\tread-write\tAnalog Output Holding Registers\tAO\n*/\n\n/* FC: \n01 (0x01)\tRead DO\t        Read Coil Status\t    Discrete\tRead\n02 (0x02)\tRead DI\t        Read Input Status\t    Discrete\tRead\n03 (0x03)\tRead AO\t        Read Holding Registers\t16 bit\t    Read\n04 (0x04)\tRead AI\t        Read Input Registers\t16 bit\t    Read\n05 (0x05)\tWrite one DO\tForce Single Coil\t    Discrete\tWrite\n06 (0x06)\tWrite one AO\tPreset Single Register\t16 bit\t    Write\n*/\n\n\n//check addr + length is less than limit\nif (address + length > 9999) {\n    node.error(`Address ${address}, Length ${length} would result in out of range address`, msg);\n    return;\n}\n\n//get modbus databanks\nconst modbus = flow.get(\"modbus\") || {};\n\n//check / create modbus slave\nif (modbus[slave] == null) {\n    modbus[slave] = {};\n    flow.set(\"modbus\", modbus);\n}\n\n\n//Calculate the modbus register\nlet modbus_reg = 0;\nlet dataType = \"bit\";\nswitch (fc) {\n    case 1:\n    case 5:\n        modbus_reg = address + 1; //DO\n        dataType = \"bit\";\n        break;\n    case 2:\n        modbus_reg = address + 10001; //DI\n        dataType = \"bit\";\n        break;\n    case 3:\n    case 6:\n        modbus_reg = address + 40001; //AO\n        dataType = \"word\";\n        break;\n    case 4:\n        modbus_reg = address + 30001; //AI\n        dataType = \"word\";\n        break;\n    default:\n}\n\n//get modbus registers for this slave\nconst modbus_registers = modbus[slave];\n\nif (fc == 1 || fc == 2) {\n    //Read DO or DI\n    const value = [];\n    for (let index = 0; index < length; index++) {\n        value.push(!!modbus_registers[modbus_reg + index])\n    }\n    msg.payload = value;\n} else if (fc == 5) {\n    //Write one DO\n    modbus_registers[modbus_reg] = !!msg.payload.DATA\n    msg.payload = true;\n } else if (fc == 6) {\n    //Preset Single Register\n    modbus_registers[modbus_reg] = msg.payload.DATA\n    msg.payload = true;\n} else {\n    //FC03 Read AO\n    //FC04 Read AI\n    const value = [];\n    for (let index = 0; index < length; index++) {\n        value.push(modbus_registers[modbus_reg + index] || 0)\n    }\n    msg.payload = value;\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1350,"y":800,"wires":[["91dadaf4f686faed"]]},{"id":"748ddcb55414bbe1","type":"switch","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Check SLAVEID","property":"request.SLAVEID","propertyType":"msg","rules":[{"t":"btwn","v":"1","vt":"num","v2":"247","v2t":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":740,"y":800,"wires":[["d0ce43fb6366e810"],["8db591992a306278"]]},{"id":"8db591992a306278","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Raise Error","func":"node.error(\"Invalid SLAVEID: \" + msg.payload.SLAVEID, msg);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":840,"wires":[[]]},{"id":"d0ce43fb6366e810","type":"switch","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Check FC","property":"request.FC","propertyType":"msg","rules":[{"t":"btwn","v":"1","vt":"num","v2":"6","v2t":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":940,"y":800,"wires":[["3e40ad8216818d3a"],["881023a46a2f14c3"]]},{"id":"881023a46a2f14c3","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Raise Error","func":"node.error(\"Invalid FC: \" + msg.payload.FC, msg);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":840,"wires":[[]]},{"id":"3e40ad8216818d3a","type":"switch","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Check ADDRESS","property":"request.ADDRESS","propertyType":"msg","rules":[{"t":"btwn","v":"0","vt":"num","v2":"9998","v2t":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1150,"y":800,"wires":[["8a972cd051b0d47e"],["d39c1920c6fd9d17"]]},{"id":"d39c1920c6fd9d17","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Raise Error","func":"node.error(\"Invalid ADDRESS: \" + msg.payload.ADDRESS, msg);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1150,"y":840,"wires":[[]]},{"id":"21275d8539c6eed2","type":"function","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Store request","func":"\nmsg.request = { ...msg.payload };\nmsg.request.LENGTH = msg.request.LENGTH || 1;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":368,"y":800,"wires":[["f44b8a2543185577"]]},{"id":"7360eb3d8795bbf5","type":"link in","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"Modbus RTU Request","links":[],"x":235,"y":800,"wires":[["21275d8539c6eed2"]]},{"id":"91dadaf4f686faed","type":"link out","z":"6e1e28ac4d35a2dd","g":"4054b1f7b0e34392","name":"","mode":"return","links":[],"x":1455,"y":800,"wires":[]},{"id":"ba278cd7435db930","type":"inject","z":"6e1e28ac4d35a2dd","name":"set Slave17, AO40108 to 0xAE41,0x5652,0x4340","props":[{"p":"payload"},{"p":"payload.SLAVEID","v":"0x11","vt":"num"},{"p":"payload.FC","v":"6","vt":"num"},{"p":"payload.ADDRESS","v":"107","vt":"num"},{"p":"payload.DATA","v":"0xAE41,0x5652,0x4340","vt":"str"},{"p":"_no_reply_serial","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":380,"y":480,"wires":[["d5adbb8754fe5978"]]},{"id":"d5adbb8754fe5978","type":"function","z":"6e1e28ac4d35a2dd","name":"","func":"node.warn(msg)\nlet data = msg.payload.DATA.split(\",\");\nnode.warn(data)\ndata = data.map(e => parseInt(e));\nnode.warn(data)\nlet address = msg.payload.ADDRESS\nfor (const d of data) {\n    const m = RED.util.cloneMessage(msg)\n    m.payload.DATA = d;\n    m.payload.ADDRESS = address;\n    m.payload.LENGTH = 1;\n    node.send(m);\n    address++;\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":480,"wires":[["6eeed409038d7f7f"]]},{"id":"e34bf9269aaad89a","type":"inject","z":"6e1e28ac4d35a2dd","name":"set Slave17, DO173 to true","props":[{"p":"payload"},{"p":"payload.SLAVEID","v":"0x11","vt":"num"},{"p":"payload.FC","v":"5","vt":"num"},{"p":"payload.ADDRESS","v":"172","vt":"num"},{"p":"payload.DATA","v":"true","vt":"bool"},{"p":"_no_reply_serial","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":310,"y":540,"wires":[["6eeed409038d7f7f"]]},{"id":"5e5351f3272dee9a","type":"inject","z":"6e1e28ac4d35a2dd","name":"set Slave17, DO173 to false","props":[{"p":"payload"},{"p":"payload.SLAVEID","v":"0x11","vt":"num"},{"p":"payload.FC","v":"5","vt":"num"},{"p":"payload.ADDRESS","v":"172","vt":"num"},{"p":"payload.DATA","v":"0","vt":"num"},{"p":"_no_reply_serial","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{}","payloadType":"json","x":320,"y":580,"wires":[["6eeed409038d7f7f"]]},{"id":"9a325eaccf4c1541","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 6: 1106000100039A9B","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1106000100039A9B","payloadType":"str","x":320,"y":300,"wires":[["d36eb2339c1e1e82"]]},{"id":"57d2ef516920aed7","type":"comment","z":"6e1e28ac4d35a2dd","name":"RTU Function Reference: \\n 01 (0x01)\tRead DO\t        Read Coil Status\t    Discrete\tRead \\n 02 (0x02)\tRead DI\t        Read Input Status\t    Discrete\tRead \\n 03 (0x03)\tRead AO\t        Read Holding Registers\t16 bit\tRead \\n 04 (0x04)\tRead AI\t        Read Input Registers\t16 bit\tRead \\n 05 (0x05)\tWrite one DO\tForce Single Coil\t    Discrete\tWrite \\n 06 (0x06)\tWrite one AO\tPreset Single Register\t16 bit\tWrite","info":"","x":1290,"y":660,"wires":[]},{"id":"704a2e8edfe42a1f","type":"function","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"bits to bytes","func":"const req = msg.request;\nlet data = msg.payload;\ndata = data.map(e => e ? 1 : 0);\n\nconst expectedBitCount = req.LENGTH\nconst actualBitCount = data.length;\n\nif (expectedBitCount !== actualBitCount) {\n    node.error(`expected ${expectedBitCount} values, recieved ${actualBitCount}`, msg);\n    return;\n}\n\nlet bit = 0;\nlet byte = 0;\nlet bytes = [];\nfor (let idx = 0; idx < data.length; idx++) {\n    const value = data[idx];\n    if (bit === 0) {\n        bytes[byte] = 0;\n    }\n    bytes[byte] = bitSet(bytes[byte] || 0, bit, value);\n    bit++;\n    if (bit >= 8) {\n        bit = 0;\n        byte++;\n    }\n}\n\nmsg.payload = bytes;\n\nreturn msg;\n\n\nfunction bitSet(num, bit, val) {\n    if (val) return bitSetBit(num, bit);\n    return bitResetBit(num, bit);\n}\n\nfunction bitGet(num, bit) {\n    return ((num >> bit) % 2 != 0)\n}\n\nfunction bitSetBit(num, bit) {\n    return num | 1 << bit;\n}\n\nfunction bitResetBit(num, bit) {\n    return num & ~(1 << bit);\n}\n\nfunction bitToggle(num, bit) {\n    return bitGet(num, bit) ? bitResetBit(num, bit) : bitSetBit(num, bit);\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":960,"wires":[["d3d939410d454bb3"]]},{"id":"d3d939410d454bb3","type":"buffer-maker","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"","specification":"spec","specificationType":"ui","items":[{"name":"SLAVEID","type":"byte","length":1,"dataType":"msg","data":"request.SLAVEID"},{"name":"FC","type":"byte","length":1,"dataType":"msg","data":"request.FC"},{"name":"BYTECOUNT","type":"byte","length":1,"dataType":"jsonata","data":"$count(payload)"},{"name":"DATA","type":"byte","length":-1,"dataType":"msg","data":"payload"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":790,"y":960,"wires":[["8d45c412eccb5f90"]]},{"id":"b7e383e7b91122b3","type":"inject","z":"6e1e28ac4d35a2dd","name":"init","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":250,"y":400,"wires":[["0cbe5ee7d92359fe"]]},{"id":"0cbe5ee7d92359fe","type":"function","z":"6e1e28ac4d35a2dd","name":"Set test data","func":"\n//set bits for demo 1\n//How can I send a Modbus RTU command to read discrete output? Command 0x01\n//https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/#read_discr_out\nflow.set('modbus[\"17\"][\"20\"]', true);\nflow.set('modbus[\"17\"][\"21\"]', false);\nflow.set('modbus[\"17\"][\"22\"]', true);\nflow.set('modbus[\"17\"][\"23\"]', true);\nflow.set('modbus[\"17\"][\"24\"]', false);\nflow.set('modbus[\"17\"][\"25\"]', false);\nflow.set('modbus[\"17\"][\"26\"]', true);\nflow.set('modbus[\"17\"][\"27\"]', true);\n\nflow.set('modbus[\"17\"][\"28\"]', true);\nflow.set('modbus[\"17\"][\"29\"]', true);\nflow.set('modbus[\"17\"][\"30\"]', false);\nflow.set('modbus[\"17\"][\"31\"]', true);\nflow.set('modbus[\"17\"][\"32\"]', false);\nflow.set('modbus[\"17\"][\"33\"]', true);\nflow.set('modbus[\"17\"][\"34\"]', true);\nflow.set('modbus[\"17\"][\"35\"]', false);\n\nflow.set('modbus[\"17\"][\"36\"]', false);\nflow.set('modbus[\"17\"][\"37\"]', true);\nflow.set('modbus[\"17\"][\"38\"]', false);\nflow.set('modbus[\"17\"][\"39\"]', false);\nflow.set('modbus[\"17\"][\"40\"]', true);\nflow.set('modbus[\"17\"][\"41\"]', true);\nflow.set('modbus[\"17\"][\"42\"]', false);\nflow.set('modbus[\"17\"][\"43\"]', true);\n\nflow.set('modbus[\"17\"][\"44\"]', false);\nflow.set('modbus[\"17\"][\"45\"]', true);\nflow.set('modbus[\"17\"][\"46\"]', true);\nflow.set('modbus[\"17\"][\"47\"]', true);\nflow.set('modbus[\"17\"][\"48\"]', false);\nflow.set('modbus[\"17\"][\"49\"]', false);\nflow.set('modbus[\"17\"][\"50\"]', false);\nflow.set('modbus[\"17\"][\"51\"]', false);\n\nflow.set('modbus[\"17\"][\"52\"]', true);\nflow.set('modbus[\"17\"][\"53\"]', true);\nflow.set('modbus[\"17\"][\"54\"]', false);\nflow.set('modbus[\"17\"][\"55\"]', true);\nflow.set('modbus[\"17\"][\"56\"]', true);\nflow.set('modbus[\"17\"][\"57\"]', false);\nflow.set('modbus[\"17\"][\"58\"]', false);\n\n//set bits for demo 2\n//How can I send a Modbus RTU command to read a digital input? Command 0x02\n//https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/#read_discr_in\nflow.set('modbus[\"17\"][\"10197\"]', false);\nflow.set('modbus[\"17\"][\"10198\"]', false);\nflow.set('modbus[\"17\"][\"10199\"]', true);\nflow.set('modbus[\"17\"][\"10200\"]', true);\nflow.set('modbus[\"17\"][\"10201\"]', false);\nflow.set('modbus[\"17\"][\"10202\"]', true);\nflow.set('modbus[\"17\"][\"10203\"]', false);\nflow.set('modbus[\"17\"][\"10204\"]', true);\n\nflow.set('modbus[\"17\"][\"10205\"]', true);\nflow.set('modbus[\"17\"][\"10206\"]', true);\nflow.set('modbus[\"17\"][\"10207\"]', false);\nflow.set('modbus[\"17\"][\"10208\"]', true);\nflow.set('modbus[\"17\"][\"10209\"]', true);\nflow.set('modbus[\"17\"][\"10210\"]', false);\nflow.set('modbus[\"17\"][\"10211\"]', true);\nflow.set('modbus[\"17\"][\"10212\"]', true);\n\nflow.set('modbus[\"17\"][\"10213\"]', true);\nflow.set('modbus[\"17\"][\"10214\"]', false);\nflow.set('modbus[\"17\"][\"10215\"]', true);\nflow.set('modbus[\"17\"][\"10216\"]', false);\nflow.set('modbus[\"17\"][\"10217\"]', true);\nflow.set('modbus[\"17\"][\"10218\"]', true);\nflow.set('modbus[\"17\"][\"10219\"]', false);\nflow.set('modbus[\"17\"][\"10220\"]', false);\n\n//set values for demo 3\n//How can I send a Modbus RTU command to read analog output? Command 0x03\n//https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/#read_analog_out\nflow.set('modbus[\"17\"][\"40108\"]', 0xAE00 | 0x0041 );\nflow.set('modbus[\"17\"][\"40109\"]', 0x5600 | 0x0052 );\nflow.set('modbus[\"17\"][\"40110\"]', 0x4300 | 0x0040 );\n\n//set values for demo 4\n//How can I send the Modbus RTU command to read the analog input? Command 0x04\n//https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/#read_analog_in\nflow.set('modbus[\"17\"][\"30009\"]', 0x000A );\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":400,"wires":[[]]},{"id":"0322f5b6e2b481be","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 1: 1101001300250E84","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1101001300250E84","payloadType":"str","x":310,"y":100,"wires":[["1cd1713fe1722efa"]]},{"id":"2154c4784166083b","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 2: 110200C40016BAA9","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"110200C40016BAA9","payloadType":"str","x":320,"y":140,"wires":[["1cd1713fe1722efa"]]},{"id":"04a7e5f7c68e3aef","type":"inject","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Test 4: 110400080001B298","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"110400080001B298","payloadType":"str","x":310,"y":220,"wires":[["1cd1713fe1722efa"]]},{"id":"d36eb2339c1e1e82","type":"buffer-parser","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"int16be","name":"item1","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"buffer","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":570,"y":260,"wires":[["8082cd9177f73f26"]]},{"id":"8082cd9177f73f26","type":"buffer-parser","z":"6e1e28ac4d35a2dd","g":"f74dea32b8b4d9e1","name":"Parse Request","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint8","name":"SLAVEID","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"FC","offset":1,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"ADDRESS","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"DATA","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"CRC","offset":6,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"raw","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":620,"y":300,"wires":[["7c1c1b0cdbcc02b9"]]},{"id":"9a1e45ea2f8466fe","type":"buffer-maker","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"","specification":"spec","specificationType":"ui","items":[{"name":"SLAVEID","type":"byte","length":1,"dataType":"msg","data":"request.SLAVEID"},{"name":"FC","type":"byte","length":1,"dataType":"msg","data":"request.FC"},{"name":"ADDRESS","type":"uint16be","length":1,"dataType":"msg","data":"request.ADDRESS"},{"name":"DATA","type":"uint16be","length":1,"dataType":"msg","data":"request.DATA"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":790,"y":1040,"wires":[["8d45c412eccb5f90"]]},{"id":"448e52d2741dca1e","type":"link in","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"Modbus RTU Response","links":[],"x":235,"y":1000,"wires":[["4fac1cddc2127af3"]]},{"id":"df9fad3e0c8d8ce6","type":"link out","z":"6e1e28ac4d35a2dd","g":"844b7b3f45a0a740","name":"","mode":"return","links":[],"x":1315,"y":1000,"wires":[]},{"id":"63d3d00d2673f02e","type":"link call","z":"6e1e28ac4d35a2dd","name":"","links":["448e52d2741dca1e"],"timeout":"30","x":1150,"y":220,"wires":[["ef737fa58f053b54"]]},{"id":"ef737fa58f053b54","type":"debug","z":"6e1e28ac4d35a2dd","name":"To Serial -->","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1370,"y":220,"wires":[]},{"id":"ef3fb788ed8edb88","type":"comment","z":"6e1e28ac4d35a2dd","name":"Modbus RTU Slave.  NOTES... \\n * Only FC 1 ~ FC6 currently supported \\n * Untested with real device \\n * Tests are written to match  https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/#Contents","info":"","x":1290,"y":100,"wires":[]}]

Notes...

  • Requires node-red V2.1+ (using link call)
  • Only FC1 ~ FC6 are written - if you want FC15 and FC16 then look at how I implemented FC1~FC6 & refer to the modbus spec
  • All demo serial data is from this page where the commands and responses are listed (so you can check results for yourself)
  • All modbus slave data is stored flow context
    • image
    • To see / read / write values of the slave units data - you can use change nodes or function nodes to update the flow values.
1 Like