New SCADA ui nodes

Dang it, no

Saturday afternoon playtime...
I built a small ESP32-NOW network in the summer and thought I'd have a go at showing the readings using the 3DM Canvas node. All readings are shown in real-time on 3DM dashboard.


I took a snapshot of one of my printed circuit board based nodes and then asked "Aye Eye" to suggest a suitable background. After a few iterations it came up with the above image.

I'm going to improve the layout by having two tables associated with the Base Station. One panel will show node 20-B readings while the other panel will show readings received from other nodes.

Edit: Just implemented the above idea.


Pretty easy - modified the function to use node.send() firstly to send to a specific node and then to send each payload to "node-all" - just made the new mode sensitive to "node-all".

Snapshot of modified NR flow...

I monitor my system mainly from a cellphone so I'm curious if this is from a pc and how it looks on a cellphone

Not tried it from my mobile jelly-bone, only from PC so far.
As the background image is rather large I suspect it will look rather cramped.

My system does a lot of what it sounds like yours does. A bunch of temp sensors, some cameras, and some other stuff. To get to the point would you mind sharing your smithtek flow or pieces?

Please note: This is only my second go at using 3DM, so not that clever or artistic at this stage.

Here's a screenshot of my NR flow...


As you can see I have two sources of MQTT readings - one from my home sensors and the other from my experimental ESP32-NOW network. The two function nodes just rearrange the msg.payload and prepend it with the nodeID and node_id. When I was constructing the ESP32-NOW network I inadvertently used a different name/value pair - hence the inconsistency.

The two function nodes basically do the same thing. The only different is the lower one sends the msg.payload twice - once to a specific node (e.g. NOW-20-B) and the other to a node that is sensitive to all readings (i.e. node-all) to act as a summary node.

Here's the JSON for the NR flow...

[{"id":"5de403a93aa19911","type":"group","z":"d4783c1c9529da57","name":"","style":{"fill":"#e3f3d3","label":true},"nodes":["1340df7ed3355365","bbeec3f84b593018","ecfecbc1c01836b9","6031cec64be4e441","e7a0646f388619a7","786fb697e580f955","36b1dd41c2819786"],"x":74,"y":79,"w":752,"h":362},{"id":"1340df7ed3355365","type":"smithtek-3dm-scada-canvas","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"","x":680,"y":260,"wires":[[]]},{"id":"bbeec3f84b593018","type":"mqtt in","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"","topic":"homie/ird/+/readings","qos":"2","datatype":"auto-detect","broker":"b56b6cd03320eb2a","nl":false,"rap":true,"rh":0,"inputs":0,"x":190,"y":180,"wires":[["6031cec64be4e441"]]},{"id":"ecfecbc1c01836b9","type":"debug","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"Adjusted msg.payload","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":680,"y":180,"wires":[]},{"id":"6031cec64be4e441","type":"function","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"Prepend msg.payload with nodeID","func":"const p = msg.payload;\n\nif (\n    typeof p.temperature !== \"number\" ||\n    typeof p.humidity !== \"number\" ||\n    typeof p.pressure !== \"number\"\n) {\n    return null;\n}\n\nconst nodeID = p.nodeID || \"node44\";\n\nconst now = new Date();\nconst timestamp =\n    String(now.getHours()).padStart(2, \"0\") + \":\" +\n    String(now.getMinutes()).padStart(2, \"0\");\n\nmsg.payload = {\n    [nodeID]: {\n        timestamp: timestamp,\n        location: p.location || \"\",\n        temperature: p.temperature,\n        humidity: p.humidity,\n        pressure: p.pressure\n    }\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":260,"wires":[["1340df7ed3355365"]]},{"id":"e7a0646f388619a7","type":"comment","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"Second go at using 3DM - 03/Oct/2026","info":"","x":250,"y":120,"wires":[]},{"id":"786fb697e580f955","type":"mqtt in","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"","topic":"espnow/+/sensors/#","qos":"2","datatype":"auto-detect","broker":"b9de3426f819c4a9","nl":false,"rap":true,"rh":0,"inputs":0,"x":190,"y":320,"wires":[["36b1dd41c2819786"]]},{"id":"36b1dd41c2819786","type":"function","z":"d4783c1c9529da57","g":"5de403a93aa19911","name":"Prepend msg.payload with node_id - send twice","func":"const p = msg.payload;\n\n// Check that the expected BME280 object exists\nif (\n    !p ||\n    !p.bme280 ||\n    typeof p.bme280.temp_c !== \"number\" ||\n    typeof p.bme280.humidity !== \"number\" ||\n    typeof p.bme280.pressure !== \"number\"\n) {\n    return null;\n}\n\n// Node ID\nconst nodeID = p.node_id || \"unknown\";\n\n// Current time HH:MM\nconst now = new Date();\nconst timestamp =\n    String(now.getHours()).padStart(2, \"0\") + \":\" +\n    String(now.getMinutes()).padStart(2, \"0\");\n\n// Reading\nconst reading = {\n    timestamp: timestamp,\n    location: p.location || \"\",\n    temperature: p.bme280.temp_c,\n    humidity: p.bme280.humidity,\n    pressure: p.bme280.pressure\n};\n\n// Send to the specific node\nnode.send({\n    payload: {\n        [nodeID]: reading\n    }\n});\n\n// Send to node-all, including the originating node ID\nnode.send({\n    payload: {\n        \"node-all\": {\n            nodeID: nodeID,\n            timestamp: timestamp,\n            location: p.location || \"\",\n            temperature: p.bme280.temp_c,\n            humidity: p.bme280.humidity,\n            pressure: p.bme280.pressure\n        }\n    }\n});\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":400,"wires":[["1340df7ed3355365"]]},{"id":"b56b6cd03320eb2a","type":"mqtt-broker","name":"Home_152","broker":"192.168.1.152","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"b9de3426f819c4a9","type":"mqtt-broker","name":"","broker":"192.168.1.156","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"54e924de144c7984","type":"global-config","env":[],"modules":{"smithtek-3dm-scada-canvas":"2.3.1"}}]

I have two 3DM dashboards that match to the two MQTT feeds. There is an Action button on each dashboard to switch between the two.

Here's the JSON for the two dashboards...
scada-ESP32-layout.json (242.6 KB)
scada-house-layout.json (169.8 KB)

Here's the screenshots of the button to switch between dashboard layouts.


Obviously, you will need to tweak my offering to suit your situation - but it should act as a good starting point. Good luck go with you.

Maybe it's me, but I can't get the LED widget to work in 3dm-scada-canvas v2.3.1
When I click Add Colour State nothing happens.
I was expecting options to appear so I could define the on/off colours.

Thanks, I'll have a go later in the day

Just shout if you need any further help or explanations. Have fun. :partying_face:

got the flow loaded and downloaded the two dashboard json but what do I do with those?

Edited Steps
Switch to 'Edit' mode and then click +Page then Import the dashboard-JSON into that 'page'.


Then repeat +Page to import the second dashboard.

json not valid,
[edit] thought that was what I was supposed to do but had to check, I may still be doing something wrong but I thought I knew how to import

oops, I do have this wrong just saw the image, standby please

ok, got that part, thanks for the help,

Read the above EDITED steps - as I missed out creating a new page with +Page

got that part, took a moment

So you should now have two dashboards that look exactly like mine.

You'll probably now have to adjust my function nodes to handle your MQTT packet.

EDIT:
My data is in msg.payload.temperature, msg.payload.humidity and msg.payload.pressure

yep, thanks next is to get the data in from my MQTT
[edit] my data is in msg.payload.TempF,msg.payload.TempC,msg.payload.Humid, between me and my best friend chatgpt I'm almost half sure I'll get this figured out

I have four nodes that report readings via MQTT, so I prepend the NodeID to the payload so a typical structure looks like is...

msg.payload.node43.temperature
msg.payload.node43.humidity
msg.payload.node43.pressure

I then arrange that a widget on the dashboard is 'sensitive' to that payload.

Ok, I officially give this two thumbs up. Got it running thanks to @dynamicdave and really does seem fairly easy to use and does look good.