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

@Andre_Hartman Check if your MQTT server (for instance Mosquitto on a nas or Pi) is still running.

Is the topic name still correct? Maybe you renamed your device?

Hi all,

I received a Plugwise smile P1 as a gift and this runs smoothly with homey, however I can’t get it to send the values to Mqtt so I can use it on my dashboard. Any one experience with this?

]

Thanks!

I can reach the server with MQTT explorer. That look all okay.

I use the standard settings and it worked fine. But somehow it’s all messed up now.

Probably needs a restart of the mqtt-Hub, to include the new device.

Does someone know how to reload the values from the MQTT hub on Homey, on reconnect or refresh?

Doesn’t an App-restart automatically do this? (I did not test personally)

Thanks! It restart solved it :grin:

When I click on Deploy in Node Red the values are empty. So I have to wait for the values to be sent again from MQTT hub. What I want is to reload the value form MQTT hub on reload of the page.

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

Post edited d.d. 13.9.2020 after feedback from @HarriedeGroot - thanks!

Hi @Jeroen_Kokhuis,
I’m not sure what your question actually is.
Here’s what I understand from using node-red with homey these past two weeks:

My setup is displayed above. I run:

  • MQTT client on homey
  • MQTT hub on homey
  • MQTT broker is eclipse-mosquito in docker on my NAS so my homey is not having too much load
  • Node red in docker on my NAS

Furthermore:

  • The client on Homey needs to be configured for the broker on my NAS
  • The mqtt hub does not need boker configuration as it uses the mqtt client to communicate
  • in node red I configured an MQTT receiver (mqtt in) with config node to the broker
  • in node red I configured an MQTT Out config node to the broker

All parts started communicating and have been ever since.

Key to understand is that none of the mqtt client and node red talk to each other directly. They all use the broker to communicate.

I’m not sure, but I suspect that this post should help you figure out your concerns. If not, just post another message

All the best!

PS: Thanks Harrie for getting me to provide correct info.

@RogerSt Sorry, but your understanding is incorrect. The correct model is:
MQTT HUB <-> MQTT Client <-> MQTT Broker <-> Node RED

MQTT Client: Interface to send receive & receive mqtt messages. Maintains the connection with the broker.
MQTT Hub: Uses the mqtt client to send information (state) of all devices connected to Homey in a standardized format (Homie). It’s also capable of receiveing commands (set) to manipulate these devices (e.g. turn lights on/off).

@Jeroen_Kokhuis Unfortunately the hub does not contain a flow trigger to activate the broadcast, but there is a small trick to achieve this. The hub will automatically execute a broadcast when it receives a so called ‘birth message’.

When I reload a page on Node Red I get this:
image

And I want to let the icon show on reload of the page or when I switch a tab. Then the value is empty and I the icon isn’t displayed. Is there a way to display the icon immediately, so to reload the value’s from MQTTwith the correct icon? So I will get this on reload?
image
or this one:
image

this is the function for displaying the right Icon depending on the value:

if(msg.payload === "true"){
 msg.ui_control = {icon:"icons/light-bulb-on-60.png", tooltip:"Licht is aan"};
 msg.label = "Licht is aan"
 
}

else{
 msg.ui_control = {icon:"icons/light-bulb-off-60.png", tooltip:"Licht is uit"};
 msg.label = "Licht is uit"
}
return [msg];

Keep the state in a (global) variable in Node-RED and only switch state if messages are received?

I do not know how to do that. From what I’ve read you have to do that for every variable. Could you explain how to do that?

Hi @Jeroen_Kokhuis,

From looking at your post, you are using a function node to read the device messages on mqtt, and use these values directly from the payload to control your subsequent UI node.

So I suspect you have something like this:
image

Inside the function node you are currently using the message payload directly:

if(msg.payload === “true”) … etc.

This requires incoming messages to work. In case there is no first incoming message, your UI does not show correctly.

As @HarriedeGroot suggests you could create a context variable. Context variables can be set on various scopes, for example global (available everywhere in node red), or flow (available in the current flow only). I always use the most narrow scope I can use, so I prefer flow before global if I can. Harrie suggests using a global variable instead of a flow variable. Both will work if no other nodes mess unintentionally with the flow variable.

In case of your flow, choose a variable that you can use to store the result of the messages received in. As an example, you could use “statusWoonkamerLamp”.

Every time you receive a message, you store the state in the variable.

if(msg.payload === “true”) { flow.set("statusWoonkamerLamp, true); } else { flow.set("statusWoonkamerLamp, false); }

Next, instead of using the message payload to control the UI, use the new variable:

if(flow.get(statusWoonkamerLamp){ msg.ui_control…

Now, every time a message arrives, it is stored in the flow variable, and the UI is controlled with the flow variable instead.

We have now prepared for one additional step: making sure that the UI is also up-to-date when there are no messages for a little while.

Other solutions may exist (suggestions are welcome), but I have personally used an inject node with a repeating timer of ~15 seconds. The timer can trigger your function node.

So on the input side, create an inject node with a repeating interval:
image

Connect the inject node to the input of your function node. You should see something like this:
image
Note that this is an image for illustration purposes only. I have not put any code in these, so you see the warning and deployment decorators there.

The repeating timestamp node sends a timestamp per default. You need to make sure that your function node does not read this value as a light status value. For this to work, you need to distinguish in your flow between true, false and timestamp message.

When the timestamp message arrives, do not update the flow variable. Otherwise do update. Your code should look similar to this:

// note that this flow is triggered by an mqtt message AND by the repeating timestamp node
// here we deal with the message payload only
if (msg.payload === "true"){

    flow.set("statusWoonkamerLamp", true);
}
else if (msg.payload ==="false") {
    flow.set("statusWoonkamerLamp", false);
}
else {
    // don't do anything here as this could be your timestamp trigger
}

// here we deal with the flow variable.
if (flow.get("statusWoonkamerLamp")){
    msg.ui_control = {icon:"icons/light-bulb-on-60.png", tooltip:"Licht is aan"};
    msg.label = "Licht is aan"
}
else {
    msg.ui_control = {icon:"icons/light-bulb-off-60.png", tooltip:"Licht is uit"};
    msg.label = "Licht is uit"
}

return msg;

Please note that I have not tested the above code but it should be working or close to working code.

Now, every time a message or time trigger is arriving, the second part of your function code is triggered and updates the UI.

Another, simpler way, is to just turn the light on and off :wink:

In case of questions - please do ask!

1 Like

Thanks for your help. It works!

Have you tried restarting indeed, because I have that same device and it works just fine.

where do you host your icons so they can be consumer as relative paths?

“icons/light-bulb-on-60.png”

instead of

“http://server_or_ip:port/path/icons/light-bulb-on-60.png”