Node-Red as Controller
-
Someone here have a flow working with ACK messages ? And retry feature ? For example 5 retries before failling ?
@Fabien
Please help me understand your requirement.Ack from node to gateway are AFAIK handled by the gateway. So this is not your recquirement.
Then you are maybe looking to send messages from node-red to some nodes with the ack flag set and if you don't get the message back within xx seconds you want to have transmitted it again up to n times.
Is this your requirement?
If yes, what are xx and n?
-
Yes my requierement is to send ACK from node-red controller, wait 1s for the message back from the node and send it 5 times. If it's not ok I let the swtich (only example, it can be another actuator) in the last state. For only one message it's quite easy but when you want to handle multiple message (and sometimes the same command), it's more difficult to handle the queue correctly.
-
@Fabien
Please help me understand your requirement.Ack from node to gateway are AFAIK handled by the gateway. So this is not your recquirement.
Then you are maybe looking to send messages from node-red to some nodes with the ack flag set and if you don't get the message back within xx seconds you want to have transmitted it again up to n times.
Is this your requirement?
If yes, what are xx and n?
-
@FotoFieber do you have some examples of flows that someone could start from?
@gohan
Have you seen my controller flow?I have a multi-protocol setup and use sensors from zwave, homegear, netatmo, mysensors, sonoff, mystrom... All the messages are standardised and everything can talk to everything (in theory, wired via node-red). This setup is quite complex but very flexible. If you want to control mysensors-devices only, this may be to complicated.
Warning: following code was not intended for general usage by anyone else but me. :smile:
Here is a shortened excerpt of my generic mapping code:
[{"id":"c912e7c5.0e2cf8","type":"function","z":"9496189e.de6028","name":"Map Messages to Generic Messages (HA/)","func":"var mqtt2mqtt = \n{\n// Netatmo\n// - Indoor, TV\n\"netatmo/dashboard_data/Temperature\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"tv\", \"type\":\"S_TEMP\",\"index\":0, \"unit\":\"V_TEMP\"},\n\"netatmo/dashboard_data/Humidity\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"tv\", \"type\":\"S_HUM\",\"index\":0, \"unit\":\"V_HUM\"},\n// Haustüre\n\"homegear/1234-5678-9abc/plain/45/1/STATE\":\n {\"destination\":\"\",\"payloadTransformation\":\"OpenClose\",\"floor\":1, \"room\":\"entrance\", \"type\":\"S_DOOR\",\"index\":0, \"unit\":\"V_TRIPPED\"},\n\n// Philio Motion/Door\n// Treppenhaus oben\n// 21.0 C\n\"fhem/ZWave/ZWave_SENSOR_NOTIFICATION_13/temperature\":\n {\"destination\":\"\",\"payloadTransformation\":\"SplitFirst\",\"floor\":1, \"room\":\"staircase\", \"type\":\"S_TEMP\",\"index\":0, \"unit\":\"V_TEMP\"},\n//4 %\n\"fhem/ZWave/ZWave_SENSOR_NOTIFICATION_13/luminance\":\n {\"destination\":\"\",\"payloadTransformation\":\"SplitFirst\",\"floor\":1, \"room\":\"staircase\", \"type\":\"S_LIGHT_LEVEL\",\"index\":0, \"unit\":\"V_LIGHT_LEVEL\"},\n// MyStrom\n\n\"myStrom/Switch1/watt\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":0, \"room\":\"laundry\", \"type\":\"S_LIGHT\",\"index\":0, \"unit\":\"V_POWER\"},\n\"myStrom/Switch1/on\":\n {\"destination\":\"\",\"payloadTransformation\":\"False0True1\",\"floor\":0, \"room\":\"laundry\", \"type\":\"S_LIGHT\",\"index\":1, \"unit\":\"V_STATUS\"},\n\n// Heat Befehle des UI\n\"HEAT/bedroom laura\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"bedroom laura\", \"type\":\"S_HEATER\",\"index\":0, \"unit\":\"V_HEATER_SW\", \"append\":\"/SET\"},\n\n\"HEAT/bedroom parents\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"bedroom parents\", \"type\":\"S_HEATER\",\"index\":0, \"unit\":\"V_HEATER_SW\", \"append\":\"/SET\"},\n\n// Temp/Hum Stube\n\"MYS-NODERED/2/167/1/V_TEMP\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"living room\", \"type\":\"S_TEMP\",\"index\":0, \"unit\":\"V_TEMP\"},\n\"MYS-NODERED/2/167/2/V_HUM\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"living room\", \"type\":\"S_HUM\",\"index\":0, \"unit\":\"V_HUM\"},\n\"MYS-NODERED/2/167/0/V_LEVEL\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"living room\", \"type\":\"S_AIR_QUALITY\",\"index\":0, \"unit\":\"MM_PPM\"},\n // Temp/Hum/CO2 Badezimmer\n\"MYS-NODERED/2/15/1/V_TEMP\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"bathroom\", \"type\":\"S_TEMP\",\"index\":0, \"unit\":\"V_TEMP\"},\n\"MYS-NODERED/2/15/2/V_HUM\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"bathroom\", \"type\":\"S_HUM\",\"index\":0, \"unit\":\"V_HUM\"},\n\"MYS-NODERED/2/15/0/V_LEVEL\":\n {\"destination\":\"\",\"payloadTransformation\":\"\",\"floor\":1, \"room\":\"bathroom\", \"type\":\"S_AIR_QUALITY\",\"index\":0, \"unit\":\"MM_PPM\"},\n\n};\n\n\nfunction payloadTransformation(payload, transformation) {\n if (transformation == \"OnOff\") {\n\t\tif (payload === 0) {\n\t\t\tpayload = 'OFF';\n\t\t} \n\t\telse\n\t\t{\n\t\t\tpayload = 'ON';\n\t\t}\n\t} else if (transformation == \"RemoveBrackets\") {\n\t\tpayload = payload.replace(\"[\",\"\");\n\t\tpayload = payload.replace(\"]\",\"\");\n\t} else if (transformation == \"NetatmoBatteryIndoor\") {\n\t /* Battery range: 6000 ... 4200 */\n // const INDOOR_BATTERY_LEVEL_0 = 5640;/*full*/\n // const INDOOR_BATTERY_LEVEL_1 = 5280;/*high*/\n // const INDOOR_BATTERY_LEVEL_2 = 4920;/*medium*/\n // const INDOOR_BATTERY_LEVEL_3 = 4560;/*low*/\n /* Below 4560: very low */\n if (payload >= 5640) {\n payload = 100;\n } else if (payload < 4560) {\n payload = 0;\n } else {\n payload = 100.0 * (payload - 4560) / (5640 - 4560);\n }\n\t} else if (transformation == \"NetatmoBatteryOutdoor\") {\n /* Battery range: 6000 ... 3600 */\n // const BATTERY_LEVEL_0 = 5500;/*full*/\n // const BATTERY_LEVEL_1 = 5000;/*high*/\n // const BATTERY_LEVEL_2 = 4500;/*medium*/\n //const BATTERY_LEVEL_3 = 4000;/*low*/\n /* below 4000: very low */\n if (payload >= 5500) {\n payload = 100;\n } else if (payload < 4000) {\n payload = 0;\n } else {\n payload = 100.0 * (payload - 4000) / (5500 - 4000);\n }\n\t} else if (transformation == \"HomegearBatteryLOWBAT\") {\n if (payload == '[false]') {\n payload = 100;\n } else {\n payload = 0;\n }\n\t} else if (transformation == \"SplitFirst\") {\n\t var tokens = payload.split(\" \");\n payload = tokens[0];\n\t} else if (transformation == \"StringMotion\") {\n\t upper = payload.toUpperCase();\n\t if (upper.search(\"MOTION\") > -1) {\n\t payload = 1;\n\t }\n\t else\n\t {\n\t payload = 0;\n\t }\n\t} else if (transformation == \"False0True1\") {\n\t payload = payload.replace(\"[\",\"\");\n\t\tpayload = payload.replace(\"]\",\"\");\n\t upper = payload.toUpperCase();\n\t if (upper.search(\"FALSE\") > -1) {\n\t payload = 0;\n\t }\n\t else if (upper.search(\"TRUE\") > -1)\n\t {\n\t payload = 1;\n\t }\n\t} else if (transformation == \"False1True0\") {\n\t payload = payload.replace(\"[\",\"\");\n\t\tpayload = payload.replace(\"]\",\"\");\n\t upper = payload.toUpperCase();\n\t if (upper.search(\"FALSE\") > -1) {\n\t payload = 1;\n\t }\n\t else if (upper.search(\"TRUE\") > -1)\n\t {\n\t payload = 0;\n\t } if (upper.search(\"0\") > -1) {\n\t payload = 1;\n\t }\n\t else if (upper.search(\"1\") > -1)\n\t {\n\t payload = 0;\n\t }\n\t} else if (transformation == \"Off0On1\") {\n\t upper = payload.toUpperCase();\n\t if (upper.search(\"OFF\") > -1) {\n\t payload = 0;\n\t }\n\t else\n\t {\n\t payload = 1;\n\t }\n\t} else if (transformation == \"StringClosed\") { // 1 wenn nicht closed (open)\n\t upper = payload.toUpperCase();\n\t if (upper.search(\"CLOSED\") > -1) {\n\t payload = 0;\n\t }\n\t else\n\t {\n\t payload = 1;\n\t }\n\t} else \tif (transformation == \"OpenClose\") {\n\t\tif (payload == '[true]') {\n\t\t\tpayload = 1;\n\t\t} \n\t\telse\n\t\t{\n\t\t\tpayload = 0;\n\t\t}\n\t}\n\treturn payload;\n}\n\nmapping = mqtt2mqtt[msg.topic];\n\nif (mapping === undefined) {\n\treturn;\n}\nelse if (mapping.destination === undefined)\n{\n var outputMsgs = [];\n for (var j = 0; j < mapping.length; j++){\n \n destination = mapping[j].destination;\n \n if (destination === \"\") {\n destination = \"HA/\" + mapping[j].floor + \"/\" + mapping[j].room + \"/\" + mapping[j].type + \"/\" + mapping[j].index + \"/\" + mapping[j].unit;\n }\n \n //\"floor\":1, \"room\":\"Bathroom\", \"type\":\"S_HUM\",\"index\":0, \"unit\":\"V_HUM\"\n //\n var createmsg = { \n topic: destination, \n value: payloadTransformation(msg.payload,mapping[j].payloadTransformation), \n floor: mapping[j].floor,\n room: mapping[j].room,\n type: mapping[j].type,\n index: mapping[j].index,\n unit: mapping[j].unit,\n append: mapping[j].append,\n timestamp: Date.now(),\n source: msg.topic\n };\n \n \n if(typeof mapping[j].append !== \"undefined\") {\n createmsg.topic += mapping[j].append; \n }\n \n outputMsgs.push({ topic: createmsg.topic,\n payload: createmsg});\n }\n\n return [ outputMsgs ];\n}\nelse\n{\n var outputMsgs = [];\n \n destination = mapping.destination;\n \n if (destination === \"\") {\n destination = \"HA/\" + mapping.floor + \"/\" + mapping.room + \"/\" + mapping.type + \"/\" + mapping.index + \"/\" + mapping.unit;\n }\n \n var createmsg = { \n topic: destination, \n value: payloadTransformation(msg.payload,mapping.payloadTransformation),\n floor: mapping.floor,\n room: mapping.room,\n type: mapping.type,\n index: mapping.index,\n unit: mapping.unit,\n append: mapping.append,\n timestamp: Date.now(),\n source: msg.topic\n };\n\n \n if(typeof mapping.append !== \"undefined\") {\n createmsg.topic += mapping.append; \n }\n \n \n outputMsgs.push({ topic: createmsg.topic,\n payload: createmsg});\n \n return [ outputMsgs ];\n \n}","outputs":1,"noerr":0,"x":430.81597900390625,"y":368.27081298828125,"wires":[[]]}]I persist all the current values in a global state and use alasql to get values from it.
[{"id":"e4db6599.e95a68","type":"mqtt in","z":"82560a8c.4f93a8","name":"HA Messages","topic":"HA/#","qos":"2","broker":"26224260.2d8dde","x":176,"y":68,"wires":[["6c3867ae.e5a808"]]},{"id":"f8f5de85.ce073","type":"debug","z":"82560a8c.4f93a8","name":"","active":false,"console":"false","complete":"true","x":528,"y":70,"wires":[]},{"id":"5bb12e6f.3a271","type":"function","z":"82560a8c.4f93a8","name":"HA State -> context.global.hastate","func":"var hastate = {};\n\nif (context.global.hastate === undefined)\n{\n context.global.hastate = hastate;\n}\n\nhc = hashcode(msg.topic);\n\nmsg.payload.topichc = hc;\n\ncontext.global.hastate[msg.topic] = msg.payload;\n\nreturn msg;\n\nfunction hashcode(str) {\n var hash = 0, i, chr, len;\n if (str.length === 0) return hash;\n for (i = 0, len = str.length; i < len; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return hash;\n}","outputs":1,"noerr":0,"x":641,"y":126,"wires":[[]]},{"id":"f6c2ddcb.2d25b","type":"comment","z":"82560a8c.4f93a8","name":"Restore State from Mongo","info":"","x":181,"y":193,"wires":[]},{"id":"1b115bf9.64b9f4","type":"inject","z":"82560a8c.4f93a8","name":"Restore context.global.hastate from Mongo","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"x":266.25,"y":242,"wires":[["eaaa6edf.b5414"]]},{"id":"98fa3c0.77ffec8","type":"function","z":"82560a8c.4f93a8","name":"restore context.global.hastate","func":"context.global.hastate = {};\n\nvar erg = {};\n\nfor (var item in msg.payload) {\n var payload = msg.payload[item];\n var topic = payload.topic;\n //delete payload.topic;\n delete payload._id;\n payload.topichc = hashcode(topic);\n erg[topic] = payload;\n}\n\nmsg.payload = erg;\n\ncontext.global.hastate = msg.payload;\n\nreturn msg;\n\nfunction hashcode(str) {\n var hash = 0, i, chr, len;\n if (str.length === 0) return hash;\n for (i = 0, len = str.length; i < len; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return hash;\n}","outputs":1,"noerr":0,"x":1074.25,"y":240.75,"wires":[["a7df8317.f084b","8172bfbb.14412"]]},{"id":"c2eaa629.552f18","type":"mongodb in","z":"82560a8c.4f93a8","mongodb":"135c13e7.1b136c","name":"Status aus Mongo lesen","collection":"STATE","operation":"find","x":807.5,"y":240.75,"wires":[["98fa3c0.77ffec8","304fc7dd.6070a8"]]},{"id":"a7df8317.f084b","type":"debug","z":"82560a8c.4f93a8","name":"","active":false,"console":"false","complete":"false","x":1170.75,"y":126,"wires":[]},{"id":"46b56401.cc231c","type":"debug","z":"82560a8c.4f93a8","name":"","active":true,"console":"false","complete":"false","x":565,"y":539,"wires":[]},{"id":"fcde162a.b0bfe8","type":"inject","z":"82560a8c.4f93a8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":143,"y":540,"wires":[["59bf7cc5.978df4"]]},{"id":"59bf7cc5.978df4","type":"function","z":"82560a8c.4f93a8","name":"context.global.hastate","func":"msg.payload = context.global.hastate;\n\nreturn msg;","outputs":1,"noerr":0,"x":367,"y":542,"wires":[["46b56401.cc231c"]]},{"id":"eaaa6edf.b5414","type":"function","z":"82560a8c.4f93a8","name":"limit und skip setzen","func":"msg.limit = 65000;\nmsg.skip = 0;\n\nreturn msg;","outputs":1,"noerr":0,"x":556,"y":243,"wires":[["c2eaa629.552f18"]]},{"id":"8172bfbb.14412","type":"function","z":"82560a8c.4f93a8","name":"HASTATE bereinigen-> älter als 7 Tage","func":"var i=0;\n\nfor (var erg in context.global.hastate) {\n payload = context.global.hastate[erg];\n if ((Date.now() - payload.timestamp)>1000*60*60*24*7) {\n delete context.global.hastate[erg]; \n i++;\n }\n}\n\nmsg.payload = i;\n\nreturn msg;","outputs":1,"noerr":0,"x":448,"y":297,"wires":[["c07188b0.618918"]]},{"id":"1e5a496b.e5cdf7","type":"inject","z":"82560a8c.4f93a8","name":"","topic":"","payload":"","payloadType":"date","repeat":"3600","crontab":"","once":false,"x":151,"y":296,"wires":[["8172bfbb.14412"]]},{"id":"c07188b0.618918","type":"debug","z":"82560a8c.4f93a8","name":"","active":true,"console":"false","complete":"false","x":719,"y":297,"wires":[]},{"id":"6c3867ae.e5a808","type":"json","z":"82560a8c.4f93a8","name":"","x":367.5,"y":130,"wires":[["5bb12e6f.3a271","f8f5de85.ce073"]]},{"id":"304fc7dd.6070a8","type":"debug","z":"82560a8c.4f93a8","name":"","active":true,"console":"false","complete":"false","x":918,"y":377,"wires":[]},{"id":"26224260.2d8dde","type":"mqtt-broker","z":"","broker":"192.168.92.5","port":"1883","clientid":"","usetls":false,"verifyservercert":false,"compatmode":false,"keepalive":"15","cleansession":true,"willTopic":"","willQos":"1","willRetain":"false","willPayload":"","birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":""},{"id":"135c13e7.1b136c","type":"mongodb","z":"","hostname":"192.168.92.5","port":"27017","db":"HASTATE","name":"HASTATE"}] -
With this standardisation, I can write simple adapters. Here is one for imperihome:
[{"id":"83e8531b.e90b4","type":"debug","z":"a5e97fda.4d467","name":"","active":true,"console":"false","complete":"payload","x":851.2857437133789,"y":251.00000286102295,"wires":[]},{"id":"ab451d5a.6cd2e","type":"http in","z":"a5e97fda.4d467","name":"","url":"/LG53/system","method":"get","swaggerDoc":"","x":190.2857437133789,"y":163.00000286102295,"wires":[["bc40a5db.70b1b8"]]},{"id":"7cd541a2.7f6ba","type":"http in","z":"a5e97fda.4d467","name":"","url":"/LG53/devices","method":"get","swaggerDoc":"","x":178.2857437133789,"y":236.00000286102295,"wires":[["fe9ee011.09dfa"]]},{"id":"247f001b.66de7","type":"http in","z":"a5e97fda.4d467","name":"","url":"/LG53/rooms","method":"get","swaggerDoc":"","x":169.2857437133789,"y":313.00000286102295,"wires":[["2ed87d9a.fe1cd2"]]},{"id":"cfc02ef5.b0a42","type":"http response","z":"a5e97fda.4d467","name":"","x":733.2857437133789,"y":194.00000286102295,"wires":[]},{"id":"bc40a5db.70b1b8","type":"function","z":"a5e97fda.4d467","name":"","func":"msg.payload = { \"id\": \"ISS:LG53:03\", \"apiversion\": 1};\nreturn msg;","outputs":1,"noerr":0,"x":431.2857437133789,"y":160.00000286102295,"wires":[["cfc02ef5.b0a42"]]},{"id":"40424d8c.d0dc34","type":"comment","z":"a5e97fda.4d467","name":"Imperihome","info":"","x":171.2857437133789,"y":119.00000286102295,"wires":[]},{"id":"a77d6fd5.c5549","type":"inject","z":"a5e97fda.4d467","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":157.2857437133789,"y":277.00000286102295,"wires":[["2ed87d9a.fe1cd2"]]},{"id":"2ed87d9a.fe1cd2","type":"function","z":"a5e97fda.4d467","name":"rooms from context.global.hastate","func":"var alasql = global.get('alasql');\n\n\nvar ha = context.global.hastate;\nvar disprooms = [\n {room:'bedroom parents',disp:'Eltern'}, \n {room:'bedroom laura', disp:'Laura'},\n {room:'bedroom baba',disp:'Baba'},\n {room:'living room',disp:'Wohnzimmer'},\n {room:'backyard',disp:'Garten hinten'},\n {room:'basement',disp:'Keller'},\n {room:'bathroom',disp:'Bad'},\n {room:'entrance',disp:'Eingang'},\n {room:'kitchen',disp:'Küche'},\n {room:'laundry',disp:'Waschküche'},\n {room:'staircase',disp:'Treppenhaus'},\n {room:'shower',disp:'Dusche'},\n {room:'toilet',disp:'Gäste WC'},\n {room:'tv',disp:'Fernsehraum'}\n ];\n \n//var res = alasql('SELECT [1]->payload FROM ? WHERE [1]->payload->room=?',[ha, room]);\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, disp.[1]->disp disp FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room ORDER BY 1',[ha,disprooms]);\n\n\n\nvar rooms = [];\n\nfor (var room in res) {\n rooms.push( \n {\n \"id\" : 'room_' + hashcode(res[room].room).toString(),\n \"name\" : res[room].disp || res[room].room\n }\n );\n}\n\nvar resp = { \"rooms\": rooms };\n\nmsg.payload = JSON.stringify(resp);\n\n//msg.payload = JSON.stringify(res);\n\n//msg.payload = {\"rooms\": [{ \"id\": \"roomID1\", \"name\": \"Test Room\" }]};\nreturn msg;\n\n\nfunction hashcode(str) {\n var hash = 0, i, chr, len;\n if (str.length === 0) return hash;\n for (i = 0, len = str.length; i < len; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return hash;\n}","outputs":1,"noerr":0,"x":473.2857437133789,"y":287.00000286102295,"wires":[["83e8531b.e90b4","cfc02ef5.b0a42"]]},{"id":"a6a951b8.fedbf","type":"inject","z":"a5e97fda.4d467","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":166.2857437133789,"y":200.00000286102295,"wires":[["fe9ee011.09dfa"]]},{"id":"15947c1c.319ae4","type":"function","z":"a5e97fda.4d467","name":"","func":"var alasql = global.get('alasql');\n\nvar ha = context.global.hastate;\n\nvar device = Number(msg.req.params.deviceID);\nvar startdate = Number(msg.req.params.startdate);\nvar enddate = Number(msg.req.params.enddate);\n\n\n//var startdate = 1463992263701;\n//var enddate = 1463992263701;\n\nvar topic = alasql('SELECT VALUE ha.[1]->topic FROM ? ha WHERE ha.[1]->topichc=?',[ha,device]);\n\nmsg.topic = topic;\nmsg.startdate = startdate;\nmsg.enddate = enddate;\n\nreturn msg;","outputs":1,"noerr":0,"x":511.2857437133789,"y":683.000002861023,"wires":[["e65fbae2.f30598"]]},{"id":"7331dd31.45cae4","type":"http in","z":"a5e97fda.4d467","name":"ISS Rest Actions History","url":"/LG53/devices/:deviceID/:paramKey/histo/:startdate/:enddate","method":"get","swaggerDoc":"","x":159.2857437133789,"y":611.000002861023,"wires":[["15947c1c.319ae4"]]},{"id":"8746e45c.4cd538","type":"http response","z":"a5e97fda.4d467","name":"","x":716.2857437133789,"y":534.000002861023,"wires":[]},{"id":"e6416541.255858","type":"inject","z":"a5e97fda.4d467","name":"","topic":"deviceid","payload":"1897446547","payloadType":"str","repeat":"","crontab":"","once":false,"x":319.2857437133789,"y":696.000002861023,"wires":[["15947c1c.319ae4"]]},{"id":"eb3d187d.5eae98","type":"influxdb in","z":"a5e97fda.4d467","influxdb":"ab6501aa.2f69a","name":"Get History from Influx","query":"","x":945.2857437133789,"y":758.000002861023,"wires":[["d55f902c.1a3b4"]]},{"id":"e65fbae2.f30598","type":"function","z":"a5e97fda.4d467","name":"Select History","func":"measurement = msg.topic.split('/').join('.');\n\nmsg.query = \"SELECT time, value FROM /\" + measurement + \"/ WHERE time >= \" + msg.startdate + \"ms AND time <= \" + msg.enddate +\"ms ORDER BY time\";\n\nreturn msg;","outputs":1,"noerr":0,"x":722.2857437133789,"y":760.000002861023,"wires":[["eb3d187d.5eae98","8b54b1c8.95d46"]]},{"id":"c01efb63.763dc8","type":"inject","z":"a5e97fda.4d467","name":"","topic":"deviceid","payload":"1897446547","payloadType":"str","repeat":"","crontab":"","once":false,"x":340.2857437133789,"y":768.000002861023,"wires":[["64f1c1c5.dbb0d"]]},{"id":"64f1c1c5.dbb0d","type":"function","z":"a5e97fda.4d467","name":"","func":"msg.topic = \"HA/0/bedroom baba/S_TEMP/0/V_TEMP\";\n\nmsg.startdate = 1463993343938;\nmsg.enddate = 1464166145880;\n \nreturn msg;","outputs":1,"noerr":0,"x":529.2857437133789,"y":758.000002861023,"wires":[["e65fbae2.f30598"]]},{"id":"6ca3314f.2e221","type":"debug","z":"a5e97fda.4d467","name":"","active":false,"console":"false","complete":"false","x":1262.285743713379,"y":758.000002861023,"wires":[]},{"id":"8b54b1c8.95d46","type":"debug","z":"a5e97fda.4d467","name":"","active":false,"console":"false","complete":"query","x":955.2857437133789,"y":847.000002861023,"wires":[]},{"id":"d55f902c.1a3b4","type":"function","z":"a5e97fda.4d467","name":"Convert to Imperihome Format","func":"erg = msg.payload[0];\n\nvar resp = [];\n\nfor (var i in erg) {\n var mesDate = new Date(erg[i].time);\n \n resp.push( \n {\n \"value\" : erg[i].value,\n \"date\" : mesDate.getTime()\n }\n );\n}\n\nmsg.payload = {\"values\": resp };\n\nreturn msg;","outputs":1,"noerr":0,"x":979.2857437133789,"y":634.000002861023,"wires":[["6ca3314f.2e221","8746e45c.4cd538"]]},{"id":"2003094c.594996","type":"debug","z":"a5e97fda.4d467","name":"","active":true,"console":"false","complete":"req.params","x":434.2857437133789,"y":471.00000286102295,"wires":[]},{"id":"7eb92db4.13ccf4","type":"http in","z":"a5e97fda.4d467","name":"ISS Rest Actions","url":"/LG53/devices/:deviceID/action/:actionName/:actionParam","method":"get","swaggerDoc":"","x":129.2857437133789,"y":469.00000286102295,"wires":[["2003094c.594996","70c707e7.138c18"]]},{"id":"70c707e7.138c18","type":"function","z":"a5e97fda.4d467","name":"","func":"var alasql = global.get('alasql');\n\nvar ha = context.global.hastate;\n\nvar device = Number(msg.req.params.deviceID);\n\nvar topic = alasql('SELECT VALUE ha.[1]->topic FROM ? ha WHERE ha.[1]->topichc=?',[ha,device]);\n\nvar output = {};\n\noutput.topic = \"imperihome/\"+topic+\"/\" + msg.req.params.actionName;\noutput.payload = msg.req.params.actionParam;\n\nreturn output;","outputs":1,"noerr":0,"x":285.2857437133789,"y":543.000002861023,"wires":[["59c69af0.4ef8e4"]]},{"id":"59c69af0.4ef8e4","type":"debug","z":"a5e97fda.4d467","name":"","active":true,"console":"false","complete":"false","x":455.2857437133789,"y":541.000002861023,"wires":[]},{"id":"fe9ee011.09dfa","type":"function","z":"a5e97fda.4d467","name":"devices from context.global.hastate","func":"var alasql = global.get('alasql');\n\nvar devices = [];\n\n\nvar ha = context.global.hastate;\nvar disprooms = [\n {room:'bedroom parents',disp:'Eltern'}, \n {room:'bedroom laura', disp:'Laura'},\n {room:'bedroom baba',disp:'Baba'},\n {room:'living room',disp:'Wohnzimmer'},\n {room:'backyard',disp:'Garten hinten'},\n {room:'basement',disp:'Keller'},\n {room:'bathroom',disp:'Bad'},\n {room:'entrance',disp:'Eingang'},\n {room:'kitchen',disp:'Küche'},\n {room:'laundry',disp:'Waschküche'},\n {room:'staircase',disp:'Treppenhaus'},\n {room:'shower',disp:'Dusche'},\n {room:'toilet',disp:'Gäste WC'},\n {room:'tv',disp:'Fernsehraum'}\n ];\n \n\n// Tür/Fenstersensoren\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_DOOR\" ORDER BY 1',[ha,disprooms]);\n\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"FT\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevDoor\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"Tripped\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// Motion\n//var res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_MOTION\" ORDER BY 1',[ha,disprooms]);\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx, ABS(DATEDIFF(Minute, ha.[1]->`timestamp`,NOW())) minuten FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_MOTION\" ORDER BY 1',[ha,disprooms]);\n\n\nfor (var device in res) {\n if (res[device].minuten > 15) {\n motion = 0; \n } else\n {\n motion = 1;\n }\n \n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"MO\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevMotion\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"Tripped\",\"value\": motion, \"graphable\": true}]\n }\n );\n}\n\n// Temperatur\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_TEMP\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Temp\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevTemperature\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// Feuchtigkeit\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_HUM\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Hyg\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevHygrometry\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// CO2\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_AIR_QUALITY\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"CO2\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevCO2\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// BARO\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_BARO\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Luftdruck\",\n \"type\" : \"DevPressure\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// Licht\n\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_LIGHT_LEVEL\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Licht\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevLuminosity\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert,\"unit\":\"%\", \"graphable\": true}]\n }\n );\n}\n\n// Noise\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_SOUND\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Lärm\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevNoise\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"value\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n// S_LIGHT -> V_POWWER, V_STATUS\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_LIGHT\" AND [1]->`unit` = \"V_STATUS\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Schalter\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevSwitch\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"Status\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\nvar res = alasql('SELECT DISTINCT ha.[1]->room room, ha.[1]->`topichc` srcid, ha.[1]->`value` wert, disp.[1]->disp disp, ha.[1]->`index` idx FROM ? ha LEFT JOIN ? disp ON ha.[1]->room = disp.[1]->room WHERE [1]->`type` = \"S_LIGHT\" AND [1]->`unit` = \"V_POWER\" ORDER BY 1',[ha,disprooms]);\n\nfor (var device in res) {\n devices.push( \n {\n \"id\" : res[device].srcid,\n \"name\" : \"Watt\" + res[device].idx + \" \" + (res[device].disp || res[device].room),\n \"type\" : \"DevElectricity\", \n \"room\" : 'room_' + hashcode(res[device].room).toString(),\n \"params\": [{\"key\": \"Watts\",\"value\": res[device].wert, \"graphable\": true}]\n }\n );\n}\n\n\nvar resp = { \"devices\": devices };\n\nmsg.payload = JSON.stringify(resp);\n\n//msg.payload = JSON.stringify(res);\n\n//msg.payload = {\"rooms\": [{ \"id\": \"roomID1\", \"name\": \"Test Room\" }]};\nreturn msg;\n\n\nfunction hashcode(str) {\n var hash = 0, i, chr, len;\n if (str.length === 0) return hash;\n for (i = 0, len = str.length; i < len; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0; // Convert to 32bit integer\n }\n return hash;\n}","outputs":1,"noerr":0,"x":449,"y":222,"wires":[["cfc02ef5.b0a42"]]},{"id":"ab6501aa.2f69a","type":"influxdb","z":"a5e97fda.4d467","hostname":"127.0.0.1","port":"8086","database":"HA","name":"Storage for HA"}]New sensors appear with this setup automagically in imperihome.
-
@FotoFieber
I like your approach to a Node Red implementation of a home automation controller for MySensors. I understand that the code has grown to the point where it will no longer fit in a post. Could you please upload your code to your GitHub so that the whole program is visible?
I understand that your code may not be up to date for the latest version but what I've seen of your code has already helped to improve my MySensors 2.3.0 compatible version.Thank you.
-
@FotoFieber
I like your approach to a Node Red implementation of a home automation controller for MySensors. I understand that the code has grown to the point where it will no longer fit in a post. Could you please upload your code to your GitHub so that the whole program is visible?
I understand that your code may not be up to date for the latest version but what I've seen of your code has already helped to improve my MySensors 2.3.0 compatible version.Thank you.
FotoFieber has been kind enough to post his Node-Red controller on GitHub at: https://github.com/FotoFieber/MySensorsNodeRedController
The direct link to the code to import is: https://raw.githubusercontent.com/FotoFieber/MySensorsNodeRedController/master/README.md
Thank you very much @FotoFieber !
Once I complete a few of the pieces added from his code, I'll post mine as well but it will be a few weeks as I'm traveling now.
-
FotoFieber has been kind enough to post his Node-Red controller on GitHub at: https://github.com/FotoFieber/MySensorsNodeRedController
The direct link to the code to import is: https://raw.githubusercontent.com/FotoFieber/MySensorsNodeRedController/master/README.md
Thank you very much @FotoFieber !
Once I complete a few of the pieces added from his code, I'll post mine as well but it will be a few weeks as I'm traveling now.
-
@chisight I attempted to use this code but node-red would not let me import. Checked the json online and it does not appear to be valid.
A little clean up in notepad++ and I have a working flow!
I made some modifcations (of course) for my setup. The main one is the ability to get the sensor value type. Initially the code in the Parse node did:// msg.topic = context.global.MYS.TOPIC_PREFIX + '/' + msg.controller + ' / ' + msg.nodeId + ' / ' + msg.childSensorId + ' / ' + msg.subTypeString; var tokens = msg.topic.split('/'); msg.rawData = tokens; if(tokens.length >= 5) { msg.controller = parseInt(tokens[1]); msg.nodeId = parseInt(tokens[2]); msg.childSensorId = parseInt(tokens[3]); msg.subTypeString = tokens[4]; msg.subType = context.global.MYS.VNum(msg.subTypeString); msg.command = 1; // SET msg.acknowledge = 0; // no ack as default } return msg;I modified the msg.subType to use:
msg.subType = context.global.MYS.VString(msg.subTypeString);Now to set this up to write to my DB.
Many thanks to @FotoFieber and @chisight -
A little clean up in notepad++ and I have a working flow!
I made some modifcations (of course) for my setup. The main one is the ability to get the sensor value type. Initially the code in the Parse node did:// msg.topic = context.global.MYS.TOPIC_PREFIX + '/' + msg.controller + ' / ' + msg.nodeId + ' / ' + msg.childSensorId + ' / ' + msg.subTypeString; var tokens = msg.topic.split('/'); msg.rawData = tokens; if(tokens.length >= 5) { msg.controller = parseInt(tokens[1]); msg.nodeId = parseInt(tokens[2]); msg.childSensorId = parseInt(tokens[3]); msg.subTypeString = tokens[4]; msg.subType = context.global.MYS.VNum(msg.subTypeString); msg.command = 1; // SET msg.acknowledge = 0; // no ack as default } return msg;I modified the msg.subType to use:
msg.subType = context.global.MYS.VString(msg.subTypeString);Now to set this up to write to my DB.
Many thanks to @FotoFieber and @chisight@wergeld said in Node-Red as Controller:
A little clean up in notepad++ and I have a working flow!
You need to import the raw file, not the "formatted" one that github presents. You can also download the .zip or git clone it.
This is all @FotoFieber, I did nothing but request they post it for us.
Thank you for the notes on the updates that you did.
-
@wergeld said in Node-Red as Controller:
A little clean up in notepad++ and I have a working flow!
You need to import the raw file, not the "formatted" one that github presents. You can also download the .zip or git clone it.
This is all @FotoFieber, I did nothing but request they post it for us.
Thank you for the notes on the updates that you did.
@chisight I had done the raw file import. Issue I was facing was with the single/double quotes being interchanged. Easy fix. I am not sure if my "fix" for the VString was necessary. I just wanted to see what each sensor value was going to return. I need to get my DB setup a little better (using SQL Server as I cant stand the rigidness of time-series DBs like Influx).
-
@chisight I had done the raw file import. Issue I was facing was with the single/double quotes being interchanged. Easy fix. I am not sure if my "fix" for the VString was necessary. I just wanted to see what each sensor value was going to return. I need to get my DB setup a little better (using SQL Server as I cant stand the rigidness of time-series DBs like Influx).
@wergeld Odd, I went to the raw file, hit select all in Firefox and right click copy, then went to Node Red and did the import from clipboard, no edits. Maybe it's something related to Windows, I did mine through Debian.
Still, good to know that some paths need the single/double quotes repaired so thank you.