Node-RED est un logiciel qui permet de réaliser une programmation graphique à l’aide de blocs ou nœuds issus d’une bibliothèque standard qui peut être étendue.
L’exemple qui va suivre montre l’utilisation d’un bloc « Function » qui permet de créer donc une fonction en JavaScript comme expliqué ici :
https://nodered.org/docs/writing-functions
Node-RED est disponible dans Raspbian, la distribution standard sur le Raspberry Pi, ainsi que sur la gamme de produits netIOT Edge de Hilscher.
Nous pourrons donc développer une application Node-RED au-dessus du serveur Modbus TCP développé en Python et utilisé dans l’article précédent.
Pour réaliser une interface graphique simple, un tableau de bord, il est possible d’utiliser des composants issus de la bibliothèque suivante :
https://github.com/node-red/node-red-dashboard
J’ai repris le code Python pour pouvoir piloter la matrice de LEDs.
Le code ajouté permet uniquement de modifier la couleur de l’ensemble des LEDs via Modbus TCP.
Cela suffit à ma démonstration, c’est plus joli !
Avec ce petit bout de code on peut piloter le SenseHAT depuis n’importe quel Client Modbus TCP, comme un automate, « Ada for Automation »…
Ci-dessous une vue de l’éditeur Node-RED tournant dans un navigateur, Firefox ici en l’occurrence :
[
{
"id": "fd47437e.e21aa",
"type": "tab",
"label": "SenseHAT",
"disabled": false,
"info": ""
},
{
"id": "77438b0a.41299c",
"type": "modbus-read",
"z": "fd47437e.e21aa",
"name": "ReadSenseHAT",
"topic": "",
"showStatusActivities": true,
"showErrors": true,
"unitid": "",
"dataType": "HoldingRegister",
"adr": "0",
"quantity": "10",
"rate": "1000",
"rateUnit": "ms",
"delayOnStart": false,
"startDelayTime": "",
"server": "b96d491a.dcdc78",
"useIOFile": false,
"ioFile": "",
"useIOForPayload": false,
"x": 120,
"y": 140,
"wires": [
[
"4a16a9de.7c964"
],
[
"50d27a17.1ae20c",
"f74fbbe6.028478"
]
]
},
{
"id": "4a16a9de.7c964",
"type": "debug",
"z": "fd47437e.e21aa",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 330,
"y": 80,
"wires": []
},
{
"id": "50d27a17.1ae20c",
"type": "debug",
"z": "fd47437e.e21aa",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 330,
"y": 120,
"wires": []
},
{
"id": "f74fbbe6.028478",
"type": "function",
"z": "fd47437e.e21aa",
"name": "DataSplitter",
"func": "var Temperature = { payload:msg.payload.buffer.readFloatBE(0) };\nvar Pressure = { payload:msg.payload.buffer.readFloatBE(4) };\nvar Humidity = { payload:msg.payload.buffer.readFloatBE(8) };\nvar Orientation = { payload:msg.payload.buffer.readFloatBE(12) };\nreturn [ Temperature, Pressure, Humidity, Orientation ];",
"outputs": 4,
"noerr": 0,
"x": 330,
"y": 180,
"wires": [
[
"9ad2be63.c8adc",
"684f463e.a67e08"
],
[
"2526530a.198c7c"
],
[
"5f81af96.9837a"
],
[
"ddd2a50b.358408"
]
]
},
{
"id": "9ad2be63.c8adc",
"type": "debug",
"z": "fd47437e.e21aa",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 570,
"y": 160,
"wires": []
},
{
"id": "66618513.e64734",
"type": "modbus-write",
"z": "fd47437e.e21aa",
"name": "WriteSenseHAT",
"showStatusActivities": true,
"showErrors": true,
"unitid": "",
"dataType": "MHoldingRegisters",
"adr": "20",
"quantity": "3",
"server": "b96d491a.dcdc78",
"x": 560,
"y": 620,
"wires": [
[
"3941225c.7f078e"
],
[]
]
},
{
"id": "3941225c.7f078e",
"type": "debug",
"z": "fd47437e.e21aa",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 750,
"y": 620,
"wires": []
},
{
"id": "5a82fd29.44ccac",
"type": "function",
"z": "fd47437e.e21aa",
"name": "RGB",
"func": "var msg_out = { payload:[] };\nmsg_out.payload[0] = msg.payload.r; \nmsg_out.payload[1] = msg.payload.g; \nmsg_out.payload[2] = msg.payload.b; \nreturn msg_out;",
"outputs": 1,
"noerr": 0,
"x": 410,
"y": 620,
"wires": [
[
"66618513.e64734"
]
]
},
{
"id": "b461cb2e.d156",
"type": "ui_colour_picker",
"z": "fd47437e.e21aa",
"name": "",
"label": "LED",
"group": "be67e649.357968",
"format": "rgb",
"outformat": "object",
"showSwatch": true,
"showPicker": true,
"showValue": true,
"showHue": false,
"showAlpha": false,
"showLightness": true,
"dynOutput": "false",
"order": 0,
"width": 0,
"height": 0,
"passthru": true,
"topic": "",
"x": 270,
"y": 620,
"wires": [
[
"5a82fd29.44ccac"
]
]
},
{
"id": "99dde901.715ce",
"type": "ui_gauge",
"z": "fd47437e.e21aa",
"name": "Temperature Gauge",
"group": "7e7ef767.3e339",
"order": 1,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Temperature",
"label": "°C",
"format": "{{value | number:1}}",
"min": "-30",
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 780,
"y": 220,
"wires": []
},
{
"id": "a0adf36d.51b37",
"type": "ui_gauge",
"z": "fd47437e.e21aa",
"name": "Humidity Gauge",
"group": "26985721.743b18",
"order": 1,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Humidity",
"label": "%",
"format": "{{value | number:1}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 760,
"y": 420,
"wires": []
},
{
"id": "9922e191.af4fc8",
"type": "ui_gauge",
"z": "fd47437e.e21aa",
"name": "Orientation",
"group": "d4761c71.80721",
"order": 4,
"width": 0,
"height": 0,
"gtype": "compass",
"title": "Orientation",
"label": "°",
"format": "{{value | number:1}}",
"min": "0",
"max": "360",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 750,
"y": 520,
"wires": []
},
{
"id": "ba444fa5.e56808",
"type": "ui_chart",
"z": "fd47437e.e21aa",
"name": "Pressure Chart",
"group": "a3b17833.ce9cf8",
"order": 3,
"width": 0,
"height": 0,
"label": "Pressure",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "no data",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "3600",
"cutout": 0,
"useOneColor": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"useOldStyle": false,
"x": 760,
"y": 360,
"wires": [
[],
[]
]
},
{
"id": "684f463e.a67e08",
"type": "rbe",
"z": "fd47437e.e21aa",
"name": "",
"func": "deadbandEq",
"gap": "0.1",
"start": "",
"inout": "out",
"property": "payload",
"x": 570,
"y": 220,
"wires": [
[
"99dde901.715ce",
"dea80a9.06a4af8"
]
]
},
{
"id": "5f81af96.9837a",
"type": "rbe",
"z": "fd47437e.e21aa",
"name": "",
"func": "deadbandEq",
"gap": "0.1",
"start": "",
"inout": "out",
"property": "payload",
"x": 570,
"y": 420,
"wires": [
[
"a0adf36d.51b37",
"af44a971.a42448"
]
]
},
{
"id": "2526530a.198c7c",
"type": "rbe",
"z": "fd47437e.e21aa",
"name": "",
"func": "deadbandEq",
"gap": "0.1",
"start": "",
"inout": "out",
"property": "payload",
"x": 570,
"y": 320,
"wires": [
[
"ba444fa5.e56808",
"ea0515e0.796008"
]
]
},
{
"id": "ddd2a50b.358408",
"type": "rbe",
"z": "fd47437e.e21aa",
"name": "",
"func": "deadbandEq",
"gap": "0.1",
"start": "",
"inout": "out",
"property": "payload",
"x": 570,
"y": 520,
"wires": [
[
"9922e191.af4fc8"
]
]
},
{
"id": "9a71ea81.cac458",
"type": "ui_button",
"z": "fd47437e.e21aa",
"name": "",
"group": "be67e649.357968",
"order": 0,
"width": 0,
"height": 0,
"passthru": false,
"label": "Red",
"color": "",
"bgcolor": "Red",
"icon": "",
"payload": "{"r":255,"g":0,"b":0,"a":1}",
"payloadType": "json",
"topic": "",
"x": 90,
"y": 580,
"wires": [
[
"b461cb2e.d156"
]
]
},
{
"id": "6d6a4864.12d508",
"type": "ui_button",
"z": "fd47437e.e21aa",
"name": "",
"group": "be67e649.357968",
"order": 0,
"width": 0,
"height": 0,
"passthru": false,
"label": "Green",
"color": "",
"bgcolor": "Green",
"icon": "",
"payload": "{"r":0,"g":255,"b":0,"a":1}",
"payloadType": "json",
"topic": "",
"x": 90,
"y": 620,
"wires": [
[
"b461cb2e.d156"
]
]
},
{
"id": "e63c60a8.faae7",
"type": "ui_button",
"z": "fd47437e.e21aa",
"name": "",
"group": "be67e649.357968",
"order": 0,
"width": 0,
"height": 0,
"passthru": false,
"label": "Blue",
"color": "",
"bgcolor": "Blue",
"icon": "",
"payload": "{"r":0,"g":0,"b":255,"a":1}",
"payloadType": "json",
"topic": "",
"x": 90,
"y": 660,
"wires": [
[
"b461cb2e.d156"
]
]
},
{
"id": "dea80a9.06a4af8",
"type": "ui_chart",
"z": "fd47437e.e21aa",
"name": "Temperature Chart",
"group": "7e7ef767.3e339",
"order": 2,
"width": 0,
"height": 0,
"label": "Temperature",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "no data",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "3600",
"cutout": 0,
"useOneColor": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"useOldStyle": false,
"x": 770,
"y": 260,
"wires": [
[],
[]
]
},
{
"id": "ea0515e0.796008",
"type": "ui_gauge",
"z": "fd47437e.e21aa",
"name": "Pressure Gauge",
"group": "a3b17833.ce9cf8",
"order": 1,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Pressure",
"label": "mbar",
"format": "{{value | number:1}}",
"min": "260",
"max": "1260",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 770,
"y": 320,
"wires": []
},
{
"id": "af44a971.a42448",
"type": "ui_chart",
"z": "fd47437e.e21aa",
"name": "Humidity Chart",
"group": "26985721.743b18",
"order": 3,
"width": 0,
"height": 0,
"label": "Humidity",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "no data",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "3600",
"cutout": 0,
"useOneColor": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"useOldStyle": false,
"x": 760,
"y": 460,
"wires": [
[],
[]
]
},
{
"id": "fa47ae1e.212af",
"type": "ui_button",
"z": "fd47437e.e21aa",
"name": "",
"group": "be67e649.357968",
"order": 0,
"width": 0,
"height": 0,
"passthru": false,
"label": "Black",
"color": "",
"bgcolor": "Black",
"icon": "",
"payload": "{"r":0,"g":0,"b":0,"a":1}",
"payloadType": "json",
"topic": "",
"x": 90,
"y": 700,
"wires": [
[
"b461cb2e.d156"
]
]
},
{
"id": "5768184e.c56d7",
"type": "ui_button",
"z": "fd47437e.e21aa",
"name": "",
"group": "be67e649.357968",
"order": 0,
"width": 0,
"height": 0,
"passthru": false,
"label": "White",
"color": "Black",
"bgcolor": "White",
"icon": "",
"payload": "{"r":255,"g":255,"b":255,"a":1}",
"payloadType": "json",
"topic": "",
"x": 90,
"y": 740,
"wires": [
[
"b461cb2e.d156"
]
]
},
{
"id": "b96d491a.dcdc78",
"type": "modbus-client",
"z": "",
"name": "Sense-OMB",
"clienttype": "tcp",
"bufferCommands": true,
"stateLogEnabled": false,
"tcpHost": "127.0.0.1",
"tcpPort": "1502",
"tcpType": "DEFAULT",
"serialPort": "/dev/ttyUSB",
"serialType": "RTU-BUFFERD",
"serialBaudrate": "9600",
"serialDatabits": "8",
"serialStopbits": "1",
"serialParity": "none",
"serialConnectionDelay": "100",
"unit_id": 1,
"commandDelay": 1,
"clientTimeout": 1000,
"reconnectTimeout": 2000
},
{
"id": "be67e649.357968",
"type": "ui_group",
"z": "",
"name": "Output",
"tab": "8664d22c.b212",
"order": 5,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "7e7ef767.3e339",
"type": "ui_group",
"z": "",
"name": "Temperature",
"tab": "8664d22c.b212",
"order": 1,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "26985721.743b18",
"type": "ui_group",
"z": "",
"name": "Humidity",
"tab": "8664d22c.b212",
"order": 3,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "d4761c71.80721",
"type": "ui_group",
"z": "",
"name": "Inertial Measurement Unit",
"tab": "8664d22c.b212",
"order": 4,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "a3b17833.ce9cf8",
"type": "ui_group",
"z": "",
"name": "Pressure",
"tab": "8664d22c.b212",
"order": 2,
"disp": true,
"width": "6",
"collapse": false
},
{
"id": "8664d22c.b212",
"type": "ui_tab",
"z": "",
"name": "SenseHAT",
"icon": "dashboard",
"order": 1
}
]
Ce « flow » procède donc à une lecture des mesures disponibles dans le SenseHAT, avec un nœud qui effectue une requête FC3, (lecture de registres), et une fonction (DataSplitter) qui découpe la réponse en ses composantes.
Une fonction prend un message en entrée et peut retourner un ou plusieurs messages en sortie.
Une mesure physique évolue sans cesse, les blocs « deadband » permettent de limiter le rafraichissement de l’interface utilisateur qui pourrait saturer si l’on augmentait la fréquence de la lecture.
Comme l’UI n’affiche qu’un chiffre après la virgule, le « deadband » est configuré pour ne laisser passer la valeur que si l’écart avec la valeur précédente est supérieur ou égal à 0.1.
Enfin, la mesure est affichée grâce aux composants graphiques du tableau de bord, jauges et courbes de tendance.
Pour ce qui est des LEDs, il est possible de choisir une couleur dans la palette ou une présélection avec les boutons.
Elles sont pour finir transmises au SenseHAT avec la fonction FC16 (écriture de registres).
Mon WordPress ne permet pas que je vous donne les fichiers .py, sécurité oblige.