Processing sensor data and structuring node-red-program

for my floor heating system I had to manage quite a few sensors. I put all sensors in one flow and all my different rooms in seperate flows. I used a flow router (input) to bring the sensor data to the rooms.
I actually used the same method for my outputs to actors. All outputs like http request, mqtt, websocket, telegram are in one flow with an output router. This turned out to be very effectiv if you had to add new sensors or outputs.
Attached a basic example:

sensor.txt (16.7 KB)

As the flow was to big to post it here just download the txt file.

1 Like

updated version of sensor object and routing

[{"id":"eab1516f6ab665a5","type":"switch","z":"77a4781684365cf3","name":"flow router","property":"payload.send_to","propertyType":"msg","rules":[{"t":"eq","v":"flow1","vt":"str"},{"t":"eq","v":"flow2","vt":"str"},{"t":"eq","v":"flow3","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":1170,"y":300,"wires":[["5eae49bedbc1e910"],["082010b3df594541"],["c0f2cc238c3f9dc2"]]},{"id":"dfe81d783fd57341","type":"link in","z":"77a4781684365cf3","name":"flow_router_in","links":["b1fb0e92ce39e921","d7c9d7dbb2eeb0f2","7d0c6ad3b4eea62e","71983a5d4481ea31"],"x":1055,"y":300,"wires":[["eab1516f6ab665a5"]]},{"id":"5eae49bedbc1e910","type":"link out","z":"77a4781684365cf3","name":"router out","mode":"link","links":["33c29b5a726ed66c"],"x":1315,"y":260,"wires":[]},{"id":"082010b3df594541","type":"link out","z":"77a4781684365cf3","name":"","links":["30d05c4b5f80a0c2"],"x":1315,"y":300,"wires":[]},{"id":"8721ac1e9a0b8de7","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":110,"y":160,"wires":[["e87591bcd25842d7"]]},{"id":"e87591bcd25842d7","type":"change","z":"77a4781684365cf3","name":"init sensor offset","rules":[{"t":"set","p":"sensoroffset","pt":"flow","to":"0.5","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":160,"wires":[[]]},{"id":"ba8509f4a360bb5e","type":"mqtt in","z":"77a4781684365cf3","name":"Sensor 1 data","topic":"home/ug/Buero/unipi/1/temp/2892844D07000012","qos":"0","datatype":"auto","broker":"36a1d46c.15334c","nl":false,"rap":true,"rh":0,"inputs":0,"x":110,"y":360,"wires":[[]]},{"id":"7d0c6ad3b4eea62e","type":"link out","z":"77a4781684365cf3","name":"","links":["dfe81d783fd57341"],"x":955,"y":300,"wires":[]},{"id":"8b7b671ec9bc2cb7","type":"function","z":"77a4781684365cf3","name":"add routing information to object","func":"let sensordata = msg.payload;\nlet route = {'send_to':'flow3','type':'HZVL'};\nmsg.payload = Object.assign(sensordata, route);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":300,"wires":[["7d0c6ad3b4eea62e"]]},{"id":"9731cf9db29a3c8d","type":"trigger","z":"77a4781684365cf3","name":"time - filter","op1":"","op2":"","op1type":"pay","op2type":"nul","duration":"10","extend":false,"overrideDelay":false,"units":"s","reset":"msg.reset","bytopic":"all","topic":"topic","outputs":1,"x":310,"y":340,"wires":[[]]},{"id":"ebed51f349a2b08b","type":"rbe","z":"77a4781684365cf3","name":"% - filter","func":"deadbandEq","gap":"5","start":"","inout":"out","septopics":false,"property":"payload","topi":"topic","x":300,"y":380,"wires":[[]]},{"id":"415aa050e48993b9","type":"inject","z":"77a4781684365cf3","name":"reset min max","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 00 * * *","once":false,"onceDelay":0.1,"topic":"reset","x":520,"y":260,"wires":[["ce87e1a8c4dbc99e"]]},{"id":"baf13327520f39ea","type":"switch","z":"77a4781684365cf3","name":"!= previous value","property":"payload","propertyType":"msg","rules":[{"t":"neq","v":"","vt":"prev"}],"checkall":"true","repair":false,"outputs":1,"x":330,"y":420,"wires":[[]]},{"id":"42fb581468a416e1","type":"function","z":"77a4781684365cf3","name":"no filter","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":300,"wires":[["ce87e1a8c4dbc99e"]]},{"id":"ce87e1a8c4dbc99e","type":"function","z":"77a4781684365cf3","name":"sensorobject","func":"if (msg.topic === 'reset'){\n    context.set('min',0.0);\n    context.set('max',0.0);\n    return null;\n}\n\n//-------------------------------------------\n\nlet time = Date.now();\nlet sensoroffset = flow.get('sensoroffset');\n//  the next 3 lines to be edited per sensor\nlet sensortype = \"one-wire\";\nlet sensorname = \"HZVL\";\nlet sensorlocation = \"DGWZ\";\n//\nlet sensorrawvalue = parseFloat(msg.payload);\nlet sensorvalue = sensorrawvalue + sensoroffset;\nlet sensorobject = {\"sensorlocation\":sensorlocation,\"sensortype\":sensortype,\"senorname\":sensorname,\"sensortimestamp\":time,\"sensorrawvalue\":sensorrawvalue,\"sensoroffset\":sensoroffset, \"sensorvalue\":sensorvalue};\nminmaxobject = minmax(sensorvalue);\nmsg.payload = Object.assign(sensorobject, minmaxobject);\nreturn msg;\n\n//-------------------------------------------\n\nfunction minmax(value){\n    let x = parseFloat(value);\n    let min = context.get('min');\n    let max = context.get('max');\n\n    if ((min === 0) && (max === 0)){\n        min = x;\n        context.set('min',x);\n        max = x;\n        context.set('max',x);\n    }\n    else {\n        if (x < min){\n            min = x;\n            context.set('min',x);\n        }\n        if (x > max){\n        max = x;\n        context.set('max',x);\n        }\n    }\n    return {'sensormin':min,'sensormax':max};\n}","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nif (context.get(\"min\") === undefined) {\n    context.set(\"min\", 0.0)\n}\n\nif (context.get(\"max\") === undefined) {\n    context.set(\"max\", 0.0)\n}","finalize":"","libs":[],"x":530,"y":300,"wires":[["8b7b671ec9bc2cb7"]]},{"id":"c0f2cc238c3f9dc2","type":"debug","z":"77a4781684365cf3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1370,"y":340,"wires":[]},{"id":"0b17b744ab851705","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25.6","payloadType":"str","x":130,"y":240,"wires":[["42fb581468a416e1"]]},{"id":"5335b411815f998f","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"31.7","payloadType":"str","x":130,"y":280,"wires":[["42fb581468a416e1"]]},{"id":"a432eeb37f671c0c","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20.3","payloadType":"str","x":130,"y":320,"wires":[["42fb581468a416e1"]]},{"id":"36a1d46c.15334c","type":"mqtt-broker","name":"","broker":"10.0.0.43","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"MQTT/Development","birthQos":"0","birthRetain":"true","birthPayload":"online","birthMsg":{},"closeTopic":"MQTT/Development","closeQos":"0","closeRetain":"true","closePayload":"offline","closeMsg":{},"willTopic":"MQTT/Development","willQos":"0","willRetain":"true","willPayload":"disconnected","willMsg":{},"sessionExpiry":""}]

updated version of sensor object and routing:

  • added timestamps to min max values and watchdog

  • sensor location and routing to different flow is more clearly defined

  • init and reset of flows should be organized in a seperate init or reset flow

[{"id":"eab1516f6ab665a5","type":"switch","z":"77a4781684365cf3","name":"flow router","property":"payload.send_to","propertyType":"msg","rules":[{"t":"eq","v":"flow_1","vt":"str"},{"t":"eq","v":"flow_2","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1150,"y":300,"wires":[["5eae49bedbc1e910","f1592a072d390dc7"],["143e848171a56697","741a5693cbf3a09e"]]},{"id":"dfe81d783fd57341","type":"link in","z":"77a4781684365cf3","name":"flow_router_in","links":["b1fb0e92ce39e921","d7c9d7dbb2eeb0f2","7d0c6ad3b4eea62e","71983a5d4481ea31"],"x":1035,"y":300,"wires":[["eab1516f6ab665a5"]]},{"id":"5eae49bedbc1e910","type":"link out","z":"77a4781684365cf3","name":"to_flow_1","mode":"link","links":["04ebd35e3b8e1db7"],"x":1295,"y":260,"wires":[]},{"id":"8721ac1e9a0b8de7","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":110,"y":140,"wires":[["e87591bcd25842d7"]]},{"id":"e87591bcd25842d7","type":"change","z":"77a4781684365cf3","name":"init sensor offset","rules":[{"t":"set","p":"sensoroffset_DGWZ","pt":"flow","to":"0.5","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":140,"wires":[[]]},{"id":"ba8509f4a360bb5e","type":"mqtt in","z":"77a4781684365cf3","name":"Sensor 1 data","topic":"home/ug/Buero/unipi/1/temp/2892844D07000012","qos":"0","datatype":"auto","broker":"36a1d46c.15334c","nl":false,"rap":true,"rh":0,"inputs":0,"x":110,"y":360,"wires":[["e033b10dbd61fed1"]]},{"id":"7d0c6ad3b4eea62e","type":"link out","z":"77a4781684365cf3","name":"","links":["dfe81d783fd57341"],"x":985,"y":300,"wires":[]},{"id":"8b7b671ec9bc2cb7","type":"function","z":"77a4781684365cf3","name":"add routing information to flow_xx","func":"let sensordata = msg.payload;\nlet route = {'send_to':\"flow_1\"};\nmsg.payload = Object.assign(route, sensordata);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":300,"wires":[["7d0c6ad3b4eea62e"]]},{"id":"9731cf9db29a3c8d","type":"trigger","z":"77a4781684365cf3","name":"time - filter","op1":"","op2":"","op1type":"pay","op2type":"nul","duration":"10","extend":false,"overrideDelay":false,"units":"s","reset":"msg.reset","bytopic":"all","topic":"topic","outputs":1,"x":310,"y":340,"wires":[[]]},{"id":"ebed51f349a2b08b","type":"rbe","z":"77a4781684365cf3","name":"% - filter","func":"deadbandEq","gap":"5","start":"","inout":"out","septopics":false,"property":"payload","topi":"topic","x":300,"y":380,"wires":[[]]},{"id":"415aa050e48993b9","type":"inject","z":"77a4781684365cf3","name":"reset min max","props":[{"p":"reset","v":"","vt":"str"}],"repeat":"","crontab":"00 00 * * *","once":false,"onceDelay":0.1,"topic":"","x":340,"y":220,"wires":[["ce87e1a8c4dbc99e"]],"info":"typicaly midnight or midnight between sunday and monday"},{"id":"baf13327520f39ea","type":"switch","z":"77a4781684365cf3","name":"!= previous value","property":"payload","propertyType":"msg","rules":[{"t":"neq","v":"","vt":"prev"}],"checkall":"true","repair":false,"outputs":1,"x":330,"y":420,"wires":[[]]},{"id":"42fb581468a416e1","type":"function","z":"77a4781684365cf3","name":"no filter","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":300,"wires":[["ce87e1a8c4dbc99e"]]},{"id":"ce87e1a8c4dbc99e","type":"function","z":"77a4781684365cf3","name":"sensorobject","func":"if ('reset' in msg){\n    context.set(\"min\", {\"min\":0.0, \"time\":0});\n    context.set(\"max\", {\"max\":0.0, \"time\":0});\n    return null;\n}\n\n//-------------------------------------------\n\nlet time = Date.now();\n//  the next 5 lines to be edited per sensor\nlet timestring = date_time_string(\"full\",time);  // Format full, dayhm, hm, hms\nlet location = \"DGWZ\";\nlet messagetype = \"sensor\";\nlet sensortype = \"enocean\";\nlet sensorname = \"ist\";\n\nlet sensoroffset = flow.get('sensoroffset_DGWZ');\n//\nlet sensorrawvalue = parseFloat(msg.payload);\nlet sensorvalue = sensorrawvalue + sensoroffset;\nlet sensorobject = {\"location\":location,\"messagetype\":messagetype,\"senorname\":sensorname,\"sensortimestamp\":time,\"sensortimestring\":timestring,\"sensorrawvalue\":sensorrawvalue,\"sensoroffset\":sensoroffset, \"sensorvalue\":sensorvalue};\nminmaxobject = minmax(sensorvalue);\nmsg.payload = Object.assign(sensorobject, minmaxobject);\nreturn msg;\n\n//-------------------------------------------\n\nfunction minmax(value){\n    let x = parseFloat(value);\n    let min = context.get('min');\n    let max = context.get('max');\n\n    if ((min.min === 0) && (max.max === 0)){\n        min.min = x;\n        min.time = Date.now();\n        context.set('min',min);\n        max.max = x;\n        context.set('max',max);\n        max.time = Date.now();        \n    }\n    else {\n        if (x < min.min){\n            min.min = x;\n            min.time = Date.now();            \n            context.set('min',min);\n        }\n        if (x > max.max){\n            max.max = x;\n            max.time = Date.now();            \n            context.set('max',max);\n        }\n    }\n    return {'sensormin':min,'sensormax':max};\n}\n\n//-------------------------------------------\n\nfunction date_time_string(type,time){\n    let date =new Date(time);\n    let year = date.getFullYear();\n    let month = (\"0\" + (date.getMonth() + 1)).slice(-2);\n    let day = (\"0\" + date.getDate()).slice(-2);\n    let hours = (\"0\" + date.getHours()).slice(-2);\n    let minutes = (\"0\" + date.getMinutes()).slice(-2);\n    let seconds = (\"0\" + date.getSeconds()).slice(-2);\n    let datetimestring = \"\";\n    switch(type) {\n        case \"full\":\n            datetimestring = day + \"-\" + month + \"-\" + year + \" \" + hours + ':' + minutes + ':' + seconds;  \n        break\n        case \"dayhm\":\n            datetimestring = day + \"-\" + month + \" @ \" + hours + ':' + minutes;  \n        break        \n        case \"hm\": \n            datetimestring = hours + ':' + minutes;  \n        break\n        case \"hms\":\n            datetimestring = hours + ':' + minutes + ':' + seconds; \n        break;\n        default:\n            datetimestring = null;        \n    }\n    return datetimestring;\n}    \n//-------------------------------------------\n","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nif (context.get(\"min\") === undefined) {\n    context.set(\"min\", {\"min\":0.0, \"time\":0});\n}\n\nif (context.get(\"max\") === undefined) {\n    context.set(\"max\", {\"max\":0.0, \"time\":0});\n}","finalize":"","libs":[],"x":530,"y":300,"wires":[["8b7b671ec9bc2cb7"]]},{"id":"0b17b744ab851705","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25.6","payloadType":"str","x":130,"y":240,"wires":[["42fb581468a416e1"]]},{"id":"5335b411815f998f","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"31.7","payloadType":"str","x":130,"y":280,"wires":[["42fb581468a416e1"]]},{"id":"a432eeb37f671c0c","type":"inject","z":"77a4781684365cf3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20.3","payloadType":"str","x":130,"y":320,"wires":[["42fb581468a416e1"]]},{"id":"143e848171a56697","type":"link out","z":"77a4781684365cf3","name":"to_flow_2","mode":"link","links":["95d33b37acbce999"],"x":1295,"y":340,"wires":[]},{"id":"741a5693cbf3a09e","type":"debug","z":"77a4781684365cf3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1390,"y":320,"wires":[]},{"id":"ed6ec4ff9597bc17","type":"inject","z":"77a4781684365cf3","name":"reset","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"send_to\":\"flow_1\",\"location\":\"DGWZ\",\"messagetype\":\"reset\"}","payloadType":"json","x":870,"y":240,"wires":[["7d0c6ad3b4eea62e"]]},{"id":"8a5065838f4ef879","type":"inject","z":"77a4781684365cf3","name":"init all flows","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"send_to\":\"flow_1\",\"location\":\"DGWZ\",\"messagetype\":\"init\"},{\"send_to\":\"flow_2\",\"location\":\"DGSZ\",\"messagetype\":\"init\"}]","payloadType":"json","x":850,"y":200,"wires":[["7d0c6ad3b4eea62e"]]},{"id":"f1592a072d390dc7","type":"debug","z":"77a4781684365cf3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1390,"y":280,"wires":[]},{"id":"e033b10dbd61fed1","type":"trigger","z":"77a4781684365cf3","name":"watchdog timer","op1":"","op2":"sensor dead","op1type":"nul","op2type":"str","duration":"10","extend":true,"overrideDelay":false,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":320,"y":460,"wires":[["dc27178e40c6f16e"]]},{"id":"dc27178e40c6f16e","type":"debug","z":"77a4781684365cf3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":460,"wires":[]},{"id":"36a1d46c.15334c","type":"mqtt-broker","name":"","broker":"10.0.0.43","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"MQTT/Development","birthQos":"0","birthRetain":"true","birthPayload":"online","birthMsg":{},"closeTopic":"MQTT/Development","closeQos":"0","closeRetain":"true","closePayload":"offline","closeMsg":{},"willTopic":"MQTT/Development","willQos":"0","willRetain":"true","willPayload":"disconnected","willMsg":{},"sessionExpiry":""}]