Node Red: A widget based dashboard working with Homey trough MQTT

Hi all,

After having leeched a ton of information from this topic, I think it’s time to also share a bit of information.
Please keep in mind that I am not a programmer, have barely any knowledge of JavaScript, CSS etc. so I’m sure things can be done more efficiently, but nevertheless I hope someone can appreciate this post.

I created a ‘top x’ large electricity consumers, which creates a sorted list of devices currently consuming the most electricity. Personally I find this useful functionality; perhaps others can see a use for it as well.

The dashboard uses the awesome CSS with homey-look as posted earlier by @DeepBlueNine; but I believe any CSS should be able to use the tools I post here.

The dashboard displays this list:
image

For this to work, I used several things in node-red.

  1. First, I created a JavaScript Map object in the global namespace. For this I created a Function that I triggered once with a timestamp message using an inject node connected to this function. The code for the javascript Map is this:

[{“id”:“c30332a9.8b807”,“type”:“function”,“z”:“9235bbe0.d90a78”,“name”:“createGlobal Map object for grootverbruikers”,“func”:“var map = new Map();\nglobal.set("GrootverbruikersMap", map);\nreturn msg;”,“outputs”:1,“noerr”:0,“initialize”:“”,“finalize”:“”,“x”:1190,“y”:1760,“wires”:[]}]

  1. Next I used function nodes that already register electricity consumption, e.g. from INNR Plug, or Fibaro Plugs, and had them populate the Map every time a new power measure comes in. The code inside this function node is:

var gvb = new Map(global.get(“GrootverbruikersMap”));
gvb.set(pl, power);
global.set(“GrootverbruikersMap”, gvb);

where pl could be the name of the device (like ‘dishwasher’) and power would be the current power consumption, like 100.

After some time, most devices have posted their power consumption to the map, resulting in a Map object that has payload similar to this debug output:

  1. The next challenge is that the map is not a sorted list, and I believe it cannot be sorted as far as I can tell. the list is ordered in the sequence entries are added, so first device added is first device in the list.

To overcome this, I needed to make sure that I use an object that can be sorted, and I chose the JavaScript Array.

On the destination tab, so where I needed the list to appear, I created three nodes, a repeating timestamp injector, to trigger my flow periodically, e.g. 4 times per minute, a function node to create the sorted list, and the ui template to show the list in my dashboard.
image

The function node has the Map → Array conversion and the sort-by-power-column-descending code.

// sorteren grootverbruikers

var grootverbruikers = new Map(global.get(“GrootverbruikersMap”));

function compare(a, b) {
// Use toUpperCase() to ignore character casing
const powerA = a.value;
const powerB = b.value;

let comparison = 0;
if (powerA > powerB) {
comparison = 1;
} else if (powerA < powerB) {
comparison = -1;
}
return comparison *-1; // multiply with -1 for reverse sorting order
}

var gvbArray = Array.from(grootverbruikers, ([name, value]) => ({ name, value }));
//console.log(gvbArray);
gvbArray.sort(compare);
//console.log("Sorted: " + gvbArray);

msg.payload=gvbArray;
return msg;

The return message would contain a sorted array.

  1. The last bit is getting the list displayed. I use the UI template node, and plain HTML table, listing the first 8 elements in the array statically. I know I could iterate the entire array, but I consciously decided that it does not make sense to show more than 5…8 devices for my purposes…

The code for the above three nodes is listed here:

[{“id”:“2c319ac.1847166”,“type”:“function”,“z”:“7a3033ea.2f403c”,“name”:“sorteerGrootverbruikers”,“func”:“// sorteren grootverbruikers\n\n/\nconst singers = [\n { name: ‘Steven Tyler’, band: ‘Aerosmith’, born: 1948 },\n { name: ‘Karen Carpenter’, band: ‘The Carpenters’, born: 1950 },\n { name: ‘Kurt Cobain’, band: ‘Nirvana’, born: 1967 },\n { name: ‘Stevie Nicks’, band: ‘Fleetwood Mac’, born: 1948 },\n]; \n/\n\nvar grootverbruikers = new Map(global.get("GrootverbruikersMap"));\n\nfunction compare(a, b) {\n // Use toUpperCase() to ignore character casing\n const powerA = a.value;\n const powerB = b.value;\n\n let comparison = 0;\n if (powerA > powerB) {\n comparison = 1;\n } else if (powerA < powerB) {\n comparison = -1;\n }\n return comparison -1; // multiply with -1 for reverse sorting order\n}\n\n\n//\n\n\n\n/\nconst iterator1 = grootverbruikers.entries();\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n console.log(iterator1.next().value);\n*/\n\nvar gvbArray = Array.from(grootverbruikers, ([name, value]) => ({ name, value }));\n//console.log(gvbArray);\ngvbArray.sort(compare);\n//console.log("Sorted: " + gvbArray);\n\n\nmsg.payload=gvbArray;\nreturn msg;\n\n\n”,“outputs”:1,“noerr”:0,“initialize”:“”,“finalize”:“”,“x”:490,“y”:1340,“wires”:[[“66e2da5b.1f8d94”]]},{“id”:“d51e14d5.87d178”,“type”:“inject”,“z”:“7a3033ea.2f403c”,“name”:“”,“props”:[{“p”:“payload”},{“p”:“topic”,“vt”:“str”}],“repeat”:“10”,“crontab”:“”,“once”:true,“onceDelay”:0.1,“topic”:“”,“payload”:“”,“payloadType”:“date”,“x”:280,“y”:1340,“wires”:[[“2c319ac.1847166”]]},{“id”:“66e2da5b.1f8d94”,“type”:“ui_template”,“z”:“7a3033ea.2f403c”,“group”:“fce2a100.6e2d6”,“name”:“sortedGrootverbruikers”,“order”:1,“width”:6,“height”:6,“format”:“<span style="vertical-align:middle; font-size:15px; color: rgb(255, 255, 255); text-align: left; " class="fr-class-transparency">\n\n <table width="100%">\n {{msg.payload[0].name}}:<td width="30%" align="right">{{msg.payload[0].value}} [W]\n {{msg.payload[1].name}}:<td width="30%" align="right">{{msg.payload[1].value}} [W]\n {{msg.payload[2].name}}:<td width="30%" align="right">{{msg.payload[2].value}} [W]\n {{msg.payload[3].name}}:<td width="30%" align="right">{{msg.payload[3].value}} [W]\n {{msg.payload[4].name}}:<td width="30%" align="right">{{msg.payload[4].value}} [W]\n {{msg.payload[5].name}}:<td width="30%" align="right">{{msg.payload[5].value}} [W]\n {{msg.payload[6].name}}:<td width="30%" align="right">{{msg.payload[6].value}} [W]\n {{msg.payload[7].name}}:<td width="30%" align="right">{{msg.payload[7].value}} [W]\n \n ”,“storeOutMessages”:true,“fwdInMessages”:true,“resendOnRefresh”:true,“templateScope”:“local”,“x”:740,“y”:1340,“wires”:[]},{“id”:“fce2a100.6e2d6”,“type”:“ui_group”,“z”:“”,“name”:“Grootverbruikers”,“tab”:“9184a885.936cb8”,“order”:5,“disp”:true,“width”:“6”,“collapse”:true},{“id”:“9184a885.936cb8”,“type”:“ui_tab”,“z”:“”,“name”:“Energie”,“icon”:“dashboard”,“order”:3,“disabled”:false,“hidden”:false}]

Hope this helps - have fun!

2 Likes