Utility nodes for sensors and wifi devices
These are some utility nodes I use very day for sensors and wifi devices:
- calculate ontime: how long a switch was on, how many times it got turned on
- last update: show when the last message arrived from a sensor and how much time elapsed since last update.
Please check the comment nodes in the flow for each example where I put more description.
I have covered this in details in the following video: https://youtu.be/UotFHkIXpnU
[{"id":"17f6ed58.217203","type":"tab","label":"Toolbox","disabled":false,"info":""},{"id":"956531c1.1fa3e","type":"comment","z":"17f6ed58.217203","name":"Sensor on time deluxe","info":"Input data:\n- msg.payload can be 1/0, on/off (in any case), or true/false\n- msg.topic: \"update\" refresh the calculation (every second) anything else is treated as data\n- output is an object:\n\nmsg.payload.ontime.hour: number of hours the device was on\nmsg.payload.ontime.hour: number of minutes (within the hour) the device was on\nmsg.payload.ontime.sec: number of seconds (within the minute) the device was on\nmsg.payload.ontime.totalmin: total number of minutes the device was on\nmsg.payload.ontime.totalsec: total number of seconds the device was on\nmsg.payload.ontime.text: total number of time the device was on in HH:MM:SS format\nmsg.payload.ontime.text2: total number of time the device was on in HHhMMmSSs format\nmsg.payload.switchcount: total of times the device was turned on\n","x":120,"y":340,"wires":[]},{"id":"956366af.8535c8","type":"function","z":"17f6ed58.217203","name":"On time counter deluxe","func":"let cumulative = true;\nlet now = new Date();\nlet uptime = context.get(\"uptime\");\nif (uptime===undefined) {\n uptime = 0;\n}\nlet switchcount = context.get(\"switchcount\");\nif (switchcount===undefined) {\n switchcount = 0;\n}\nlet ontime = context.get(\"ontime\");\nlet returndata = false;\n\nif (msg.topic===\"info\") {\n returndata = true;\n}\n\nif (msg.topic===\"update\") {\n // calculate uptime\n if ((ontime===undefined)||(ontime===null)) {\n //node.status({fill:\"grey\",shape:\"dot\",text:\"no data\"}); \n } else {\n uptime+=Math.floor((now-ontime)/1000); \n node.status({fill:\"green\",shape:\"ring\",text:\"On | \"+switchcount+\" | \"+uptime +\" sec\"}); \n returndata = true;\n }\n} else {\n if (msg.topic===\"reset\") {\n // Reset the counter\n uptime=0;\n switchcount=0;\n context.set(\"uptime\", uptime);\n //context.set(\"ontime\", null);\n context.set(\"switchcount\",switchcount);\n node.status({fill:\"grey\",shape:\"dot\",text:\"Off | \"+switchcount+\" | \"+uptime +\" sec\"});\n } else {\n // any other case, device on/off data was received\n if ((parseInt(msg.payload)===1)||(String(msg.payload).toLowerCase()===\"on\")||(msg.payload===true)) {\n // device was turned on\n if (ontime===null||ontime===undefined) {\n context.set(\"ontime\",now);\n switchcount++;\n context.set(\"switchcount\",switchcount);\n }\n }\n if ((parseInt(msg.payload)===0)||(String(msg.payload).toLowerCase()===\"off\")||(msg.payload===false)) {\n // device was turned off\n if (ontime===null) {\n // no data on when the device was turned on\n uptime = 0;\n } else {\n // calculate the ontime based on when the device was turned on\n if (!cumulative) {\n uptime = 0;\n } else {\n uptime+=Math.floor((now-ontime)/1000); \n }\n }\n context.set(\"uptime\", uptime);\n context.set(\"ontime\", null);\n node.status({fill:\"grey\",shape:\"dot\",text:\"Off | \"+switchcount+\" | \"+uptime +\" sec\"});\n returndata = true;\n }\n }\n}\nif (returndata) {\n let ontime_s = uptime % 60;\n let ontime_m = Math.floor((uptime % 3600) / 60);\n let ontime_h = Math.floor(uptime / 3600);\n return [ {\n topic: msg.topic, \n payload: { \n \"ontime\": {\n \"hour\": ontime_h, \n \"min\": ontime_m, \n \"sec\": ontime_s,\n \"totalmin\": Math.floor(uptime / 60),\n \"totalsec\": uptime,\n \"text\": \"\" + ontime_h + \":\" + String(\"0\" + ontime_m).slice(-2) + \":\" + String(\"0\" + ontime_s).slice(-2),\n \"text2\": \"\" + ontime_h + \"h\" + String(\"0\" + ontime_m).slice(-2) + \"m\" + String(\"0\" + ontime_s).slice(-2) + \"s\"\n },\n \"switchcount\": switchcount\n } \n } ];\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":570,"y":460,"wires":[["afe53597.8915d8"]]},{"id":"ffefa1ff.d3fdc","type":"inject","z":"17f6ed58.217203","name":"On","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"str","x":130,"y":460,"wires":[["956366af.8535c8"]]},{"id":"5ed97669.4c2f08","type":"inject","z":"17f6ed58.217203","name":"Off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"str","x":130,"y":500,"wires":[["956366af.8535c8"]]},{"id":"402bddc2.fa89d4","type":"inject","z":"17f6ed58.217203","name":"Reset","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reset","payload":"","payloadType":"date","x":130,"y":540,"wires":[["956366af.8535c8"]]},{"id":"963b7111.fd703","type":"inject","z":"17f6ed58.217203","name":"Update","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"update","payload":"","payloadType":"date","x":300,"y":400,"wires":[["956366af.8535c8"]]},{"id":"755ad947.1ee298","type":"debug","z":"17f6ed58.217203","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1110,"y":500,"wires":[]},{"id":"68a8b718.2a3518","type":"comment","z":"17f6ed58.217203","name":"Sensor on time calculation","info":"Input data:\n- msg.payload can be 1/0, on/off (in any case), or true/false\n- msg.topic: \"update\" refresh the calculation (every second) anything else is treated as data\n- output msg.payload: number of second since the device got turned on","x":130,"y":40,"wires":[]},{"id":"5ce82a82.d7ac34","type":"inject","z":"17f6ed58.217203","name":"Sensor on","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"mysensor","payload":"on","payloadType":"str","x":140,"y":100,"wires":[["548f872e.1edfe8"]]},{"id":"787dce99.0214a","type":"inject","z":"17f6ed58.217203","name":"Sensor off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"mysensor","payload":"off","payloadType":"str","x":140,"y":140,"wires":[["548f872e.1edfe8"]]},{"id":"ba4b9f3e.02874","type":"inject","z":"17f6ed58.217203","name":"Update","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"update","payload":"","payloadType":"date","x":140,"y":220,"wires":[["548f872e.1edfe8"]]},{"id":"548f872e.1edfe8","type":"function","z":"17f6ed58.217203","name":"On time calculation","func":"let store = context.get(\"store\") || {};\nlet now = new Date();\n\nif (msg.topic===\"update\") {\n // This is the part of the flow which updates the state in the context \n if (store.state===undefined) {\n // we did not get data from the sensor yet\n node.status({fill:\"grey\",shape:\"dot\",text:\"no data\"}); \n return;\n } else {\n if (!store.state) {\n // sensor is off, nothing to do\n return;\n } else {\n let ontime=Math.floor((now-store.laston)/1000); \n node.status({fill:\"green\",shape:\"ring\",text:\"On, \"+ontime +\" sec\"}); \n return [{topic: \"ontime\", payload: ontime}];\n }\n }\n\n \n} else {\n // This is the part which evaluates the state changes\n if ((parseInt(msg.payload)===1)||(String(msg.payload).toLowerCase()===\"on\")||(msg.payload===true)) {\n if (store.state===undefined||!store.state) {\n store.state = true;\n store.laston = now;\n node.status({fill:\"green\",shape:\"ring\",text:\"On\"});\n }\n }\n if ((parseInt(msg.payload)===0)||(String(msg.payload).toLowerCase()===\"off\")||(msg.payload===false)) {\n store.state = false;\n node.status({fill:\"grey\",shape:\"dot\",text:\"Off\"});\n }\n context.set(\"store\", store);\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":140,"wires":[["b397d8ba.f3d7d8","5e40e98b.169188"]]},{"id":"b397d8ba.f3d7d8","type":"debug","z":"17f6ed58.217203","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":100,"wires":[]},{"id":"26d003bd.7daadc","type":"comment","z":"17f6ed58.217203","name":"Last Update","info":"Both nodes accept any input message\n\nFrom last update just displays the date and time in the status when the message was received\n\nTime since last update: displayed the time elapsed since the last update in days, hours, minutes and seconds format, and also outputs in msg.sinceupate property the number of seconds since the last update.","x":90,"y":680,"wires":[]},{"id":"4ed1876e.ea5b68","type":"function","z":"17f6ed58.217203","name":"From last update","func":"var devicename = \"Sonoff1\";\n\nvar temp = global.get(devicename+\"_wifi\");\nvar current = new Date();\nif (temp!== undefined && temp!==null) {\n msg.payload = current.getTime() - temp;\n global.set(devicename+\"_wifi\",current.getTime());\n} else {\n msg.payload = \"\";\n global.set(devicename+\"_wifi\",current.getTime());\n}\n\n// Update the status with current timestamp\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Last update: \"+dd + \".\" + mm + \".\" + yyyy + \" \" + hh + \":\" + mmm + \":\" + ss});\n\nmsg.formattedtime = dd + \".\" + mm + \".\" + yyyy + \" \" + hh + \":\" + mmm + \":\" + ss;\n\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":740,"wires":[[]]},{"id":"c6cc1b5d.d48b18","type":"function","z":"17f6ed58.217203","name":"Time since last update","func":"let temp = context.get(\"store\");\nlet current = new Date();\nmsg.payload = \"No data\";\n\nif (msg.topic===\"update\") {\n if (temp!==undefined) {\n current = current - temp;\n current = Math.floor(current/1000);\n msg.sinceupdate = current;\n var minute = Math.floor(current/60);\n var hour = Math.floor(minute/60);\n var day = Math.floor(hour/24);\n if (current>24*60*60) {\n msg.payload = \"Last update \" + day + \" days, \" + hour%24 + \" hours, \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else if (current>60*60) {\n msg.payload = \"Last update \" + hour%24 + \" hours, \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else if (current>60) {\n msg.payload = \"Last update \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else {\n msg.payload = \"Last update \" + current%60 + \" seconds ago\";\n }\n \n node.status({fill:\"blue\",shape:\"ring\",text:msg.payload});\n return msg;\n } else {\n node.status({fill:\"grey\",shape:\"dot\",text:\"no data\"});\n }\n} else {\n context.set(\"store\",current);\n return msg;\n}\n\n\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":440,"y":820,"wires":[["d3ce8b22.373cb8"]]},{"id":"5638a5bc.f4c2bc","type":"inject","z":"17f6ed58.217203","name":"Update","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"update","payload":"","payloadType":"date","x":140,"y":860,"wires":[["c6cc1b5d.d48b18"]]},{"id":"59036357.0217ec","type":"inject","z":"17f6ed58.217203","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":780,"wires":[["4ed1876e.ea5b68","c6cc1b5d.d48b18"]]},{"id":"5e40e98b.169188","type":"switch","z":"17f6ed58.217203","name":"Reached 300 sec","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"300","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":670,"y":180,"wires":[["bda9c847.bf6318"]]},{"id":"bda9c847.bf6318","type":"change","z":"17f6ed58.217203","name":"Warning","rules":[{"t":"set","p":"payload","pt":"msg","to":"This is running too long","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":880,"y":180,"wires":[["d727a309.91d11"]]},{"id":"d727a309.91d11","type":"debug","z":"17f6ed58.217203","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1070,"y":180,"wires":[]},{"id":"c9e95dc3.1500d","type":"inject","z":"17f6ed58.217203","name":"Daily extract","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 00 * * *","once":false,"onceDelay":0.1,"topic":"info","payload":"","payloadType":"date","x":280,"y":600,"wires":[["956366af.8535c8"]]},{"id":"afe53597.8915d8","type":"switch","z":"17f6ed58.217203","name":"Daily update","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"info","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":830,"y":460,"wires":[["db7b5cb0.f8706","a8b37cdc.6f2e3"],["755ad947.1ee298"]]},{"id":"db7b5cb0.f8706","type":"debug","z":"17f6ed58.217203","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1110,"y":440,"wires":[]},{"id":"a8b37cdc.6f2e3","type":"change","z":"17f6ed58.217203","name":"Reset stats","rules":[{"t":"set","p":"topic","pt":"msg","to":"reset","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":380,"wires":[["956366af.8535c8"]]},{"id":"d3ce8b22.373cb8","type":"switch","z":"17f6ed58.217203","name":"Off for more than 5 minutes","property":"sinceupdate","propertyType":"msg","rules":[{"t":"eq","v":"300","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":740,"y":820,"wires":[["42439c3b.a38184"]]},{"id":"42439c3b.a38184","type":"debug","z":"17f6ed58.217203","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1010,"y":820,"wires":[]}]