Archives de catégorie : Labo

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

Raspberry Pi 3 / SenseHAT / netHAT / netPi

Bonjour,

Je cherchais pour mes expériences une solution qui soit ludique, et aussi accessible à tout un chacun, et mes tribulations m’ont conduit à sélectionner un combo Raspberry Pi 3 + SenseHAT.

Hilscher France a donc fait l’acquisition de ces éléments chez KUBII :

Starter Kit Officiel Pi3
https://www.kubii.fr/fr/kits-raspberry-pi/1637-kit-demarrage-raspberry-pi3-3272496004207.html

Raspberry Pi Sense Hat
https://www.kubii.fr/fr/cartes-extension-cameras-raspberry-pi/1081-raspberry-pi-sense-hat-640522710799.html

Pourquoi donc un tel investissement me direz vous ?

Sans doute parce que depuis quelques temps cette plateforme économique et performante permet d’imaginer tout un tas d’utilisations dans ce qu’il est convenu d’appeler l’IoT, l’IIoT, Industry 4.0, etc…

Aussi, à l’instar de certains confrères, la société Hilscher a développé certains produits autour de cette plateforme comme le netHAT, le netPi et le Edge Gateway « Connect » :
https://www.netiot.com/interface/nethat/
https://www.netiot.com/netpi/industrial-raspberry-pi-3/
https://www.netiot.com/edge/

Hilscher innove en testant le canal de vente Amazon et l’on peut y acheter le netHAT et le netPi :
https://www.amazon.fr/Hilscher-NXHAT52-RTE-nethat-52-de-RTE/dp/B01MFH0FP9
https://www.amazon.fr/Industrial-Raspberry-Industry-Communication-4×1-2Ghz-Real-Time-Ethernet/dp/B0756XD2CN

Pour ces produits, Hilscher France n’assure pas de support.
Cependant, l’on peut vous proposer une formation !

Le netHAT est fourni avec un pilote Linux compilé, des firmwares en version limitée à 32 octets d’E/S pour EtherCAT, Ethernet/IP et PROFINET IO Device et bien sûr de la documentation.

Le tout s’installe sans encombre sur la Raspbian et ça tombe en marche comme sur le plan.

Vous pouvez donc vous familiariser avec la technologie Hilscher pour une quarantaine d’euros, ce qui est modique vous en conviendrez aisément.

Bien sûr, « Ada for Automation » peut tout à fait être utilisé avec le netHAT. J’y reviendrai bien sûr.

Avec un Raspberry Pi + un netHAT, on peut aussi tester le concept netPi et développer des applications qui tourneront sur le netPi sans modification.

Bref, je souhaitais monter une manipulation avec un Rasberry Pi 3, un SenseHAT pour des capteurs pas chers et un netHAT pour connecter ce bijou de technologie à votre automate préféré.

En fait, le SenseHAT et le netHAT ne peuvent pas se monter l’un sur l’autre comme on pourrait le penser de prime abord.

Je pensais développer un binding Ada pour le SenseHAT mais ce n’est pas si simple.
Le langage choisi par l’équipe Raspberry est plutôt le Python et la plupart des bibliothèques fournies pour les « HAT » sont en Python.
La bibliothèque disponible pour le SenseHAT est donc en Python aussi et utilise d’une part une bibliothèque en C++ qui gère nombre de capteurs et d’autre part le framebuffer pour les LEDs et un IO device pour le joystick.

Monsieur Phil Munts, que je remercie, m’a bien fait part de sa librairie :
http://git.munts.com/libsimpleio/ada/

Mais je voulais quelque chose de super vite fait et j’ai penché pour une solution mettant en œuvre un framework Modbus développé par un collègue, Monsieur Luc JEAN, que je remercie chaleureusement :
https://github.com/ljean/modbus-tk

Pourquoi donc ? « Ada for Automation » disposant de la fonctionnalité Modbus TCP Client et le framework permettant de réaliser très simplement un serveur Modbus TCP, il suffisait donc de raccrocher les données des capteurs dans les registres du serveur.

J’ai utilisé également ce bout de code qui m’a bien aidé, je remercie aussi son auteur :
https://frank-deng.github.io/python-kbhit.en.html

Le SenseHAT dispose d’une application de simulation avec interface graphique et il est possible d’utiliser celle-ci en lieu et place du matériel tel que dans l’exemple suivant.

SenseHAT Simu GUI
SenseHAT Simu GUI

On y démarre depuis un terminal :

python 3 sense-omb.py
python 3 sense-omb.py

Et on teste par exemple avec Modbus Poll :

Modbus Poll Example
Modbus Poll Example

En Python, c’est une vingtaine de lignes de code pour remonter température, pression, hygrométrie et cap :

#!/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 modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp

from sense_emu 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")

    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;

            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', sense.compass)))

            time.sleep(0.1);

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

if __name__ == "__main__":
    main()

C’est naturellement pas très temps réel mais c’est très bien pour mon cas d’école.

Et en plus, on peut facilement imaginer de reproduire ce schéma avec d’autres HATs comme le « Automation HAT » par exemple :
https://shop.pimoroni.com/products/automation-hat

Vous pouvez donc remonter les données du SenseHAT vers votre automate préféré disposant d’une connectivité Modbus TCP Client.
Bon, en travaillant un peu, ça doit fonctionner dans les deux sens, hein !

Quid du netHAT ? Si on utilise l’application de simulation SenseHAT on peut bien sûr le mettre sur le même Raspberry Pi et se connecter en local.
Si l’on souhaite de vraies données physiques, il faudra l’installer sur un autre Raspberry Pi, et le faire communiquer avec le premier, toujours en Modbus TCP.
C’est trivial avec « Ada for Automation » et je vous le montrerai ce tantôt.

Cordialement,
Stéphane

cifX : Siemens WinAC RTX – Application de test – CANopen Master – SDO

Bonjour,

Dans les articles précédents concernant l’application de test pour le pilote cifX pour WinAC RTX® il était question de configurer une carte Hilscher cifX 50E-RE avec les firmwares Open Modbus TCP en IO Server, PROFINET IO IRT Device ou Ethernet/IP Adapter.

Faisant suite à la requête d’un de nos clients, une nouvelle version de cette application de test est disponible qui permet, en utilisant une carte Hilscher cifX 50E-CO avec le firmware CANopen Master, de lire une donnée du dictionnaire d’objet d’un esclave via SDO :
http://www.hf-news.fr/download/cifX/WinACRTX2009/cifXWACDriver/20131023.zip

Comme l’application implémente plusieurs options de configuration, ces options sont maintenant configurables depuis l’OB de démarrage.

Enfin, les blocs fonctions gérant les messages ont été retouchés pour traiter d’éventuelles erreurs.

Le bloc fonction qui implémente la lecture via SDO a donc été rajouté :

FB47 CIFX_COM_SDOR0_0 Hilscher cifX Driver : Messaging / CANopen Master SDO Read

La carte Hilscher cifX 50E-CO est configurée avec l’outil SYCON.net en CANopen Master et l’application de test montre la lecture de l’objet (Identity Object, Product code) d’index := 1018, sous-index := 2 de l’esclave d’adresse 2.

Une VAT permet de piloter cette requête et d’afficher le résultat :

cifXWACVATSDO1

Il suffit de renseigner les paramètres de la requête et de lancer la commande (Do).

La réponse est contenue dans les quatre octets de données 00 1A 20 20, soit 1712160 en décimal, ce qui correspond au code produit de la passerelle Hilscher netTAP CANopen esclave utilisée pour les essais.

cifXWACVATSDO2

Votre NanoBox Siemens peut donc se voir adjoindre une fonctionnalité Maitre CANopen et ainsi être tout à fait capable de gérer des esclaves CANopen en prenant en charge les PDO, c’est à dire les données process cycliques, les SDO pour l’accès en lecture et écriture aux données du dictionnaire, et les commandes NMT pour la gestion des modes de marche des nœuds.

N’hésitez pas à nous solliciter pour obtenir un exemple de code convenant à votre besoin.

Cordialement,
Stéphane

cifX : FDT/DTM – Un exemple intéressant – E+H FieldCare

Bonjour,

J’ai abordé dans cet article la technologie FDT/DTM mise en œuvre dans l’outil Hilscher SYCON.net.

J’y décrivais l’utilisation du DTM pour l’instrument Micropilot M FMR 244 de chez Endress + Hauser dans SYCON.net.

S’il est possible d’utiliser les DTM Endress + Hauser dans SYCON.net, il est tout aussi possible d’utiliser les DTM Hilscher avec l’outil FieldCare.

Pour pouvoir utiliser la carte Hilscher cifX DP/DPM dans FieldCare il suffit d’installer SYCON.net, ce qui installera les DTM Hilscher, de mettre à jour le catalogue des DTM FieldCare et de sélectionner le DTM de la carte cifX DP/DPM comme interface PROFIBUS DP V1.

Le seul bémol c’est que ça ne fonctionne pas si l’on sélectionne le Français comme langue d’interface dans FieldCare… En choisissant US English, ça le fait !
Comme de bien entendu j’ai remonté le problème à mes collègues et je ne désespère pas qu’il soit réglé très bientôt.

Voici quelques copies d’écran montrant une configuration avec une carte PROFIBUS DP Hilscher cifX 50-DP Maitre et un Micropilot M FMR 244 de chez Endress + Hauser.

Une vue du catalogue des DTM, avec le minimum d’équipements importés :

FieldCare-DTM-Catalog

Une portion de la configuration de la carte cifX 50-DP :

FieldCare-DTM-cifX-Config

Les diagnostics intégrés de la carte cifX :

FieldCare-DTM-cifX-Diag

Une vue du IO Monitor qui permet d’afficher les données dans la Dual Port Memory de la carte, c’est à dire l’image procédé en octets :

FieldCare-DTM-cifX-IOMonitor

Et pour terminer, une vue de diagnostic de l’équipement Endress + Hauser :

FieldCare-DTM-FMR-Diag

Bien sûr, il est possible de réaliser toutes les autres opérations sur cet équipement comme la configuration en / hors ligne, la sauvegarde des paramètres, la surveillance du procédé…

Cordialement,
Stéphane

cifX : Mise en œuvre : API – Messages – PROFIBUS DP V1 Class 2

Bonjour,

J’ai déjà abordé l’API cifX dans cet article d’introduction et les messages de configuration dans celui-ci entre autres.

Comme je dispose d’un équipement Endress + Hauser Micropilot M FMR 244, un instrument de mesure de niveau radar qui peut répondre à des requêtes PROFIBUS DP V1 Class 2 pour la lecture / écriture des paramètres de l’instrument, j’en profite pour vous donner cet autre exemple d’utilisation de la messagerie Hilscher.

Le document suivant de chez Endress + Hauser nous renseigne sur les éléments adressables de cet instrument :
https://portal.endress.com/wa001/dla/5000415/7572/000/08/BA00249FEN_1311.pdf

L’instrument est DP V0 et supporte une communication PROFIBUS DP V1 Class 2 mais pas la communication PROFIBUS DP V1 Class 1.

La carte Hilscher cifX 50-DP est donc configurée en PROFIBUS DP Maitre et la documentation suivante est nécessaire :
PROFIBUS DP Master Protocol API

On la trouve bien sûr également sur le DVD dans le répertoire Documentation :
6. Programming Manuals\english\4. Protocol Application Programming Interface\PROFIBUS DP Master

Cette documentation nous indique que nous pouvons utiliser les services d’un maitre DP V1 Class 1 ou ceux d’un maitre DP V1 Class 2. Dans cette manipulation nous utiliserons donc la seconde option.

Nous y trouvons la commande à employer pour la lecture d’un bloc de données d’un esclave DP V1 en p.192 :
PROFIBUS_FSPMM2_CMD_READ_REQ/CNF – V1 Class 2 Read Request

Cependant, avant il nous faut ouvrir une connexion entre le maitre Class 2, notre carte cifX, et l’esclave DP.

Nous disposons dans le document E+H au chapitre 5.5.6 Slot/index tables en p.48 de la table des données avec les paramètres nécessaires à l’élaboration de notre requête.

Ainsi essayons de lire par exemple :
Block parameters
Software revision 1 73 16 OSTRING X constant

Il nous faut l’adresse de l’esclave, dans notre cas 2, le numéro de slot, ici 1, l’index, ici 73 et la taille des données, soit 16 pour notre chaine de caractères.

L’algorithme est simple :

  • S’enregistrer auprès de la pile de protocole PROFIBUS DP Maitre pour pouvoir recevoir les indications, notamment celle que nous allons recevoir à notre demande de déconnexion, cf. « Register / Unregister an Application » dans le document netX Dual-Port Memory Interface (DPM).
  • Se connecter à l’esclave, cf. PROFIBUS_FSPMM2_CMD_INITIATE_REQ/CNF– Initiate DPV1C2 Connection
  • Envoyer notre requête de lecture, cf. PROFIBUS_FSPMM2_CMD_READ_REQ/CNF – V1 Class 2 Read Request
  • Envoyer notre requête de déconnexion, cf. PROFIBUS_FSPMM2_CMD_ABORT_REQ/CNF – Request Abort of Connection
  • Purger les indications, cf. PROFIBUS_FSPMM2_CMD_CLOSED_IND/RES – Closed Indication
  • Se dés-enregistrer.

Bien sûr, c’est un code de démonstration. On peut et on doit faire mieux !

D’où le code du jour :

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "rcX_Public.h"
#include "cifxuser.h"
#include "cifxErrors.h"
#include "TLR_Types.h"
#include "ProfibusFspmm_Public.h"
#include "ProfibusFspmm2_Public.h"

#define IO_WAIT_TIMEOUT     10

/*****************************************************************************/
/*! Show error
 *                                                                           */

/*****************************************************************************/
void ShowError( long lError)
{
  if( lError != CIFX_NO_ERROR)
    {
      /* Read driver error description */
      char szError[1024] ={0};
      xDriverGetErrorDescription( lError,  szError, sizeof(szError));
      printf("Error: 0x%X, <%s>\r\n", lError, szError);
    }
}

static uint32_t      ulMsgId         = 0;
static uint32_t      ulCRef          = 0;

/*****************************************************************************/
/*! Register Application Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int Register_Application(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  RCX_REGISTER_APP_REQ_T    *pPktRegisterReq;
  RCX_REGISTER_APP_CNF_T    *pPktRegisterCnf;

  pPktRegisterReq = (RCX_REGISTER_APP_REQ_T *)&tSendPacket;

  pPktRegisterReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktRegisterReq->tHead.ulSrc    = 0x00;
  pPktRegisterReq->tHead.ulDestId = 0x00;
  pPktRegisterReq->tHead.ulSrcId  = 0x00;
  pPktRegisterReq->tHead.ulLen    = 0;
  pPktRegisterReq->tHead.ulId     = ulMsgId++;
  pPktRegisterReq->tHead.ulSta    = 0x00;
  pPktRegisterReq->tHead.ulCmd    = RCX_REGISTER_APP_REQ;
  pPktRegisterReq->tHead.ulExt    = 0x00;
  pPktRegisterReq->tHead.ulRout   = 0x00;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Register Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktRegisterCnf = (RCX_REGISTER_APP_CNF_T *)&tRecvPacket;
          if(pPktRegisterCnf->tHead.ulCmd != RCX_REGISTER_APP_CNF)
            {
              printf("Wrong CNF received : pPktRegisterCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktRegisterCnf->tHead.ulCmd);
            }
          else if(pPktRegisterCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktRegisterCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktRegisterCnf->tHead.ulSta);
            }
          else
            {
              printf("Register Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! Unregister Application Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int Unregister_Application(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  RCX_UNREGISTER_APP_REQ_T    *pPktUnregisterReq;
  RCX_UNREGISTER_APP_CNF_T    *pPktUnregisterCnf;

  pPktUnregisterReq = (RCX_UNREGISTER_APP_REQ_T *)&tSendPacket;

  pPktUnregisterReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktUnregisterReq->tHead.ulSrc    = 0x00;
  pPktUnregisterReq->tHead.ulDestId = 0x00;
  pPktUnregisterReq->tHead.ulSrcId  = 0x00;
  pPktUnregisterReq->tHead.ulLen    = 0;
  pPktUnregisterReq->tHead.ulId     = ulMsgId++;
  pPktUnregisterReq->tHead.ulSta    = 0x00;
  pPktUnregisterReq->tHead.ulCmd    = RCX_UNREGISTER_APP_REQ;
  pPktUnregisterReq->tHead.ulExt    = 0x00;
  pPktUnregisterReq->tHead.ulRout   = 0x00;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Unregister Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktUnregisterCnf = (RCX_UNREGISTER_APP_CNF_T *)&tRecvPacket;
          if(pPktUnregisterCnf->tHead.ulCmd != RCX_UNREGISTER_APP_CNF)
            {
              printf("Wrong CNF received : pPktUnregisterCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktUnregisterCnf->tHead.ulCmd);
            }
          else if(pPktUnregisterCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktUnregisterCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktUnregisterCnf->tHead.ulSta);
            }
          else
            {
              printf("Unregister Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Initiate Connection Request to a PROFIBUS DP Slave
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Initiate(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_INITIATE_REQ_T    *pPktInitiateReq;
  PROFIBUS_FSPMM2_PACKET_INITIATE_CNF_T    *pPktInitiateCnf;

  pPktInitiateReq = (PROFIBUS_FSPMM2_PACKET_INITIATE_REQ_T *)&tSendPacket;

  pPktInitiateReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktInitiateReq->tHead.ulSrc    = 0x00;
  pPktInitiateReq->tHead.ulDestId = 0x00;
  pPktInitiateReq->tHead.ulSrcId  = 0x00;
  pPktInitiateReq->tHead.ulLen    = PROFIBUS_FSPMM2_INITIATE_REQ_SIZE + 4 + 4;
  pPktInitiateReq->tHead.ulId     = ulMsgId++;
  pPktInitiateReq->tHead.ulSta    = 0x00;
  pPktInitiateReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_INITIATE_REQ;
  pPktInitiateReq->tHead.ulExt    = 0x00;
  pPktInitiateReq->tHead.ulRout   = 0x00;

  pPktInitiateReq->tData.ulRemAdd        =    2;   /* Slave address */
  pPktInitiateReq->tData.usSendTimeout   =  100;

  pPktInitiateReq->tData.bFeaturesSupported1 =  1;
  pPktInitiateReq->tData.bFeaturesSupported2 =  0;

  pPktInitiateReq->tData.bProfileFeaturesSupported1 =  0;
  pPktInitiateReq->tData.bProfileFeaturesSupported2 =  0;
  pPktInitiateReq->tData.usProfileIdentNumber       =  0;

  pPktInitiateReq->tData.tAddAddrParam.bS_Type =  0;
  pPktInitiateReq->tData.tAddAddrParam.bS_Len  =  2;
  pPktInitiateReq->tData.tAddAddrParam.bD_Type =  0;
  pPktInitiateReq->tData.tAddAddrParam.bD_Len  =  2;

  pPktInitiateReq->tData.tAddAddrParam.abAddParam[0] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[1] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[2] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[3] =  0;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Initiate Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktInitiateCnf = (PROFIBUS_FSPMM2_PACKET_INITIATE_CNF_T *)&tRecvPacket;
          if(pPktInitiateCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_INITIATE_CNF)
            {
              printf("Wrong CNF received : pPktInitiateCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktInitiateCnf->tHead.ulCmd);
            }
          else if(pPktInitiateCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktInitiateCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktInitiateCnf->tHead.ulSta);
              printf("ulRemAdd     = 0x%08X\r\n"
                     "bErrorDecode = 0x%02X\r\n"
                     "bErrorCode1  = 0x%02X\r\n"
                     "bErrorCode2  = 0x%02X\r\n"
                     "usDetail     = 0x%04X\r\n",
                     pPktInitiateCnf->tData.tCnfNeg.ulRemAdd,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorDecode,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorCode1,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorCode2,
                     pPktInitiateCnf->tData.tCnfNeg.usDetail);
            }
          else
            {
              printf("Initiate Request confirmation received.\r\n");
              ulCRef = pPktInitiateCnf->tData.tCnfPos.ulCRef;
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Read Request PROFIBUS DP Slave
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Read(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_READ_REQ_T    *pPktReadReq;
  PROFIBUS_FSPMM2_PACKET_READ_CNF_T    *pPktReadCnf;

  pPktReadReq = (PROFIBUS_FSPMM2_PACKET_READ_REQ_T *)&tSendPacket;

  pPktReadReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktReadReq->tHead.ulSrc    = 0x00;
  pPktReadReq->tHead.ulDestId = 0x00;
  pPktReadReq->tHead.ulSrcId  = 0x00;
  pPktReadReq->tHead.ulLen    = PROFIBUS_FSPMM2_READ_REQ_SIZE;
  pPktReadReq->tHead.ulId     = ulMsgId++;
  pPktReadReq->tHead.ulSta    = 0x00;
  pPktReadReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_READ_REQ;
  pPktReadReq->tHead.ulExt    = 0x00;
  pPktReadReq->tHead.ulRout   = 0x00;

  pPktReadReq->tData.ulCRef     = ulCRef;   /* Connection Reference */
  pPktReadReq->tData.ulSlot     =      1;   /* Requested slot */
  pPktReadReq->tData.ulIndex    =     73;   /* Requested index */
  pPktReadReq->tData.ulLength   =     16;   /* Requested data length */

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Read Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktReadCnf = (PROFIBUS_FSPMM2_PACKET_READ_CNF_T *)&tRecvPacket;
          if(pPktReadCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_READ_CNF)
            {
              printf("Wrong CNF received : pPktReadCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktReadCnf->tHead.ulCmd);
            }
          else if(pPktReadCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktReadCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktReadCnf->tHead.ulSta);
            }
          else
            {
              printf("Read Request confirmation received.\r\n");
              printf("Software revision : %.16s\r\n", pPktReadCnf->tData.tCnfPos.abData);
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Abort Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Abort(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_ABORT_REQ_T    *pPktAbortReq;
  PROFIBUS_FSPMM2_PACKET_ABORT_CNF_T    *pPktAbortCnf;

  pPktAbortReq = (PROFIBUS_FSPMM2_PACKET_ABORT_REQ_T *)&tSendPacket;

  pPktAbortReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktAbortReq->tHead.ulSrc    = 0x00;
  pPktAbortReq->tHead.ulDestId = 0x00;
  pPktAbortReq->tHead.ulSrcId  = 0x00;
  pPktAbortReq->tHead.ulLen    = PROFIBUS_FSPMM2_ABORT_REQ_SIZE;
  pPktAbortReq->tHead.ulId     = ulMsgId++;
  pPktAbortReq->tHead.ulSta    = 0x00;
  pPktAbortReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_ABORT_REQ;
  pPktAbortReq->tHead.ulExt    = 0x00;
  pPktAbortReq->tHead.ulRout   = 0x00;

  pPktAbortReq->tData.ulCRef      = ulCRef;   /* Connection Reference */
  pPktAbortReq->tData.bSubnet     = PROFIBUS_FSPMM2_SUBNET_NO;
  pPktAbortReq->tData.bInstance   = PROFIBUS_FSPMM2_INSTANCE_USER;
  pPktAbortReq->tData.bReasonCode =      0;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Abort Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktAbortCnf = (PROFIBUS_FSPMM2_PACKET_ABORT_CNF_T *)&tRecvPacket;
          if(pPktAbortCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_ABORT_CNF)
            {
              printf("Wrong CNF received : pPktAbortCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktAbortCnf->tHead.ulCmd);
            }
          else if(pPktAbortCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktAbortCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktAbortCnf->tHead.ulSta);
            }
          else
            {
              printf("Abort Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

int Purge(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;
  int           iCount          = 3; /* Wait 3 times */

  CIFX_PACKET   tRecvPacket     = {0};

  do
    {
      iCount--;
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          printf("Packet received :\r\n"
                 "ulCmd      = 0x%08X\r\n"
                 "ulDest     = 0x%08X\r\n"
                 "ulSrc      = 0x%08X\r\n"
                 "ulDestId   = 0x%08X\r\n"
                 "ulSrcId    = 0x%08X\r\n",
                 tRecvPacket.tHeader.ulCmd,
                 tRecvPacket.tHeader.ulDest,
                 tRecvPacket.tHeader.ulSrc,
                 tRecvPacket.tHeader.ulDestId,
                 tRecvPacket.tHeader.ulSrcId);

          switch (tRecvPacket.tHeader.ulCmd)
            {
              case PROFIBUS_FSPMM2_CMD_CLOSED_IND:
                {
                  printf("Closed Indication received : tRecvPacket->tHead.ulSta = 0x%08X\r\n",
                         tRecvPacket.tHeader.ulState);

                  tRecvPacket.tHeader.ulDest = 0x20; /* Channel mailbox */
                  tRecvPacket.tHeader.ulCmd  = PROFIBUS_FSPMM2_CMD_CLOSED_RES;

                  lRet = xChannelPutPacket(hChannel, &tRecvPacket, 1000);
                  if(lRet != CIFX_NO_ERROR)
                    {
                      ShowError( lRet);
                    }
                  else
                    {
                      printf("Closed Response sent...\r\n");
                      iResult = 0;
                    }
                  break;
                }

              default:
                {
                  printf("Message received : tRecvPacket->tHead.ulCmd = 0x%08X\r\n",
                         tRecvPacket.tHeader.ulCmd);
                  break;
                }
            }
        }
    }
    while (iCount > 0);
  return iResult;
}

/*****************************************************************************/
/*! The main function
 *   \return 0 on success                                                    */

/*****************************************************************************/
int main(int argc, char* argv[])
{
  HANDLE hDriver  = NULL;
  HANDLE hChannel = NULL;
  long   lRet     = CIFX_NO_ERROR;
  long   lOldRet  = CIFX_NO_ERROR;

  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);

  /* Open the cifX driver */
  lRet = xDriverOpen(&hDriver);
  if(CIFX_NO_ERROR != lRet)
    {
      printf("Error opening driver!\r\n");
      ShowError(lRet);
    }
  else
    {
      lRet = xChannelOpen(hDriver, "cifX0", 0, &hChannel);
      if(CIFX_NO_ERROR != lRet)
        {
          printf("Error opening Channel!\r\n");
          ShowError(lRet);
        }
      else
        {
          printf("\n--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2, Slot 1, Index 73 ---\r\n");

          printf("\n--- Register Application Request ---\r\n");

          if(Register_Application(hChannel) != 0)
            {
              printf("Error registering application\r\n");
            }
          else
            {
              printf("\n--- DPV1 Class 2 Initiate Request PROFIBUS DP Slave 2 ---\r\n");

              if(DPV1Class2_Initiate(hChannel) != 0)
                {
                  printf("Error initiating connection\r\n");
                }
              else
                {
                  printf("\n--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2 ---\r\n");

                  if(DPV1Class2_Read(hChannel) != 0)
                    {
                      printf("Error reading\r\n");
                    }

                  printf("\n--- DPV1 Class 2 Abort Request PROFIBUS DP Slave 2 ---\r\n");

                  if(DPV1Class2_Abort(hChannel) != 0)
                    {
                      printf("Error aborting\r\n");
                    }

          Purge(hChannel);
        }
            }

      if(Unregister_Application(hChannel) != 0)
        {
          printf("Error unregistering application\r\n");
        }

          /* Close the communication channel */
          xChannelClose(hChannel);
        }
      /* Close the cifX driver */
      xDriverClose(hDriver);
    }
  return 0;
}

Et la trace qui nous montre que c’est tombé en marche !

On lit bien la version du logiciel : Software revision : 01.05.00

H:\Steve\GNAT\ENDRESS\C05\exe\DPV1C2_main

--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2, Slot 1, Index 73 ---


--- Register Application Request ---

Register Request sent...

Register Request confirmation received.


--- DPV1 Class 2 Initiate Request PROFIBUS DP Slave 2 ---

Initiate Request sent...

Initiate Request confirmation received.


--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2 ---

Read Request sent...

Read Request confirmation received.

Software revision : 01.05.00        


--- DPV1 Class 2 Abort Request PROFIBUS DP Slave 2 ---

Abort Request sent...

Abort Request confirmation received.

Packet received :

ulCmd      = 0x00004428

ulDest     = 0x00000000

ulSrc      = 0x80169C80

ulDestId   = 0xA009A021

ulSrcId    = 0x00000000

Closed Indication received : tRecvPacket->tHead.ulSta = 0x00000000

Closed Response sent...

Error: 0x800C0019, <No packet available>

Error: 0x800C0019, <No packet available>

Unregister Request sent...

Unregister Request confirmation received.

Cordialement,
Stéphane