Node-RED and MQTT Advanced

Building a functional User Interface

In the last course we created a Login Form in our dashboard UI that receives the IP address, user name and password. This flow published the login information directly via MQTT so we can request the current state from the camera.

We now want to, additionally, save them in a form that is available to other flows. Node-RED offers us the Context function to save messages to flows (make them available to every flow that is on the same tab as the flow that created the payload) and save them to a global state, to have them available everywhere by calling:

var ip = global.get("ip");
var user = global.get("user");
var password = global.get("password");

The easiest way to set a variable to the global or flow context, is the Change Node (all three parts of the source code can be imported from the JSON code below: Global Login, UI Buttons, SET Audio Detection State):

Node-RED

We can add the Change Node in between our Input Form and the MQTT Node in the Global Login flow to set all three variables to a global context. And publish them with an MQTT Output Node.

The UI buttons carry a payload of { "val" : "disabled"} and { "val" : "enabled"} respectively. Which is then published by another MQTT Output Node. Please make sure that all MQTT output node topics are set to unique values - our login node publishes its payload under ipcam/login, while the UI buttons send their payload to ipcam/alarm/setaudioalarmattr/activate.

Node-RED

Notice that the third flow now consists of two parts. The lower part is triggered by the Global Login flow and already populates the UI node with the current state on the camera at the time of login.

Node-RED

The upper part, on the other hand, is subscribed to the output of our UI buttons. Whenever the user clicks a button, the state is set on our camera and the Text Output on our dashboard is updated to reflect the change in state. Go to http://localhost:1880/dashboard and type in your camera's IP address and your admin login. Once you submit these values, the UI will display the current state on your camera:

Node-RED

Clicking one of the two buttons will set the corresponding state on your camera and update the text displayed on your dashboard.

JSON Code export

Global Login

[{"id":"9f5d07da.b800b8","type":"ui_form","z":"13bcbd83.ab1fe2","name":"Camera Address","label":"Change Address","group":"c0689660.4e98c8","order":0,"width":0,"height":0,"options":[{"label":"IP Address","value":"ip","type":"text","required":true},{"label":"Username","value":"user","type":"text","required":true},{"label":"Password","value":"password","type":"password","required":true}],"formValue":{"ip":"","user":"","password":""},"payload":"","submit":"Submit","cancel":"Cancel","topic":"user_set","x":90,"y":100,"wires":[["59d50e34.9ad15"]]},{"id":"d5b37a35.b7c358","type":"comment","z":"13bcbd83.ab1fe2","name":"Set Login Global","info":"","x":90,"y":40,"wires":[]},{"id":"59d50e34.9ad15","type":"change","z":"13bcbd83.ab1fe2","name":"Set Global Login","rules":[{"t":"set","p":"ip","pt":"global","to":"payload.ip","tot":"msg"},{"t":"set","p":"password","pt":"global","to":"payload.password","tot":"msg"},{"t":"set","p":"user","pt":"global","to":"payload.user","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":140,"y":160,"wires":[["abe4025e.efce5"]]},{"id":"abe4025e.efce5","type":"mqtt out","z":"13bcbd83.ab1fe2","name":"","topic":"ipcam/login","qos":"","retain":"","broker":"47feb3e4.56f11c","x":180,"y":220,"wires":[]},{"id":"c0689660.4e98c8","type":"ui_group","z":"","name":"Camera Login","tab":"7a3391b6.03a81","order":1,"disp":true,"width":"6","collapse":false},{"id":"47feb3e4.56f11c","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"7a3391b6.03a81","type":"ui_tab","z":"","name":"Wiki Tutorial","icon":"dashboard"}]

UI Buttons

[{"id":"964c8404.63ed18","type":"ui_button","z":"13bcbd83.ab1fe2","name":"Deactivate Audio Detection","group":"eb6d72f9.38466","order":3,"width":0,"height":0,"passthru":false,"label":"Deactivate Audio Detection","color":"#000000","bgcolor":"#dddddd","icon":"fa-bell","payload":"{ \"val\" : \"disabled\"}","payloadType":"json","topic":"snap","x":121,"y":380,"wires":[["c9765093.00946"]]},{"id":"c9765093.00946","type":"mqtt out","z":"13bcbd83.ab1fe2","name":"Set Audio Alarm","topic":"ipcam/alarm/setaudioalarmattr/activate","qos":"1","retain":"","broker":"47feb3e4.56f11c","x":331,"y":360,"wires":[]},{"id":"9a80df92.8aab6","type":"ui_button","z":"13bcbd83.ab1fe2","name":"Activate Audio Detection","group":"eb6d72f9.38466","order":2,"width":0,"height":0,"passthru":false,"label":"Activate Audio Detection","color":"#ffffff","bgcolor":"#f17c35","icon":"fa-bell","payload":"{ \"val\" : \"enabled\"}","payloadType":"json","topic":"snap","x":121,"y":340,"wires":[["c9765093.00946"]]},{"id":"14d2d8ff.b3c887","type":"comment","z":"13bcbd83.ab1fe2","name":"Audio Detection Buttons","info":"","x":110,"y":288,"wires":[]},{"id":"eb6d72f9.38466","type":"ui_group","z":"","name":"Audio Detection","tab":"7a3391b6.03a81","disp":true,"width":"6","collapse":false},{"id":"47feb3e4.56f11c","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"7a3391b6.03a81","type":"ui_tab","z":"","name":"Wiki Tutorial","icon":"dashboard"}]

SET Audio Detection State

[{"id":"c5cf5c15.7cc0a","type":"mqtt in","z":"13bcbd83.ab1fe2","name":"Set Audio Alarm","topic":"ipcam/alarm/setaudioalarmattr/activate","qos":"1","broker":"47feb3e4.56f11c","x":90,"y":500,"wires":[["bebb4aa8.4fa1a8"]]},{"id":"282510af.cc1f9","type":"switch","z":"13bcbd83.ab1fe2","name":"Push","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"enabled","vt":"str"},{"t":"eq","v":"disabled","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":271,"y":500,"wires":[["2068dd82.b90c82"],["1c4f1b7e.b4bfb5"]]},{"id":"2068dd82.b90c82","type":"function","z":"13bcbd83.ab1fe2","name":"Enable Audio Detection","func":"var ip = global.get(\"ip\");\nvar user = global.get(\"user\");\nvar password = global.get(\"password\");\n\n// Update the status with current timestamp\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd  = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm  = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss  = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\nvar currenttime= hh + \":\" + mmm + \":\" + ss;\nvar currentdate= dd + \".\" + mm + \".\" + yyyy;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Last updateChanged: \" + currentdate + \" - \" + currenttime});\n\nmsg.topic = \"type\";\nmsg.payload = ip+\"/param.cgi?cmd=setaudioalarmattr&-aa_enable=1&-usr=\"+user+\"&-pwd=\"+password;\nreturn msg;\n\n// msg.payload = 'http://192.168.1.116/param.cgi?cmd=setaudioalarmattr&-aa_enable=1&-usr=admin&-pwd=1029384756';\n// return msg;","outputs":1,"noerr":0,"x":436,"y":477,"wires":[["b51eb789.8f4058"]]},{"id":"370a40c.b9163c","type":"string","z":"13bcbd83.ab1fe2","name":"toJSON","methods":[{"name":"replaceAll","params":[{"type":"str","value":"[Succeed]set ok."},{"type":"str","value":"{ \"aa_enable\": \"enabled\" }"}]},{"name":"replaceAll","params":[{"type":"str","value":"[Error]Param error."},{"type":"str","value":"{ \"aa_enable\": \"Error\" }"}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":833,"y":477,"wires":[["102231e6.822f5e"]]},{"id":"102231e6.822f5e","type":"json","z":"13bcbd83.ab1fe2","name":"","property":"payload","action":"","pretty":false,"x":961,"y":516,"wires":[["91abfd33.6d186"]]},{"id":"91abfd33.6d186","type":"ui_text","z":"13bcbd83.ab1fe2","group":"eb6d72f9.38466","order":1,"width":0,"height":0,"name":"Audio Detection","label":"Audio Detection","format":"{{msg.payload.aa_enable}}","layout":"row-spread","x":1111,"y":516,"wires":[]},{"id":"b51eb789.8f4058","type":"change","z":"13bcbd83.ab1fe2","name":"Set URL","rules":[{"t":"set","p":"url","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":592,"y":477,"wires":[["25d9a3ff.508bac"]]},{"id":"25d9a3ff.508bac","type":"http request","z":"13bcbd83.ab1fe2","name":"snap","method":"GET","ret":"txt","url":"","tls":"","x":712,"y":477,"wires":[["370a40c.b9163c"]]},{"id":"bebb4aa8.4fa1a8","type":"function","z":"13bcbd83.ab1fe2","name":"process value","func":"msg.payload = JSON.parse(msg.payload).val;\nreturn msg;","outputs":1,"noerr":0,"x":111,"y":540,"wires":[["282510af.cc1f9"]]},{"id":"1c4f1b7e.b4bfb5","type":"function","z":"13bcbd83.ab1fe2","name":"Disable Audio Detection","func":"var ip = global.get(\"ip\");\nvar user = global.get(\"user\");\nvar password = global.get(\"password\");\n\n// Update the status with current timestamp\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd  = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm  = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss  = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\nvar currenttime= hh + \":\" + mmm + \":\" + ss;\nvar currentdate= dd + \".\" + mm + \".\" + yyyy;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Last updateChanged: \" + currentdate + \" - \" + currenttime});\n\nmsg.topic = \"type\";\nmsg.payload = ip+\"/param.cgi?cmd=setaudioalarmattr&-aa_enable=0&-usr=\"+user+\"&-pwd=\"+password;\nreturn msg;\n\n// msg.payload = 'http://192.168.1.116/param.cgi?cmd=setaudioalarmattr&-aa_enable=0&-usr=admin&-pwd=1029384756';\n// return msg;","outputs":1,"noerr":0,"x":436,"y":517,"wires":[["86e3f600.a42a08"]]},{"id":"86e3f600.a42a08","type":"change","z":"13bcbd83.ab1fe2","name":"Set URL","rules":[{"t":"set","p":"url","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":592,"y":517,"wires":[["b150cc6.1cb693"]]},{"id":"b150cc6.1cb693","type":"http request","z":"13bcbd83.ab1fe2","name":"snap","method":"GET","ret":"txt","url":"","tls":"","x":712,"y":517,"wires":[["7cd1ddf0.8c4d54"]]},{"id":"7cd1ddf0.8c4d54","type":"string","z":"13bcbd83.ab1fe2","name":"toJSON","methods":[{"name":"replaceAll","params":[{"type":"str","value":"[Succeed]set ok."},{"type":"str","value":"{ \"aa_enable\": \"disabled\" }"}]},{"name":"replaceAll","params":[{"type":"str","value":"[Error]Param error."},{"type":"str","value":"{ \"aa_enable\": \"Error\" }"}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":832,"y":517,"wires":[["102231e6.822f5e"]]},{"id":"8e8eb1d2.917c2","type":"http request","z":"13bcbd83.ab1fe2","name":"aa_enable","method":"GET","ret":"txt","url":"","tls":"","x":720,"y":560,"wires":[["86f4a6aa.505e18"]]},{"id":"ad2301ba.7ecf2","type":"function","z":"13bcbd83.ab1fe2","name":"aa_enabled","func":"var ip = msg.payload.ip;\nvar user = msg.payload.user;\nvar password = msg.payload.password;\n\n// Update the status with current timestamp\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd  = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm  = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss  = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\nvar currenttime= hh + \":\" + mmm + \":\" + ss;\nvar currentdate= dd + \".\" + mm + \".\" + yyyy;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Last updateChanged: \" + currentdate + \" - \" + currenttime});\n\nmsg.topic = \"type\";\nmsg.payload = ip+\"/param.cgi?cmd=getaudioalarmattr&-usr=\"+user+\"&-pwd=\"+password;\nreturn msg;","outputs":1,"noerr":0,"x":564,"y":568,"wires":[["5ea07282.f2da8c"]]},{"id":"21531d43.05f9f2","type":"mqtt in","z":"13bcbd83.ab1fe2","name":"","topic":"ipcam/login","qos":"2","broker":"47feb3e4.56f11c","x":392,"y":567,"wires":[["bc315627.7ea318"]]},{"id":"e05fa8b0.8d2e88","type":"json","z":"13bcbd83.ab1fe2","name":"","property":"payload","action":"","pretty":false,"x":871,"y":566,"wires":[["5db7972.1d65668"]]},{"id":"5db7972.1d65668","type":"change","z":"13bcbd83.ab1fe2","name":"change 1/0","rules":[{"t":"change","p":"payload.aa_enable","pt":"msg","from":"0","fromt":"str","to":"disabled","tot":"str"},{"t":"change","p":"payload.aa_enable","pt":"msg","from":"1","fromt":"str","to":"enabled","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":994,"y":566,"wires":[["91abfd33.6d186"]]},{"id":"bc315627.7ea318","type":"json","z":"13bcbd83.ab1fe2","name":"","property":"payload","action":"","pretty":false,"x":450,"y":619,"wires":[["ad2301ba.7ecf2"]]},{"id":"5ea07282.f2da8c","type":"change","z":"13bcbd83.ab1fe2","name":"","rules":[{"t":"set","p":"url","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":620,"wires":[["8e8eb1d2.917c2"]]},{"id":"86f4a6aa.505e18","type":"string","z":"13bcbd83.ab1fe2","name":"toJSON","methods":[{"name":"delRightMost","params":[{"type":"str","value":";"}]},{"name":"append","params":[{"type":"str","value":" }"}]},{"name":"prepend","params":[{"type":"str","value":"{ "}]},{"name":"replaceAll","params":[{"type":"str","value":"var "},{"type":"str","value":"\""}]},{"name":"replaceAll","params":[{"type":"str","value":"="},{"type":"str","value":"\":"}]},{"name":"replaceAll","params":[{"type":"str","value":";"},{"type":"str","value":","}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":830,"y":620,"wires":[["e05fa8b0.8d2e88"]]},{"id":"5b58098b.bc2538","type":"comment","z":"13bcbd83.ab1fe2","name":"SET Audio Detection State","info":"","x":120,"y":460,"wires":[]},{"id":"3ef2b6da.cc575a","type":"comment","z":"13bcbd83.ab1fe2","name":"GET Audio Detection State","info":"","x":440,"y":660,"wires":[]},{"id":"47feb3e4.56f11c","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"eb6d72f9.38466","type":"ui_group","z":"","name":"Audio Detection","tab":"7a3391b6.03a81","disp":true,"width":"6","collapse":false},{"id":"7a3391b6.03a81","type":"ui_tab","z":"","name":"Wiki Tutorial","icon":"dashboard"}]