Communicate over modbus with Hörmann garage door

I trying to make my UniPi Patron unit running Node-RED to communicate with a Hörmann SupraMatic 4 motor for controlling my garage door and querying it's current state.
There are projects like GitHub - Gifford47/HCPBridgeMqtt: Emulates Hörmann UAP1-HCP board using an ESP32 and a RS485 converter, and exposes garage door controls over web page and MQTT. where the communication happens on an ESP32 or alike for communication with Home Assistant, but as I try to configure/setup my home automation basic functionality in Node-RED on the UniPi units in my house so I'm not dependent on extra computers running Home Assistant.
And since the SupraMatic motor is located almost next to such a UniPi unit, which support RS485 out of the box, I figured I should be able to hardwire the motor to the UniPi unit and control it using Node-RED running on that UniPi.

Therefore I want to communicate over modbus with the motor, which doesn't look too complicated when looking at the investigation of the communication already performed here: HCPBridgeMqtt/Investigation at master · Gifford47/HCPBridgeMqtt · GitHub.. However, I seem to need Function Code 17, which seems not implemented in node-red-contrib-modbus or any other modbus implementations I find in the Node-RED library.

Could someone guide me on how I could try to handle this communication within Node-RED? I'm completely new to modbus.

I dont think FC 17 is implemented in the underlying Modbus library (jsmodbus) - well it isnt stated as NOT being supported but there is this issue that speaks of adding FC 17 (the item remains unchecked)

So, your options are:

  1. Raise a "Feature Request" on the github repos + cross your finger :crossed_fingers:
  2. Create Pull Requests to both jsmodbus and node-red-contrib-modbus to add FC 17 support
  3. Fork both jsmodbus and node-red-contrib-modbus to add FC 17 support and start using it / publish own package.
  4. DIY the protocol in Node-RED.

DIY* It is not that complicated. The CRC calcs are well documented (there are even examples on this forum)

If you want a starting point, I wrote a simple demo you could build upon.

To aid you in this, there is an online modbus decoder. Here it is decoding the data in the github repo you linked:

1 Like

Thanks for the links and starting points. I'll try going the DIY way as adding FC 17 to jsmodbus and node-red-contrib-modbus myself is probably a bit too far fetched for my current knowledge of modbus and javascript..

@Steve-Mcl, one more question.
What node should I use to communicate with the RS485 port (/dev/ttymxc0)? I tried node-red-node-serialport but that doesn't seem to see any incoming data.

I tried installing node-red-contrib-serialport-rs485 but that is updated last 7 years ago and doesn't want to install due to compilation errors.

Does the device running node-red have a built in RS485 port or are you using an adaptor?

Show us how you have configured it.

It has a built-in RS485 port.
This is my current flow: just a serial in and a debug node:

[{"id":"835c3a54e5fc06c8","type":"serial in","z":"f9a4695ed5b7732a","name":"RS485-1 Modbus","serial":"ccba265ed98153e1","x":300,"y":780,"wires":[["28130398b5b33ddc"]]},{"id":"28130398b5b33ddc","type":"debug","z":"f9a4695ed5b7732a","name":"Serial Out","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":520,"y":780,"wires":[]},{"id":"ccba265ed98153e1","type":"serial-port","serialport":"/dev/ttymxc0","serialbaud":"57600","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"\\n","bin":"bin","out":"char","addchar":"","responsetimeout":"10000"}]

When I perform a busscan on the SupraMatic motor, I see RX led on the UniPi flicker.
When I do a od -x < /dev/ttymxc0 , I see incoming data.. But the debug window in Node-Red stays empty.

you have set the serial node to wait for \n - does a newline ever get sent?

image

you'd be better off using something less specific to start with (like after "after a silence of 100ms")

image


Also, be sure the port is not in use by any other process on the device. Including (but not limited to) Modbus nodes (and the modbus config nodes)


And as it is modbus, try actually poking a valid modbus command (like a read FN) see if you get a response.

1 Like

No idea if a \n gets send :slight_smile: I tried with the timeout setting also but that didn't make a difference.

I had already removed any modbus nodes I experimented with before.. But I did forget about the modbus config node. After removing that one too, I now get input from the serial in. Currently chopped in 1 or 2 byte chunks, so I assume I'll have to play with the silence timeout now.

I'm afraid it is not the average standard modbus device, as it won't react to modbus read commands.

This motor is designed to connect with separately sold proprietary HCP bridge/gateway. Communication happens to be modbus, but it certainly is not a full modbus implementation.
It actually has modbus disabled but I can start a busscan on it (using a service menu on the motor), where it will enable the port and start sending out the scan sequence 02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35 according to the docs I linked earlier. It expects 02 17 0a 00 00 02 05 04 30 10 ff a8 45 0e df as an answer. If that "handshake" is performed it will keep it's modbus port up and open and I should be able to control the garage door using specific commands. It will also broadcast the current state of the garage door. It will also at regular intervals broadcast some more specific sequences which I will have to answer correctly or the motor will go in error and stop modbus communication until power cycled..
So I still have a few challenges to tackle :slight_smile:

All right.. I'm a step closer.. I found that the time between commands should be 3.5x the time for a character, so that depends on the baud rate but there is a min. of 1.75ms which is reached at 38400bps and since I have to use 57600, 1.75ms seems to be the magic number.
I also had to set Parity to Even (which I overlooked at first) .
Now I get the scan sequences as mentioned above nicely in separate buffers..

Thanks a lot already for the pointers!

2 Likes

I've relative success.. I've managed to make it work, for a few seconds.
All communication looks fine, but after a few seconds the motor stops communicating.
I figured that in the communication sometimes the motor repeats a command, and when that happens 2 times then the motor stops communication about 22 messages later.

I have added some timing measurements to calculate the difference between the time the command comes in and the time Node-Red sends the generated response. And it seems that when the time between command and response exceeds about 12ms, the motor resends its last command.

So somehow I need to make Node-Red reply within those 12ms. On average it generates a response in about 7 to 8ms but sometimes it takes up to 17ms.. and it seems to only take 2 times for the motor to stop communicating.

Any tips on how to make sure timings are below 12ms ?

Current flow:

[{"id":"34b3c5de9c4adf4f","type":"inject","z":"f9a4695ed5b7732a","name":"Busscan","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"#:(volatile)::slaveid","vt":"flow"},{"p":"fc","v":"23","vt":"num"},{"p":"regReadAddr","v":"40121","vt":"num"},{"p":"regReadCount","v":"5","vt":"num"},{"p":"regWriteAddr","v":"40001","vt":"num"},{"p":"regWriteCount","v":"3","vt":"num"},{"p":"regWriteByteCount","v":"6","vt":"num"},{"p":"data","v":"[0,2,33,14,1,0]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":760,"y":140,"wires":[["13c627dcb0c2dcdc"]]},{"id":"b0de243bd7d3dea1","type":"buffer-maker","z":"f9a4695ed5b7732a","name":"build MB Request","specification":"specification","specificationType":"msg","items":[{"name":"slaveid","type":"uint8","length":1,"dataType":"msg","data":"slaveid"},{"name":"fc","type":"uint8","length":1,"dataType":"msg","data":"fc"},{"name":"regReadAddr","type":"uint16be","length":1,"dataType":"msg","data":"regReadAddr"},{"name":"regReadCount","type":"uint16be","length":1,"dataType":"msg","data":"regReadCount"},{"name":"regWriteAddr","type":"uint16be","length":1,"dataType":"msg","data":"regWriteAddr"},{"name":"regWriteCount","type":"uint16be","length":1,"dataType":"msg","data":"regWriteCount"},{"name":"regWriteByteCount","type":"uint8","length":1,"dataType":"msg","data":"regWriteByteCount"},{"name":"data","type":"buffer","length":6,"dataType":"msg","data":"data"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":450,"y":300,"wires":[["2bf23e25b6802ec1","d1b0c8e810a25324"]]},{"id":"2bf23e25b6802ec1","type":"debug","z":"f9a4695ed5b7732a","name":"Busscan request","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":300,"wires":[]},{"id":"6596665f2f0d7825","type":"buffer-parser","z":"f9a4695ed5b7732a","name":"Parse MB Request SlaveID/FC","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint8","name":"header=>slaveid","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"header=>fc","offset":1,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"buffer","offset":2,"length":-1,"offsetbit":0,"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":470,"y":420,"wires":[["91655cce1fe28623","d938baaa86802cad"]]},{"id":"91655cce1fe28623","type":"debug","z":"f9a4695ed5b7732a","name":"Parsed UID/FC","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":720,"y":380,"wires":[]},{"id":"22ecae6ccb321a23","type":"switch","z":"f9a4695ed5b7732a","name":"Check FC","property":"payload.header.fc","propertyType":"msg","rules":[{"t":"eq","v":"16","vt":"str"},{"t":"eq","v":"23","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":900,"y":480,"wires":[["bed14879abc62380"],["d368fb387a21f8a5"],["4a8b1480779248d6"]]},{"id":"4a8b1480779248d6","type":"debug","z":"f9a4695ed5b7732a","name":"Unknown functioncode","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.header.fc","targetType":"msg","statusVal":"","statusType":"auto","x":1180,"y":800,"wires":[]},{"id":"d368fb387a21f8a5","type":"buffer-parser","z":"f9a4695ed5b7732a","name":"Parse MB FC23 Request","data":"payload.buffer","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16be","name":"header=>regReadAddr","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"header=>regReadCount","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"header=>regWriteAddr","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"header=>regWriteCount","offset":6,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"header=>regWriteByteCount","offset":8,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"data","offset":9,"length":-1,"offsetbit":0,"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":1190,"y":660,"wires":[["5cb8fe9be9b67d13","1a683fd838a4c8d3"]]},{"id":"5cb8fe9be9b67d13","type":"debug","z":"f9a4695ed5b7732a","name":"FC23 Request","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1440,"y":620,"wires":[]},{"id":"d1b0c8e810a25324","type":"function","z":"f9a4695ed5b7732a","name":"Append CRC16","func":"function crc16(buf) {\n    let crc = 0xFFFF;\n    for (let i = 0; i < buf.length; i++) {\n        crc = crc ^ buf[i];\n        for (let j = 0; j < 8; j++) {\n            const temp = crc & 0x01;\n            crc >>= 0x01;\n            if (temp == 0x01) {\n                crc ^= 0xA001;\n            }\n        }\n    }\n    const arr = crc.toString(16).toUpperCase().substring(-4).padStart(4,\"0\").split(\"\")\n    return Buffer.from(arr.splice(-2).concat(arr).join(\"\"), 'hex')\n}\n\nconst crc = crc16(msg.payload);\nmsg.crc = crc\nmsg.originalPayload = msg.payload;\nmsg.payload = Buffer.concat([msg.payload, crc]);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":340,"wires":[["5f88a33391993b96","67190339e15be57c"]]},{"id":"5f88a33391993b96","type":"debug","z":"f9a4695ed5b7732a","name":"CRC16 added","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":680,"y":340,"wires":[]},{"id":"835c3a54e5fc06c8","type":"serial in","z":"f9a4695ed5b7732a","name":"RS485-1 Modbus","serial":"ccba265ed98153e1","x":160,"y":420,"wires":[["67190339e15be57c"]]},{"id":"28130398b5b33ddc","type":"debug","z":"f9a4695ed5b7732a","name":"Serial IN","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":410,"y":460,"wires":[]},{"id":"d938baaa86802cad","type":"switch","z":"f9a4695ed5b7732a","name":"Filter on SlaveID","property":"payload.header.slaveid","propertyType":"msg","rules":[{"t":"eq","v":"#:(volatile)::slaveid","vt":"flow"},{"t":"eq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":720,"y":420,"wires":[["219997bc83e4af1f"],["219997bc83e4af1f"]]},{"id":"219997bc83e4af1f","type":"function","z":"f9a4695ed5b7732a","name":"Check CRC16","func":"function crc16(buf) {\n    let crc = 0xFFFF;\n    for (let i = 0; i < buf.length; i++) {\n        crc = crc ^ buf[i];\n        for (let j = 0; j < 8; j++) {\n            const temp = crc & 0x01;\n            crc >>= 0x01;\n            if (temp == 0x01) {\n                crc ^= 0xA001;\n            }\n        }\n    }\n    const arr = crc.toString(16).toUpperCase().substring(-4).padStart(4, \"0\").split(\"\")\n    return Buffer.from(arr.splice(-2).concat(arr).join(\"\"), 'hex')\n}\n\nconst buf = msg.originalPayload;\n\nif (Buffer.compare(crc16(buf.slice(0, buf.length - 2)), buf.slice(buf.length - 2)) != 0) {\n    node.error(\"Invalid CRC\",msg)\n}\nelse {\n    // strip CRC from payload\n    msg.payload.buffer = msg.payload.buffer.slice(0, msg.payload.buffer.length - 2)\n    msg.payload.header.crc = buf.slice(buf.length - 2)\n    \n    return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":420,"wires":[["79707be5c58757ad","22ecae6ccb321a23"]]},{"id":"79707be5c58757ad","type":"debug","z":"f9a4695ed5b7732a","name":"CRC validated","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":400,"wires":[]},{"id":"ab858e79d16e9488","type":"inject","z":"f9a4695ed5b7732a","name":"Set Modbus SLAVE ID","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"2","payloadType":"num","x":160,"y":140,"wires":[["f99e1c2bd90dd04e"]]},{"id":"f99e1c2bd90dd04e","type":"change","z":"f9a4695ed5b7732a","name":"","rules":[{"t":"set","p":"#:(volatile)::slaveid","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":140,"wires":[[]]},{"id":"5f47048ecff39fbe","type":"buffer-maker","z":"f9a4695ed5b7732a","name":"Build MB Response","specification":"specification","specificationType":"msg","items":[{"name":"slaveid","type":"uint8","length":1,"dataType":"msg","data":"slaveid"},{"name":"fc","type":"uint8","length":1,"dataType":"msg","data":"fc"},{"name":"regReadAddr","type":"uint16be","length":1,"dataType":"msg","data":"regReadAddr"},{"name":"regReadCount","type":"uint16be","length":1,"dataType":"msg","data":"regReadCount"},{"name":"regWriteAddr","type":"uint16be","length":1,"dataType":"msg","data":"regWriteAddr"},{"name":"regWriteCount","type":"uint16be","length":1,"dataType":"msg","data":"regWriteCount"},{"name":"regWriteByteCount","type":"uint8","length":1,"dataType":"msg","data":"regWriteByteCount"},{"name":"data","type":"buffer","length":6,"dataType":"msg","data":"data"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":2090,"y":780,"wires":[["bf8a45364c5cc904"]]},{"id":"e79ea49b32a57a0e","type":"function","z":"f9a4695ed5b7732a","name":"Define MB FC23 Response","func":"msg.specification = {\n    \"options\": {\n        \"byteSwap\": false,\n        \"resultType\": \"keyvalue\",\n        \"msgProperty\": \"payload\",\n        \"multipleResult\": false,\n        \"setTopic\": true\n    },\n    \"items\": [ \n        {\n            \"name\": \"slaveid\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": flow.get('slaveid', 'volatile'),\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"fc\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": 23,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"byteCount\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": msg.payload.header.regReadCount * 2,\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"regReadValue\",\n            \"type\": \"buffer\",\n            \"length\": msg.payload.header.regReadCount * 2,\n            \"data\": 'response',\n            \"dataType\": \"msg\"\n        }\n    ]\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1840,"y":780,"wires":[["5f47048ecff39fbe"]]},{"id":"bf8a45364c5cc904","type":"function","z":"f9a4695ed5b7732a","name":"Append CRC16","func":"function crc16(buf) {\n    let crc = 0xFFFF;\n    for (let i = 0; i < buf.length; i++) {\n        crc = crc ^ buf[i];\n        for (let j = 0; j < 8; j++) {\n            const temp = crc & 0x01;\n            crc >>= 0x01;\n            if (temp == 0x01) {\n                crc ^= 0xA001;\n            }\n        }\n    }\n    const arr = crc.toString(16).toUpperCase().substring(-4).padStart(4,\"0\").split(\"\")\n    return Buffer.from(arr.splice(-2).concat(arr).join(\"\"), 'hex')\n}\n\nconst crc = crc16(msg.payload);\nmsg.crc = crc\nmsg.originalPayload = msg.payload;\nmsg.payload = Buffer.concat([msg.payload, crc]);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2080,"y":820,"wires":[["69187ae18d9c993e","9e0e29280c9f1e93","13cebb7f4db22f4b"]]},{"id":"13cebb7f4db22f4b","type":"debug","z":"f9a4695ed5b7732a","name":"Response","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2450,"y":920,"wires":[]},{"id":"1a683fd838a4c8d3","type":"switch","z":"f9a4695ed5b7732a","name":"regWriteAddr == 0x9C41","property":"payload.header.regWriteAddr","propertyType":"msg","rules":[{"t":"eq","v":"40001","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1470,"y":660,"wires":[["5ef12d4857ccbd0d"]]},{"id":"5ef12d4857ccbd0d","type":"switch","z":"f9a4695ed5b7732a","name":"regReadAddr == 0x9CB9","property":"payload.header.regReadAddr","propertyType":"msg","rules":[{"t":"eq","v":"40121","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1470,"y":700,"wires":[["14b45fdf66b2fba2"]]},{"id":"9752d156378625f7","type":"inject","z":"f9a4695ed5b7732a","name":"Command Request","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"#:(volatile)::slaveid","vt":"flow"},{"p":"fc","v":"23","vt":"num"},{"p":"regReadAddr","v":"40121","vt":"num"},{"p":"regReadCount","v":"8","vt":"num"},{"p":"regWriteAddr","v":"40001","vt":"num"},{"p":"regWriteCount","v":"2","vt":"num"},{"p":"regWriteByteCount","v":"4","vt":"num"},{"p":"data","v":"[62,3,0,0]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":790,"y":180,"wires":[["13c627dcb0c2dcdc"]]},{"id":"13c627dcb0c2dcdc","type":"function","z":"f9a4695ed5b7732a","name":"Define MB FC23 Request","func":"msg.specification = {\n    \"options\": {\n        \"byteSwap\": false,\n        \"resultType\": \"keyvalue\",\n        \"msgProperty\": \"payload\",\n        \"multipleResult\": false,\n        \"setTopic\": true\n    },\n    \"items\": [ \n        {\n            \"name\": \"slaveid\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": msg.slaveid,\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"fc\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": msg.fc,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"regReadAddr\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regReadAddr,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"regReadCount\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regReadCount,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"regWriteAddr\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regWriteAddr,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"regWriteCount\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regWriteCount,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"regWriteByteCount\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": msg.regWriteByteCount,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"data\",\n            \"type\": \"buffer\",\n            \"length\": msg.regWriteByteCount,\n            \"data\": msg.data,\n            \"dataType\": \"buffer\"\n        },\n        \n    ]\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":180,"wires":[["486c0c0425055075"]]},{"id":"b02751c7ddf4e2a9","type":"inject","z":"f9a4695ed5b7732a","name":"Empty Command Request","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"#:(volatile)::slaveid","vt":"flow"},{"p":"fc","v":"23","vt":"num"},{"p":"regReadAddr","v":"40121","vt":"num"},{"p":"regReadCount","v":"2","vt":"num"},{"p":"regWriteAddr","v":"40001","vt":"num"},{"p":"regWriteCount","v":"2","vt":"num"},{"p":"regWriteByteCount","v":"4","vt":"num"},{"p":"data","v":"[62,4,23,1]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":810,"y":220,"wires":[["13c627dcb0c2dcdc"]]},{"id":"01dd4cb6f1d2a978","type":"comment","z":"f9a4695ed5b7732a","name":"Request (FC0x17)","info":"","x":1170,"y":620,"wires":[]},{"id":"71300ee246eb4d22","type":"comment","z":"f9a4695ed5b7732a","name":"Broadcast (FC0x10)","info":"","x":1170,"y":440,"wires":[]},{"id":"bed14879abc62380","type":"buffer-parser","z":"f9a4695ed5b7732a","name":"Parse MB FC16 Request","data":"payload.buffer","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16be","name":"header=>regStartAddr","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16be","name":"header=>regCount","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"int8","name":"header=>regByteCount","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"data","offset":5,"length":-1,"offsetbit":0,"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":1190,"y":480,"wires":[["d8c73147a4506b02","d414925e057f7871"]]},{"id":"d8c73147a4506b02","type":"debug","z":"f9a4695ed5b7732a","name":"FC16 Request","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1440,"y":440,"wires":[]},{"id":"d414925e057f7871","type":"switch","z":"f9a4695ed5b7732a","name":"regStartAddr == 0x9D31","property":"payload.header.regStartAddr","propertyType":"msg","rules":[{"t":"eq","v":"40241","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1470,"y":480,"wires":[["cc04d25c7d046bab"]]},{"id":"cc04d25c7d046bab","type":"switch","z":"f9a4695ed5b7732a","name":"regCount == 0x0009","property":"payload.header.regCount","propertyType":"msg","rules":[{"t":"eq","v":"9","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1460,"y":520,"wires":[["373fbfea2d74cb3d"]]},{"id":"373fbfea2d74cb3d","type":"buffer-parser","z":"f9a4695ed5b7732a","name":"Parse state message","data":"payload.data","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint8","name":"door=>targetPosition","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"door=>currentPosition","offset":3,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"door=>currentState","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"light=>currentState","offset":13,"length":1,"offsetbit":0,"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":1460,"y":560,"wires":[["9386bde41f2c9747","19654d26157c8696"]]},{"id":"9386bde41f2c9747","type":"debug","z":"f9a4695ed5b7732a","name":"Parsed state","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1710,"y":520,"wires":[]},{"id":"581d2bcbc036f110","type":"link in","z":"f9a4695ed5b7732a","name":"Modbus TEST IN","links":["486c0c0425055075","2c87f36ebca4b7d4"],"x":225,"y":300,"wires":[["b0de243bd7d3dea1"]]},{"id":"486c0c0425055075","type":"link out","z":"f9a4695ed5b7732a","name":"","mode":"link","links":["581d2bcbc036f110"],"x":1265,"y":180,"wires":[]},{"id":"656d7340736f932f","type":"function","z":"f9a4695ed5b7732a","name":"Define MB FC16 Request","func":"msg.specification = {\n    \"options\": {\n        \"byteSwap\": false,\n        \"resultType\": \"keyvalue\",\n        \"msgProperty\": \"payload\",\n        \"multipleResult\": false,\n        \"setTopic\": true\n    },\n    \"items\": [ \n        {\n            \"name\": \"slaveid\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": \"slaveid\",\n            \"dataType\": \"msg\"\n        },\n        {\n            \"name\": \"fc\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": 16,\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"regStartAddr\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regStartAddr,\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"regCount\",\n            \"type\": \"uint16be\",\n            \"length\": 1,\n            \"data\": msg.regCount,\n            \"dataType\": \"number\"\n        },\n        {\n            \"name\": \"regByteCount\",\n            \"type\": \"uint8\",\n            \"length\": 1,\n            \"data\": msg.regByteCount,\n            \"dataType\": \"number\"\n        },\n        {  \n            \"name\": \"data\",\n            \"type\": \"buffer\",\n            \"length\": msg.regByteCount,\n            \"data\": msg.data,\n            \"dataType\": \"buffer\"\n        },\n        \n    ]\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1790,"y":180,"wires":[["2c87f36ebca4b7d4"]]},{"id":"2c87f36ebca4b7d4","type":"link out","z":"f9a4695ed5b7732a","name":"","mode":"link","links":["581d2bcbc036f110"],"x":1975,"y":180,"wires":[]},{"id":"192192344ecb3966","type":"inject","z":"f9a4695ed5b7732a","name":"Door closed, light off, idle","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"0","vt":"num"},{"p":"regStartAddr","v":"40241","vt":"num"},{"p":"regCount","v":"9","vt":"num"},{"p":"regByteCount","v":"18","vt":"num"},{"p":"data","v":"[83,0,0,0,64,96,0,0,0,0,0,0,0,0,0,1,0,0]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1510,"y":140,"wires":[["656d7340736f932f"]]},{"id":"a206f943ed9f8b39","type":"change","z":"f9a4695ed5b7732a","name":"set flow.portstates, flow.lightstates","rules":[{"t":"set","p":"#:(volatile)::portstates","pt":"flow","to":"{\"stateCode\":[0,1,2,5,9,10,32,64,128],\"stateStr\":[\"Stopped\",\"Opening\",\"Closing\",\"Move Half\",\"Move Venting\",\"Venting\",\"Open\",\"Closed\",\"Half Open\"]}","tot":"json"},{"t":"set","p":"#:(volatile)::lightstates","pt":"flow","to":"{\"stateCode\":[0,4,16,20],\"stateStr\":[\"Off\",\"Dimming to Off\",\"On\",\"Dimming to On\"]}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":180,"wires":[[]]},{"id":"a84fde3d5fa34250","type":"inject","z":"f9a4695ed5b7732a","name":"Init Door/Light states","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":180,"wires":[["a206f943ed9f8b39"]]},{"id":"19654d26157c8696","type":"function","z":"f9a4695ed5b7732a","name":"Save current state","func":"const portstates = flow.get('portstates', 'volatile');\nconst lightstates = flow.get('lightstates', 'volatile');\n\nvar garagepoort = global.get('garagepoort', 'persistent') || {\n    state: portstates.stateStr[0],\n    position: 0\n};\nvar licht = global.get('licht.garagepoort', 'persistent') || {\n    state: false\n};\n\n// Set new door state\nif (portstates.stateCode.includes(msg.payload.door.currentState)) {\n    if (portstates.stateStr[portstates.stateCode.indexOf(msg.payload.door.currentState)] != garagepoort.state) {\n        garagepoort.state = portstates.stateStr[portstates.stateCode.indexOf(msg.payload.door.currentState)];\n    }\n} else {\n    node.error(\"Unknown door state: \" + msg.payload.door.currentState);\n}\n\n// Set new door position\nif (garagepoort.position != msg.payload.door.currentPosition / 2) {\n    garagepoort.position = msg.payload.door.currentPosition / 2;\n}\n\n// Set new light state\nif (lightstates.stateCode.includes(msg.payload.light.currentState)) {\n    if (licht.state != (lightstates.stateStr[lightstates.stateCode.indexOf(msg.payload.light.currentState)] == \"On\" ||\n                        lightstates.stateStr[lightstates.stateCode.indexOf(msg.payload.light.currentState)] == \"Dimming to On\")) {\n        licht.state = (lightstates.stateStr[lightstates.stateCode.indexOf(msg.payload.light.currentState)] == \"On\" ||\n                       lightstates.stateStr[lightstates.stateCode.indexOf(msg.payload.light.currentState)] == \"Dimming to On\");\n    }\n} else {\n    node.error(\"Unknown light state: \" + msg.payload.light.currentState);\n}\n\n// Save new states\nglobal.set('garagepoort', garagepoort, 'persistent');\nglobal.set('licht.garagepoort', licht, 'persistent');","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1730,"y":560,"wires":[[]]},{"id":"e40bc22e0247fecb","type":"inject","z":"f9a4695ed5b7732a","name":"Door opening, light on, 0%","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"0","vt":"num"},{"p":"regStartAddr","v":"40241","vt":"num"},{"p":"regCount","v":"9","vt":"num"},{"p":"regByteCount","v":"18","vt":"num"},{"p":"data","v":"[83,0,200,0,1,96,0,0,0,0,0,0,0,20,0,1,0,0]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1510,"y":180,"wires":[["656d7340736f932f"]]},{"id":"9be3c86e0f36598d","type":"inject","z":"f9a4695ed5b7732a","name":"Door opening, light on, 2%","props":[{"p":"topic","vt":"str"},{"p":"slaveid","v":"0","vt":"num"},{"p":"regStartAddr","v":"40241","vt":"num"},{"p":"regCount","v":"9","vt":"num"},{"p":"regByteCount","v":"18","vt":"num"},{"p":"data","v":"[85,0,200,4,1,96,0,0,0,0,0,0,0,20,0,1,0,0]","vt":"bin"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1510,"y":220,"wires":[["656d7340736f932f"]]},{"id":"9e0e29280c9f1e93","type":"serial out","z":"f9a4695ed5b7732a","name":"RS485-1 Modbus","serial":"ccba265ed98153e1","x":2470,"y":860,"wires":[]},{"id":"14b45fdf66b2fba2","type":"buffer-parser","z":"f9a4695ed5b7732a","name":"Parse data","data":"payload.data","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint8","name":"counter","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"command","offset":1,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"data","offset":2,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload.data","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"return","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":1430,"y":740,"wires":[["c590619c45a933c6"]]},{"id":"ed1df68a044dec31","type":"debug","z":"f9a4695ed5b7732a","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2050,"y":700,"wires":[]},{"id":"a7924377169d7ec5","type":"function","z":"f9a4695ed5b7732a","name":"Generate response data","func":"// Init response header\nconst header = [msg.payload.data.counter, \"00\", msg.payload.data.command];\nvar response;\n\nswitch(msg.payload.data.command) {\n    case 2: // Busscan\n        response = header.concat([\"05\", \"04\", \"48\", \"16\", \"255\", \"168\", \"69\"]);\n        break;\n    case 3: // Command request\n        response = header.concat([\"01\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\", \"00\",]);\n        break;\n    case 4: // Empty command\n        response = header.concat([\"253\"]);\n        break;\n    default:\n        node.error('Unknown command received: ' + msg.payload.data.command, msg)\n}\n\nmsg.response = Buffer.from(response);\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1830,"y":740,"wires":[["ed1df68a044dec31","e79ea49b32a57a0e"]]},{"id":"c590619c45a933c6","type":"change","z":"f9a4695ed5b7732a","name":"Set Counter","rules":[{"t":"set","p":"#:(volatile)::cmdCounter","pt":"flow","to":"payload.data.counter","tot":"msg"},{"t":"set","p":"#:(volatile)::command","pt":"flow","to":"payload.data.command","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1610,"y":740,"wires":[["a7924377169d7ec5"]]},{"id":"67190339e15be57c","type":"function","z":"f9a4695ed5b7732a","name":"Add timestamp","func":"var d = new Date();\nmsg.timestamp = d.getTime();\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":540,"wires":[["28130398b5b33ddc","6596665f2f0d7825"]]},{"id":"69187ae18d9c993e","type":"function","z":"f9a4695ed5b7732a","name":"Calc timing","func":"var d = new Date();\nvar current_time = d.getTime();\n\nmsg.timing = (current_time - msg.timestamp);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2270,"y":960,"wires":[["6ac86868b1aac8d6"]]},{"id":"6ac86868b1aac8d6","type":"debug","z":"f9a4695ed5b7732a","name":"","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"timing","targetType":"msg","statusVal":"","statusType":"auto","x":2450,"y":960,"wires":[]},{"id":"e5388905e48fa181","type":"comment","z":"f9a4695ed5b7732a","name":"Settings","info":"","x":100,"y":80,"wires":[]},{"id":"cd7d97874dbe88c0","type":"comment","z":"f9a4695ed5b7732a","name":"Request simulation","info":"","x":770,"y":80,"wires":[]},{"id":"d09334570558bd6e","type":"comment","z":"f9a4695ed5b7732a","name":"Broadcast simulation","info":"","x":1470,"y":80,"wires":[]},{"id":"ccba265ed98153e1","type":"serial-port","serialport":"/dev/ttymxc0","serialbaud":"57600","databits":"8","parity":"even","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"1.75","bin":"bin","out":"interbyte","addchar":"","responsetimeout":"10000"}]