Is there an easy way to enumerate MQTT publishing devices


First, I confess: I am new to node-red.

I want to setup a demo with a small number of devices sending sensors values in MQTT on a topic "sensors/" ; the device id is a kind of serial number; it is unique for each board.

I was amazed by the easiness to show a nice dashboard connecting to my MQTT broker, mixing gauges (e.g for temperatures), values (like humidity), and so on ... to display the sensor values.

That is perfectly OK for one board, but if several board are now in place, it is more tricky to me.

I would like to have a dynamic list to be built showing all "device_id" currently publishing
And ideally after that, filtering the dashboard display gaujes on one device selected in this list.
Else, of course, if the dashboard mixes data from several boards, it becomes meaningless.

Is there an easy way for building that list, dynamically then filtering on a specific board ?

Thanks for your help.

I don't think there is a way when an MQTT publisher is seen for the first time to automatically create dashboard widgets to display the data.

I suppose you could have a bunch of widgets predefined and direct data from a new publisher to the first unused one.
I think there are ways to hide/reveal specific widgets too. Combined with the above, that would give at least the appearance of dynamically created widgets.

Put data from each device into it's own dashboard group. Then you can label the group, draw a border round it, change background colour, etc.

Do you mean something like this

[{"id":"8d139f89.d2ebd","type":"mqtt in","z":"fe76f7928e585317","name":"","topic":"sensor/+","qos":"2","datatype":"json","broker":"e8ba3ef5.22f4a8","nl":false,"rap":true,"rh":0,"x":70,"y":520,"wires":[["de2e426d.b136"]]},{"id":"de2e426d.b136","type":"change","z":"fe76f7928e585317","name":"","rules":[{"t":"set","p":"sensors","pt":"flow","to":"$append(\t   [\t       $flowContext(\"sensors\")[sensor != $$.topic]\t   ],\t   [\t        $merge([{\"sensor\":$$.topic},$$.payload])\t   ]\t)\t","tot":"jsonata"},{"t":"set","p":"options","pt":"msg","to":"$flowContext(\"sensors\").sensor","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":520,"wires":[["1ff65288.ed56a5","b142191c.f05ed","9d500ab6.43a98"]]},{"id":"1ff65288.ed56a5","type":"ui_dropdown","z":"fe76f7928e585317","name":"","label":"","tooltip":"","place":"Select option","group":"8b5cde76.edd58","order":4,"width":0,"height":0,"passthru":true,"multiple":false,"options":[],"payload":"","topic":"topic","topicType":"msg","className":"","x":290,"y":600,"wires":[["341f5f2a.f33738"]]},{"id":"b142191c.f05ed","type":"switch","z":"fe76f7928e585317","name":"","property":"show","propertyType":"flow","rules":[{"t":"eq","v":"topic","vt":"msg"}],"checkall":"false","repair":false,"outputs":1,"x":600,"y":520,"wires":[["8d819b7d.a2723","8e1186f9.2b5ea","2b64d6c4.0271b2"]]},{"id":"9d500ab6.43a98","type":"debug","z":"fe76f7928e585317","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":530,"y":440,"wires":[]},{"id":"341f5f2a.f33738","type":"change","z":"fe76f7928e585317","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"show","pt":"flow","to":"payload","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"sensors\")[sensor = $$.payload]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":600,"wires":[["b142191c.f05ed","76e2475b.46c308"]]},{"id":"8d819b7d.a2723","type":"debug","z":"fe76f7928e585317","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":830,"y":520,"wires":[]},{"id":"8e1186f9.2b5ea","type":"ui_gauge","z":"fe76f7928e585317","name":"","group":"8b5cde76.edd58","order":5,"width":0,"height":0,"gtype":"gage","title":"temp {{msg.topic}}","label":"C","format":"{{msg.payload.temp}}","min":0,"max":"30","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","className":"","x":760,"y":480,"wires":[]},{"id":"2b64d6c4.0271b2","type":"ui_gauge","z":"fe76f7928e585317","name":"","group":"8b5cde76.edd58","order":5,"width":0,"height":0,"gtype":"gage","title":"hum {{msg.topic}}","label":"%","format":"{{msg.payload.hum}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","className":"","x":760,"y":560,"wires":[]},{"id":"76e2475b.46c308","type":"debug","z":"fe76f7928e585317","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":620,"wires":[]},{"id":"ab33645f.bf3628","type":"inject","z":"fe76f7928e585317","name":"simulate mqtt - topic123qwe","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor/123qwe","payload":"{\"temp\":21,\"hum\":50}","payloadType":"json","x":170,"y":320,"wires":[["77e83809.36f918"]]},{"id":"af085f22.b96eb","type":"inject","z":"fe76f7928e585317","name":"simulate mqtt - topic123asd","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor/123asd","payload":"{\"temp\":25,\"hum\":60}","payloadType":"json","x":160,"y":360,"wires":[["77e83809.36f918"]]},{"id":"d4d946c9.c0f6f","type":"inject","z":"fe76f7928e585317","name":"simulate mqtt - topic123zxc","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"sensor/123zxc","payload":"{\"temp\":30,\"hum\":70}","payloadType":"json","x":180,"y":400,"wires":[["77e83809.36f918"]]},{"id":"77e83809.36f918","type":"mqtt out","z":"fe76f7928e585317","name":"","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"e8ba3ef5.22f4a8","x":520,"y":380,"wires":[]},{"id":"e8ba3ef5.22f4a8","type":"mqtt-broker","name":"testb","broker":"","port":"1883","clientid":"node-red","usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""},{"id":"8b5cde76.edd58","type":"ui_group","name":"default","tab":"8f03e639.85956","order":1,"disp":false,"width":"12","collapse":false},{"id":"8f03e639.85956","type":"ui_tab","name":"Home","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

all incoming topics are saved to context with the payloads, the topics are then displayed in a dropdown, this dropdown displays which sensors readings are displayed in the gauges.

1 Like

Thanks a lot for the flow.

I cannot test it, node-red give me the following message on import:

"The workspace contains some unknown node types:

  • ui_group
  • ui_tab
  • ui_dropdown
  • ui_gauge

I am on a v3.0.2 fresh install of ned-red . Can it explain the issue ?

Thanks foryour answer
Actually I am looking for a "dynamic" mode, where I would discover the devices when they publish something to me

Sorry you mentioned Dashboard and gauges, so I thought you had Node-Red Dashboard installed

So you need to split the problem down to make it easier to deal with.

Firstly handling the inputs. Assuming that the devices publish to a unique MQTT topic, you can use that to identify them. Subscribing to sensors/# will get everything on a single input. You may wish then to store the latest reading from each sensor in Node-RED to make it easier to process. You can either use a change node with JSONata or (probably easier) use a function node. Either way, use a single flow or global variable that is a JavaScript object with each entry having a key of the sensor ID. Here is an example from my own system:

You might not need the variable but you might still find it easier to follow and debug things.

The second stage is to be able to dynamically build a UI that shows the output for each sensor.

First you need to chose what tool you want to use in order to do the UI.

"Dashboard" is the one that has been mentioned and is commonly used where you don't want or need to write any UI code (e.g. HTML). However, in this case, you ARE going to have to write some front-end code because you don't know in advance what sensors you need to display. Dashboard has the ui_template node that lets you do this. In that, you will need to create a loop over your input msg which will contain the variable mentioned above. Lots of different ways to do the display, probably best to have a go at a quick web development course if you've not done any before. A couple of hours learning will help you a lot here. You cannot use the other Dashboard nodes I don't believe for what you want since they assume that you have a node for each input and you don't know how many inputs you will have.

As an alternative to Dashboard, you could use uibuilder. This doesn't give you all of the nodes that map to front-end widgets but it does have some examples for using front-end frameworks such as bootstrap-vue which makes repetitive and framework tasks very easy with minimal code required.

And if you like raw metal, there is no need for any custom node or library. To create a webpage, the Node-RED has all tools included by default.
Here's the example as starting point. Simple Web page with live data updates via websocket (flow) - Node-RED

1 Like

I am trying to simplify my demo
I know the list of device id" that can connect (I have only 10 boards)

So, if I can have on my dashboard:

  • a text box where I can select the "device id" as e.g. /PG0600642
  • a computation producing the topic name "sensors/device-id", e.g; "sensors/PG0600642"
  • a way to define the "mqtt in" topic to that value,

I did not get how to setup this in my dashboard
Any help appreciated

In dynamic subscription mode you can tell your mqtt node to unsubscribe from one topic and subscribe to another.

Assuming that you receive the relevant topic from a dropdown as msg.payload, a function like this might work

var newmsg = {}
newmsg.topic = context.get ('alreadysubscribed')
newmsg.action = "unsubscribe"
node.send (newmsg);

msg.topic = msg.payload
msg.payload = ""
msg.action = "subscribe"
context.set ('alreadysubscribed', msg.topic);
return msg;

Sorry, but I dont see where I put this code in the mqtt node
Are you referrinf to the standard mqtt node , named "mqtt in" ; it has no input and I cannot place any code in it

There is an option on the mqtt-in node to change it to dynamic mode

Perhaps I could have made that clearer.
Dynamic subscription mode is a setting of the MQTT in Node.
The function i suggested would go in a Function node.

So the flow might look like this

OK, actually my "mqtt_in" has no entry available
I guess you added with Command Palette other mqtt libs
Can you precise which one you are using ?
so far I could not connect the other ones I found

Honestly, we are referring to the standard, part-of-the-core MQTT node.

You could either try searching for drop-downs in the node configuration or else read and understand the help relating to that node in the right hand column of the NR editor. Click the little book or "rtfm" icon at the top of the column to see it.

what version of node-red are you running?

V2.2+ has this options...

I am using v3.0.2

OK, selecting "Dynamic subscription" made le left connector appear on the "mqtt in" node.

So, I have added a "text input" node, next a "function" node connecting on left of "mqtt in"

The debug shows an issue with msg.topic
Sorry, I am discovering the tool

try changing the function to ...

node.send ({topic: true, action: "unsubscribe"});

msg.topic = msg.payload
node.warn("About to subscribe to " + msg.topic)
msg.payload = ""
msg.action = "subscribe"
return msg;

Finally got something working using global settings (I hate globals, but wanted to get something working

Set Global DevId makes

  global.set("DeviceId", "SENSORS/" + msg.topic);

Get Global DevId makes :

  var topic = {}
  topic = global.get("DeviceId");

if (msg.topic == topic) {
    return msg;
} else {
    return NULL;

Thanks everybody for your help

I'd be surprised if that works. NULL should be lower case.