Raspberry Pi : un tableau de bord pour votre automate Siemens S7

Bonjour,

Si votre installation comporte des automates Siemens S7, vous êtes bien en peine de discuter selon le protocole Modbus bien que cela soit possible. Et vous vous demandez comment réaliser vous aussi un superbe tableau de bord pour votre automate comme celui exposé ici.

C’est très possible et nous allons pouvoir avec Node-RED mettre en œuvre de nouveaux nœuds permettant la communication S7 via ISO on TCP / RFC 1006 :
https://flows.nodered.org/node/node-red-contrib-s7comm

Ces nœuds ont été développés par Hilscher :
https://github.com/Hilscher/node-red-contrib-s7comm

sur la base de l’excellent travail de ce projet :
https://github.com/plcpeople/nodeS7

Vous voudrez bien consulter la littérature fournie par ces projets pour plus d’information.

Disposant d’une CPU Siemens S7-1500 PN j’ai donc pu tester ces blocs et j’ai créé ce « flow » que je partage :

Le « flow » exporté est ici :
Node-RED-S7-1500-RFC1006-0

Il réalise la lecture du %MW0, sa mise en forme et l’affichage dans une jauge.
L’état de la communication est également affiché.
Un potentiomètre permet de régler une valeur écrite dans ce même %MW0, ce qui suffit à ma démonstration.

Le code de la fonction est très simple. On récupère sur une sortie la valeur qui nous intéresse et l’on teste l’état de la communication que l’on retourne sur la seconde sortie.

var msg_out = {payload:msg.payload.value[0]};
var msg_com = {payload:(msg.payload.error === 0)};
return [msg_out, msg_com];

Les nœuds peuvent accéder en lecture et écriture aux différentes zones mémoire de l’automate (entrée, sortie, mémento, blocs de données, tempo et compteurs) et tous les types de données élémentaires, aussi les chaines de caractères S7, sont supportés.

Ainsi le « flow » montre l’exemple du réel %MD2. La valeur est aussi envoyée vers un autre « flow » connecté à une base de données « InfluxDB » mais c’est une autre histoire que je vous raconterai ce tantôt.

Et voici un beau tableau de bord pour votre S7-1500 :

Si votre CPU ne possède pas de connexion TCP/IP, rien n’est perdu !

En effet, vous pouvez lui adjoindre un NL 50-MPI (ou un NT 50-MPI) connecté sur le port MPI ou PROFIBUS.

Ces passerelles ont déjà fait l’objet de plusieurs articles.

Ainsi je peux faire la même chose avec ma CPU Siemens S7 315-2DP :

Les nœuds Node-RED S7-Comm sont installés par défaut dans toutes les passerelles de la gamme netIOT Edge.

Ces équipements industriels iront très bien à côté de votre CPU Siemens ! 😉

Cordialement,
Stéphane

Raspberry Pi : un tableau de bord pour votre automate Modbus

Bonjour,

L’article précédent montrait la réalisation d’un tableau de bord pour un serveur Modbus TCP un peu particulier avec Node-RED.

Il est bien sûr possible d’appliquer ces mêmes technologies pour créer un tableau de bord quel que soit le serveur Modbus TCP ou l’esclave Modbus RTU.

En l’occurrence, j’ai à ma disposition un automate M340 de SCHNEIDER ELECTRIC, muni d’un coupleur NOC04101, et qui possède, entre autres, la fonctionnalité serveur Modbus TCP et le « flow » ci-après réalise les opérations suivantes :

D’une part, on lit et on affiche dans une jauge la valeur du %MW200.
On trouve donc les nœuds suivants :

  • un nœud qui effectue la lecture du %MW200, fonction FC3, lecture de registres internes,
  • un autre, l’extraction et la mise en forme de la donnée pour la jauge,
  • et la jauge elle-même.

D’autre part, un potentiomètre permet de régler la valeur du %MW210.
Il y a ainsi :

  • le nœud du potentiomètre,
  • et celui réalise l’écriture du %MW210, FC6, écriture d’un registre interne.

Le code de la fonction d’extraction est très simple, la requête FC3 remonte un tableau de données et l’on ne s’intéresse qu’à la première :

var msg_out = {payload:msg.payload[0]};
return msg_out;

Le programme automate se contente de recopier la valeur du %MW210 dans le %MW200 pour les besoins de ma démonstration.

[
    {
        "id": "43e491b2.0b35a8",
        "type": "tab",
        "label": "Modbus",
        "disabled": false,
        "info": ""
    },
    {
        "id": "6ca8bbe5.c10cfc",
        "type": "modbus-read",
        "z": "43e491b2.0b35a8",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "showErrors": false,
        "unitid": "",
        "dataType": "HoldingRegister",
        "adr": "200",
        "quantity": "1",
        "rate": "1",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "43c0221.3e519dc",
        "x": 150,
        "y": 100,
        "wires": [
            [
                "d1453b66.31e57",
                "74b2d916.437c08"
            ],
            []
        ]
    },
    {
        "id": "d1453b66.31e57",
        "type": "debug",
        "z": "43e491b2.0b35a8",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 350,
        "y": 100,
        "wires": []
    },
    {
        "id": "35d64d2a.3a0dfa",
        "type": "ui_gauge",
        "z": "43e491b2.0b35a8",
        "name": "Modbus Gauge",
        "group": "338d75bf.8d7ca2",
        "order": 1,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "MW200 Gauge",
        "label": "units",
        "format": "{{value}}",
        "min": 0,
        "max": "65535",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 500,
        "y": 180,
        "wires": []
    },
    {
        "id": "74b2d916.437c08",
        "type": "function",
        "z": "43e491b2.0b35a8",
        "name": "",
        "func": "var msg_out = {payload:msg.payload[0]};\nreturn msg_out;",
        "outputs": 1,
        "noerr": 0,
        "x": 330,
        "y": 140,
        "wires": [
            [
                "35d64d2a.3a0dfa",
                "de0996f0.d1aef"
            ]
        ]
    },
    {
        "id": "de0996f0.d1aef",
        "type": "debug",
        "z": "43e491b2.0b35a8",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 490,
        "y": 140,
        "wires": []
    },
    {
        "id": "82cf117c.244d88",
        "type": "modbus-write",
        "z": "43e491b2.0b35a8",
        "name": "",
        "showStatusActivities": false,
        "showErrors": false,
        "unitid": "",
        "dataType": "HoldingRegister",
        "adr": "210",
        "quantity": "1",
        "server": "43c0221.3e519dc",
        "x": 500,
        "y": 320,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "7fad2d38.5ba184",
        "type": "ui_slider",
        "z": "43e491b2.0b35a8",
        "name": "Modbus MW210 Slider",
        "label": "MW210 Slider",
        "group": "338d75bf.8d7ca2",
        "order": 0,
        "width": 0,
        "height": 0,
        "passthru": true,
        "topic": "",
        "min": 0,
        "max": "65535",
        "step": 1,
        "x": 160,
        "y": 320,
        "wires": [
            [
                "82cf117c.244d88"
            ]
        ]
    },
    {
        "id": "43c0221.3e519dc",
        "type": "modbus-client",
        "z": "43e491b2.0b35a8",
        "name": "M340",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "tcpHost": "192.168.1.100",
        "tcpPort": "502",
        "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": "338d75bf.8d7ca2",
        "type": "ui_group",
        "z": "43e491b2.0b35a8",
        "name": "Modbus",
        "tab": "52e4f040.d2daf",
        "order": 1,
        "disp": true,
        "width": "6"
    },
    {
        "id": "52e4f040.d2daf",
        "type": "ui_tab",
        "z": "43e491b2.0b35a8",
        "name": "Modbus",
        "icon": "dashboard",
        "order": 3
    }
]

Le code du flow est disponible ici :
Node-RED-M340-OMB0

Et le tableau de bord résultant ressemble à ceci :

Je suis certain que vous voyez déjà toutes les opportunités qui s’offrent à vous pour un investissement somme toute modique.

Avec un œil sur votre automate depuis n’importe quel navigateur, de votre bureau ou sur votre mobile, vous pourrez vous consacrer à d’autres tâches comme apprendre Ada 😉 :
https://learn.adacore.com/index.html#

Bien sûr, lorsque vous vous serez fait la main sur votre Raspberry Pi, vous pourrez compter sur une version industrialisée de celui ci comme le netPI CORE 3 :
https://www.hilscher.com/products/product-groups/industrial-internet-industry-40/netiot-edge/niot-e-npi3-en/

Cordialement,
Stéphane

Raspberry Pi 3 / SenseHAT / Node-RED

Bonjour,

J’ai eu l’occasion de travailler sur un sujet déjà évoqué, le Raspberry Pi équipé d’un SenseHAT, un matériel avec lequel j’ai voulu faire une démonstration de Node-RED.

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.

Il existe à ce jour une grande variété de nœuds :
https://flows.nodered.org

Il est aussi bien sûr possible de créer ses propres blocs :
https://nodered.org/docs/creating-nodes

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.

Un site est dédié à l’offre spécifique Industrial Internet et Industry 4.0 : netIOT – Industrial Cloud Communication

Il existe des nœuds pour gérer le protocole Modbus dans Node-RED :
https://flows.nodered.org/node/node-red-contrib-modbus

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 !

#!/usr/bin/env python
# -*- coding: utf_8 -*-
"""
 Modbus TestKit: Implementation of Modbus protocol in python

 (C)2009 - Luc Jean - luc.jean@gmail.com
 (C)2009 - Apidev - http://www.apidev.fr

 This is distributed under GNU LGPL license, see license.txt
"""


import sys
import struct
import numpy as np

import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp

"""from sense_emu import SenseHat"""
from sense_hat import SenseHat

import kbhit, time;

sense = SenseHat()

def main():
    """main"""

    kbhit.init();
    running = True;

    logger = modbus_tk.utils.create_logger(name="console", record_format="%(message)s")

    screen = np.zeros((8, 8, 3), dtype=np.uint8)
    actual_color_value1 = int(0)
    actual_color_value2 = int(0)
    actual_color_value3 = int(0)

    try:
        #Create the server
        server = modbus_tcp.TcpServer(port=1502)
        logger.info("running...")
        logger.info("enter 'q' for closing the server")

        server.start()

        slave_1 = server.add_slave(1)
        slave_1.add_block('0', cst.HOLDING_REGISTERS, 0, 100)

        while running:
            if kbhit.kbhit():
                ch = kbhit.getch();
                if 'q' == ch:
                    running = False;

            north = sense.compass;
            """print("North: %s" %north);"""
           
            slave_1.set_values('0', 0, struct.unpack('>HH', struct.pack('>f', sense.temp)))
            slave_1.set_values('0', 2, struct.unpack('>HH', struct.pack('>f', sense.pressure)))
            slave_1.set_values('0', 4, struct.unpack('>HH', struct.pack('>f', sense.humidity)))
            slave_1.set_values('0', 6, struct.unpack('>HH', struct.pack('>f', north)))

            out_values = slave_1.get_values('0', 20, 3)

            color_value1 = out_values[0]
            color_value2 = out_values[1]
            color_value3 = out_values[2]

            if actual_color_value1 != color_value1 or actual_color_value2 != color_value2 or actual_color_value3 != color_value3 :
                color = (color_value1, color_value2, color_value3)
                screen[0:8, 0:8, :] = color
                sense.set_pixels([pixel for row in screen for pixel in row])
                actual_color_value1 = color_value1
                actual_color_value2 = color_value2
                actual_color_value3 = color_value3

            """time.sleep(0.05);"""

    finally:
        server.stop()
        kbhit.restore();

if __name__ == "__main__":
    main()

Avec ce petit bout de code on peut piloter le SenseHAT depuis n’importe quel Client Modbus TCP, comme un automate, « Ada for Automation »…

… ou Node-RED donc :

Ci-dessous une vue de l’éditeur Node-RED tournant dans un navigateur, Firefox ici en l’occurrence :

Il est possible d’exporter des portions de programme au format JSON.
Ci-dessous le « flow » exporté dans le presse-papiers et passé à la moulinette pour le rendre joli :
https://jsonformatter.org/json-pretty-print

[
  {
    "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.

Ci-gît le code du DataSplitter :

var Temperature = { payload:msg.payload.buffer.readFloatBE(0) };
var Pressure = { payload:msg.payload.buffer.readFloatBE(4) };
var Humidity = { payload:msg.payload.buffer.readFloatBE(8) };
var Orientation = { payload:msg.payload.buffer.readFloatBE(12) };
return [ Temperature, Pressure, Humidity, Orientation ];

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.

Les composantes Rouge, Vert et Bleu sont mises en forme par la fonction RGB :

var msg_out = { payload:[] };
msg_out.payload[0] = msg.payload.r;
msg_out.payload[1] = msg.payload.g;
msg_out.payload[2] = msg.payload.b;
return msg_out;

Elles sont pour finir transmises au SenseHAT avec la fonction FC16 (écriture de registres).

Et ça nous donne un superbe tableau de bord :

Mon WordPress ne permet pas que je vous donne les fichiers .py, sécurité oblige.

Veuillez trouver ci-dessous les liens vers les fichiers Python et Node-RED :

Node-RED exporte tout sur une seule ligne en version compact :
Node-RED 20181121

Le même fichier passé à la moulinette ou exporté en version formatée :
Node-RED 20181121-1

Le lien que j’avais fourni n’est plus d’actualité :
kbhit.py

Le serveur Modbus TCP / Sense HAT :
sense-omb.py

Cordialement,
Stéphane