Shelly Command Centre

Using Shelly devices with MQTT (i.e. not cloud-connected)? This flow will listen to incoming messages and do the following:

  • register the device in memory (global context)
  • record various details for the device like IP address, MAC address, firmware
  • monitor device data (e.g. dimming level, power consumption etc.)
  • monitor device up / down status
  • display the whole lot in Node-RED dashboard

There should be zero setup to do, except obviously setting the MQTT server to point to your own.

It listens to periodic messages from your Shelly device over MQTT (on the default Shelly topic) and starts populating the dashboard.

So far I've added support for Shelly Dimmer 2 and Shelly EM as these are the devices I currently own. But because Shelly MQTT topics are well-structured, it's really easy to add different data types for other Shellies. You just need to change the "Get Other Measurements" node and add the relevant data type, then change the Dashboard template node to add it in there. Should be self-explanatory and you can copy from the existing fields.

[{"id":"d5cd56fa.a9aae8","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/light/0/status","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":280,"y":3240,"wires":[["e86d79a2.acca98","7fdf4656.9f5f78"]]},{"id":"b379cc61.bd23a","type":"comment","z":"a09201df.15c79","name":"Shelly Monitoring (instructions & spec)","info":"Shelly Monitoring\n\n\nINSTRUCTIONS FOR ADDING DEVICE\nFor each device type (e.g. dimmer2, EM, etc.) we need to add an MQTT input. Set the MQTT path to capture just one of the multiple messages from each device.\n\nNow change the \"get other measurements\" function to store whatever data you want to store, and change the dashboard template to display this too.\n\nWe want to show all shellies;\n- link to web int\n- connection status (some watchdog monitoring)\n- brightness (shellies/gardenpillar/light/0/status payload.brightness)\n- switch state (shellies/gardenwall/light/0 payload:on|off)\n- wifi signal strength\n- temperature (shellies/gardenpillar/temperature payload)\n- over-temp bool (shellies/gardenpillar/overtemperature payload:0)\n- overload bool (shellies/gardenpillar/overload payload: 0)\n- loaderror bool (shellies/gardenpillar/loaderror payload: 0)\n- power (shellies/gardenpillar/light/0/power payload:0.00)\n- energy (shellies/gardenpillar/light/0/energy payload: number)\n","x":240,"y":3160,"wires":[]},{"id":"903ab56b.c9dd18","type":"trigger","z":"a09201df.15c79","name":"Watchdog trigger","op1":"","op2":"down","op1type":"nul","op2type":"str","duration":"65","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"topic","topic":"topic","outputs":1,"x":550,"y":3240,"wires":[["e155b367.a5675"]]},{"id":"e155b367.a5675","type":"function","z":"a09201df.15c79","name":"Up/Down Status -> Mem / auto device add","func":"d = new Date().toLocaleDateString(); t = new Date().toLocaleTimeString();\n\n// create the shellies object if it doesn't exist\nif (typeof global.get(\"home.shellies\") == 'undefined') global.set(\"home.shellies\", {})\n\nvar shellies = global.get(\"home.shellies\") // get light fixtures object from memory\nvar deviceID = msg.topic.split(\"/\")[1];         // get the shelly device ID (or MQTT prefix if set)\n\nif (shellies.hasOwnProperty(deviceID)) { // if device exists in Node RED memory\n    if (msg.payload == \"down\") {    // does trigger node say this device is down?\n        // now check if it was previously down, if so, do nothing. if not, record down_since datetime and set state to down\n        if (shellies[deviceID].state == \"up\") {\n            shellies[deviceID].state = \"down\"\n            shellies[deviceID].down_since_date = d\n            shellies[deviceID].down_since_time = t\n        }\n    } else { // IT'S UP\n        // now check if it was previously up, if so, update the last_seen datetime. also record current data\n        // if not, update last_seen AND up_since. set down_since to null\n        if (shellies[deviceID].state == \"up\") {\n            shellies[deviceID].last_seen_date = d\n            shellies[deviceID].last_seen_time = t\n        } else {  // if it was previously down\n            shellies[deviceID].last_seen_date = d\n            shellies[deviceID].last_seen_time = t\n            shellies[deviceID].up_since_date = d\n            shellies[deviceID].up_since_time = t\n            shellies[deviceID].state = \"up\"\n            shellies[deviceID].down_since_date = \"\"\n            shellies[deviceID].down_since_time = \"\"\n        }\n    }\n\n} else {  // device didn't exist\n    // create object for first time\n    shellies[deviceID] = {\n        created_date: d,\n        created_time: t,\n        state: \"up\",\n        last_seen_date: d,\n        last_seen_time: t,\n        up_since_date: d,\n        up_since_time: t,\n        down_since_date: \"\",\n        down_since_time: \"\"\n    }\n}\n\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":3200,"wires":[["5278cb4c.a63ce4"]]},{"id":"e86d79a2.acca98","type":"json","z":"a09201df.15c79","name":"","property":"payload","action":"","pretty":false,"x":470,"y":3200,"wires":[["e155b367.a5675","903ab56b.c9dd18"]]},{"id":"5a38f332.60afac","type":"inject","z":"a09201df.15c79","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":770,"y":3260,"wires":[["5278cb4c.a63ce4"]]},{"id":"5278cb4c.a63ce4","type":"change","z":"a09201df.15c79","name":"","rules":[{"t":"set","p":"shellies","pt":"msg","to":"home.shellies","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":950,"y":3260,"wires":[["140a8a8c.b493c5"]]},{"id":"140a8a8c.b493c5","type":"ui_template","z":"a09201df.15c79","group":"c99ea626.93dea8","name":"","order":2,"width":"0","height":"0","format":"<style>\n    a:visited {\n  color: #8cd3d6;\n    }\n\n    .nr-dashboard-theme .nr-dashboard-template .md-button {\n        min-height:5px;\n    }\n</style>\n\n<h1>Shelly Device Status</h1>\n<!--<div ng-if=\"msg.Notify !=== 'undefined'\">Oh no, empty</div>-->\n\n<table>\n    <tr style=\"font-weight:800; background-color:rgba(20,20,20,.6);\">\n        <td>ID</td>\n        <td>State</td>\n        <td>Since</td>\n        <td>Data</td>\n        <td>IP</td>\n        <td>Action</td>\n    </tr>\n    <tr style=\"line-height:18px\" ng-repeat=\"(key, value) in msg.shellies\">\n        <td>{{key}}<br/><span style=\"font-size:10px; color:#999\">{{value.shelly_type}}</span></td>\n        <td ng-if=\"value.state === 'up'\"><span style=\"color:green; font-weight:600\">✓</span> UP</td>\n        <td ng-if=\"value.state === 'down'\"><span style=\"color:red; font-weight:600\">✕</span> DOWN</td>\n        <td ng-if=\"value.state === 'up'\"><span style=\"color:#888; font-size:8px; display:block;\">{{value.up_since_time}}<br/>{{value.up_since_date}}</span></td>\n        <td ng-if=\"value.state === 'down'\"><span style=\"color:#888; font-size:8px; display:block;\">{{value.down_since_time}}<br/>{{value.down_since_date}}</span></td>\n        <td style=\"width:400px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;font-size:10px;color:#888;\">\n            <span ng-if=\"value.shelly_type === 'Dimmer2'\">Brightness: {{value.brightness}} | Power: {{value.power}} | Energy: {{value.energy}} | <span style=\"color:red\">Temp: {{value.temperature}}</span></span>\n            <span ng-if=\"value.shelly_type === 'EM'\">\n                <span style=\"display:block\"><strong>Perg</strong>: V = {{value.voltage_0}} | P = {{value.power_0}} | Preact = {{value.reactive_power_0}} | Ptot = {{value.total_0}} | Retrn = {{value.total_returned_0}}</span>\n                <span style=\"display:block\"><strong>Site</strong>: V = {{value.voltage_1}} | P = {{value.power_1}} | Preact = {{value.reactive_power_1}} | Ptot = {{value.total_1}} | Retrn = {{value.total_returned_1}}</span>\n            </span>\n        </td>\n        <td><a href=\"http://{{value.ip}}\" target=\"_blank\">{{value.ip}}</a></td>\n        <td><md-button ng-click=\"send({action: 'delete', topic: key})\">Del</md-button></td>\n    </tr>\n</table>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":1120,"y":3260,"wires":[["96556dd.80c359"]]},{"id":"42e827ff.f8b518","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/light/0/power","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":300,"y":3320,"wires":[["7fdf4656.9f5f78"]]},{"id":"6fef61e4.e439e","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/light/0/energy","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":300,"y":3340,"wires":[["7fdf4656.9f5f78"]]},{"id":"7fdf4656.9f5f78","type":"json","z":"a09201df.15c79","name":"","property":"payload","action":"","pretty":false,"x":530,"y":3360,"wires":[["749c1990.0302c8"]]},{"id":"749c1990.0302c8","type":"function","z":"a09201df.15c79","name":"Get other measurements","func":"d = new Date().toLocaleDateString(); t = new Date().toLocaleTimeString();\n\n// create the shellies object if it doesn't exist\nif (typeof global.get(\"home.shellies\") == 'undefined') global.set(\"home.shellies\", {})\n\nvar shellies = global.get(\"home.shellies\") // get light fixtures object from memory\nvar deviceID = msg.topic.split(\"/\")[1];         // get the shelly device ID (or MQTT prefix if set)\n\nif (shellies.hasOwnProperty(deviceID)) { // if device exists in Node RED memory\n\n    // IP address\n    if (msg.topic.split(\"/\")[2] == \"announce\") {\n        shellies[deviceID].ip = msg.payload.ip\n        shellies[deviceID].mac = msg.payload.mac\n        shellies[deviceID].new_fw = msg.payload.new_fw\n        shellies[deviceID].fw_ver = msg.payload.fw_ver\n    }\n\n    // Shelly Dimmer 2 (and other devices?)\n    if (msg.topic.split(\"/\")[2] == \"light\") {\n        // Register the device in Node-RED as a Shelly Dimmer2\n        shellies[deviceID].shelly_type = \"Dimmer2\"\n        // Get measurements / readings etc.\n        if (msg.topic.split(\"/\")[4] == \"status\") shellies[deviceID].brightness = msg.payload.brightness\n        if (msg.topic.split(\"/\")[4] == \"power\") shellies[deviceID].power = msg.payload\n        if (msg.topic.split(\"/\")[4] == \"energy\") shellies[deviceID].energy = msg.payload\n        \n    }\n    // Temp comes in on a different topic, so this could apply to other device types too\n    if (msg.topic.split(\"/\")[2] == \"temperature\") shellies[deviceID].temperature = msg.payload\n\n    // Shelly EM - register the device in Node-RED as an Electricity Meter (EM)\n    if (msg.topic.split(\"/\")[2] == \"emeter\") {\n        // Register the device in Node-RED as a Shelly EM\n        shellies[deviceID].shelly_type = \"EM\"\n        // Shelly EM - power\n        if (msg.topic.split(\"/\")[3] == \"0\" && msg.topic.split(\"/\")[4] == \"power\") shellies[deviceID].power_0 = msg.payload\n        if (msg.topic.split(\"/\")[3] == \"1\" && msg.topic.split(\"/\")[4] == \"power\") shellies[deviceID].power_1 = msg.payload\n        // Shelly EM - voltage\n        if (msg.topic.split(\"/\")[3] == \"0\" && msg.topic.split(\"/\")[4] == \"voltage\") shellies[deviceID].voltage_0 = msg.payload\n        if (msg.topic.split(\"/\")[3] == \"1\" && msg.topic.split(\"/\")[4] == \"voltage\") shellies[deviceID].voltage_1 = msg.payload\n        // Shelly EM - reactive power\n        if (msg.topic.split(\"/\")[3] == \"0\" && msg.topic.split(\"/\")[4] == \"reactive_power\") shellies[deviceID].reactive_power_0 = msg.payload\n        if (msg.topic.split(\"/\")[3] == \"1\" && msg.topic.split(\"/\")[4] == \"reactive_power\") shellies[deviceID].reactive_power_1 = msg.payload\n        // Shelly EM - total\n        if (msg.topic.split(\"/\")[3] == \"0\" && msg.topic.split(\"/\")[4] == \"total\") shellies[deviceID].total_0 = msg.payload\n        if (msg.topic.split(\"/\")[3] == \"1\" && msg.topic.split(\"/\")[4] == \"total\") shellies[deviceID].total_1 = msg.payload\n        // Shelly EM - total returned\n        if (msg.topic.split(\"/\")[3] == \"0\" && msg.topic.split(\"/\")[4] == \"total_returned\") shellies[deviceID].total_returned_0 = msg.payload\n        if (msg.topic.split(\"/\")[3] == \"1\" && msg.topic.split(\"/\")[4] == \"total_returned\") shellies[deviceID].total_returned_1 = msg.payload\n    }\n\n}\n\n\nglobal.set(\"home.shellies\",shellies)\n// NB if device doesn't exist in mem, the other function will create it and we'll get measurements next time","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":3360,"wires":[[]]},{"id":"86f1c84.826f538","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/temperature","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":300,"y":3360,"wires":[["7fdf4656.9f5f78"]]},{"id":"59976103.0a895","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/emeter/0/power/#","qos":"2","datatype":"auto","broker":"b144405e.a9f63","nl":false,"rap":false,"x":260,"y":3200,"wires":[["e86d79a2.acca98"]]},{"id":"ec00aa15.fe5778","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/emeter/#","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":290,"y":3400,"wires":[["7fdf4656.9f5f78"]]},{"id":"82c76ee1.a85e4","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/+/announce/#","qos":"2","datatype":"auto","broker":"b144405e.a9f63","nl":false,"rap":false,"x":300,"y":3440,"wires":[["7fdf4656.9f5f78"]]},{"id":"cc6b13e6.efe6b","type":"ui_chart","z":"a09201df.15c79","name":"","group":"3ac4cc84.a53204","order":17,"width":"3","height":"4","label":"Pergola W/min","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"12","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":1400,"y":3440,"wires":[[]]},{"id":"f7a7abac.143c48","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/hallway_power/emeter/0/energy","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":1070,"y":3440,"wires":[["a30cf5f.5f60208","cc6b13e6.efe6b"]]},{"id":"a30cf5f.5f60208","type":"debug","z":"a09201df.15c79","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1330,"y":3480,"wires":[]},{"id":"3fc4c764.2ab138","type":"ui_chart","z":"a09201df.15c79","name":"","group":"3ac4cc84.a53204","order":18,"width":"3","height":"4","label":"Pergola Power","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"12","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":1400,"y":3400,"wires":[[]]},{"id":"16b7463.51bb7ba","type":"mqtt in","z":"a09201df.15c79","name":"","topic":"shellies/hallway_power/emeter/0/power","qos":"2","datatype":"auto","broker":"b144405e.a9f63","x":1070,"y":3400,"wires":[["3fc4c764.2ab138"]]},{"id":"61f88c2d.48b294","type":"ui_button","z":"a09201df.15c79","name":"","group":"c99ea626.93dea8","order":1,"width":"4","height":"1","passthru":false,"label":"Send announce requests","tooltip":"","color":"","bgcolor":"","icon":"","payload":"announce","payloadType":"str","topic":"","topicType":"str","x":270,"y":3500,"wires":[["ac6303d9.b7f67"]]},{"id":"ac6303d9.b7f67","type":"mqtt out","z":"a09201df.15c79","name":"","topic":"shellies/command","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"b144405e.a9f63","x":530,"y":3500,"wires":[]},{"id":"96556dd.80c359","type":"function","z":"a09201df.15c79","name":"Delete an item","func":"if (msg.action == \"delete\") {\n    // delete the shelly\n    obj = global.get(\"home.shellies\")\n    delete obj[msg.topic]\n    global.set(\"home.shellies\", obj)\n    msg.action = undefined\n    return [msg,null];\n}\n\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1200,"y":3320,"wires":[["5278cb4c.a63ce4"],[]]},{"id":"b144405e.a9f63","type":"mqtt-broker","name":"MQTT","broker":"10.10.10.10","port":"1883","clientid":"nodered","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"c99ea626.93dea8","type":"ui_group","name":"Shellies","tab":"17707b8.7f63385","order":1,"disp":true,"width":"12","collapse":false},{"id":"3ac4cc84.a53204","type":"ui_group","name":"Hot Tub","tab":"484568b1.0b3ae8","order":2,"disp":true,"width":"6","collapse":false},{"id":"17707b8.7f63385","type":"ui_tab","name":"Device Monitoring","icon":"dashboard","disabled":false,"hidden":false},{"id":"484568b1.0b3ae8","type":"ui_tab","name":"Garden","icon":"dashboard","disabled":false,"hidden":false}]

Flow Info

Created 4 years, 2 months ago
Rating: 5 1

Owner

Actions

Rate:

Node Types

Core
  • change (x1)
  • comment (x1)
  • debug (x1)
  • function (x3)
  • inject (x1)
  • json (x2)
  • mqtt in (x9)
  • mqtt out (x1)
  • mqtt-broker (x1)
  • trigger (x1)
Other
  • ui_button (x1)
  • ui_chart (x2)
  • ui_group (x2)
  • ui_tab (x2)
  • ui_template (x1)

Tags

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