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":[]}]

Flow Info

Created 4 years, 7 months ago
Rating: 4 2

Owner

Actions

Rate:

Node Types

Core
  • change (x2)
  • comment (x3)
  • debug (x5)
  • function (x4)
  • inject (x10)
  • switch (x3)
Other
  • tab (x1)

Tags

  • ontime
  • sensors
  • last
  • update
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option