Node-RED + MQTT + ESP8266 : comment piloter un système articulé PTZ en WiFi • Domotique et objets connectés à faire soi-même

Nous continuons notre série d’articles sur le pilotage à distance de servomoteur et plus particulièrement les systèmes articulés PTZ (Pan-Tilt). Dans les tutoriels précédents, nous avons vu comment faire pour piloter un servo depuis un smartphone à l’aide des librairies pour IoT Cayenne et Blynk. Dans ce nouveau tutoriel, nous allons en faire de même à l’aide de Node-RED. Pour communiquer avec l’ESP8266 en WiFi (ou depuis internet), nous utiliserons le protocole de communication MQTT (via le broker Mosquitto).

Comment ça marche ?

Le schéma ci-dessous montre comment on peut piloter le système articulé PTZ en Wi-Fi. Nous allons utiliser le module Dashboard UI de Node-RED pour créer 2 sliders (Pan, Tilt). A chaque modification d’un position, Node-RED publie un message sur le broker MQTT. Le message est transmis sur le réseau local (mais aussi via internet en utilisant un broker en ligne tel que cloudMQTT) en WiFi à l’ESP8266. La librairie PubSubClient permet de s’abonner à serveur (Broker) MQTT et de recevoir les messages. Après décodage du message, on déplacer le servomoteur concerné à la position angulaire demandée.

De quoi avez-vous besoin ?

Vous aurez donc besoin d’un ordinateur sur lequel est installé Node-RED. Si vous avez besoin de savoir comment faire, choisissez le tutoriel qui correspond à votre environnement :

Vous aurez également besoin d’un serveur (broker) MQTT. Il en existe plusieurs. Pour ce tutoril, nous utiliserons le broker Open Source Mosquitto développé par la fondation Eclipse. Suivez les instructions de ce tutoriel pour l’installer et le configurer sur Raspbian, Windows ou macOS.

Matériel utilisé pour piloter le bras robotique

Nous allons piloter en WiFi le mini kit Pan/Tilt. Vous pouvez utiliser n’importe quel ESP8266.

Kit robotique 4DOF (4 degrés de liberté)

Le circuit est identique aux tutoriels précédents.

Code Arduino

Pour recevoir (ou envoyer) des messages avec le protocole MQTT, nous allons utiliser la librairie PubSubClient (documentation officielle)

Installation des librairies dans l’IDE Arduino

Avant d’aller plus loin, ouvrez le gestionnaire de librairie et installez les librairies suivantes :

ESP8266

servo(esp8266)

PubSubClient

Comment fonctionne la librairie PubSubClient ?

Ici, nous ne rentrerons pas dans le détail de la connexion au réseau WiFi géré par la librairie ESP8266WiFi. Nous allons uniquement regarder comment s’abonner à plusieurs Topics MQTT et traiter les messages entrants.

La librairie PubSub a besoin d’un objet qui contient la connexion au réseau WiFi. On commence par créer un objet à l’aide de la classe WiFiClient comme ceci

WiFiClient espClient;

Ensuite, on créé un objet client

PubSubClient client(espClient);

Vous trouverez 3 variables au début du programme

  • const char* mqtt_server = “xxx.xxx.xxx.xxx” : c’est l’adresse IP de votre serveur (Broker) MQTT
  • const char* topic_pan = “servo/pan” : topic qui contient l’angle du servo Pan
  • const char* topic_tilt = “servo/tilt” : Topic qui contient l’angle du servo Tilt

Dans la fonction setup(), on réalise les opérations suivantes

  • On attache chaque servo sur le Pin qui lui correspond. Par exemple Pan sur D5 et Tilt sur D6
    pan.attach(D5);
    tilt.attach(D6);
  • On appel la fonction setupWiFi()  qui s’occupe de connecter l’ESP8266 au réseau WiFi local
  • On se connecte au broker MQTT sur le port 1883 (le port par défaut) client.setServer(mqtt_server, 1883)
  • Enfin on définit une fonction callback qui sera appelée à chaque fois qu’un MQTT message est reçu client.setCallback(callback)

Réception des messages MQTT dans le programme Arduino

La fonction callback (documentation) permet donc de récupérer les messages entrants. Elle contient le topic, le message (payload) et la longueur du message. Avant de pouvoir traiter le message, il faut le convertir en string. Il n’y a aucun helper pour décoder les messages qui arrivent sous la forme d’un tableau de byte. Heureusement, on trouve beaucoup d’exemples sur internet. Voici comment décoder le message entrant

String string;
for (int i = 0; i < length; i++) {
   string+=((char)payload[i]);  
}

La fonction servo.write(position) acceptant en entrée en entier, on devra convertir le message (payload)

int pos = string.toInt();

Pour déterminer si on doit déplacer le Pan ou le Tilt, on va utiliser la fonction de comparaison de chaînes strcmp. Il renvoi le nombre de caractères différents entre les 2 chaines. S’il n’y a aucune différence (retour = 0), c’est que les deux chaines sont identiques. Ce qui va par exemple nous donner pour le Topic Pan :

if ( strcmp(topic, topic_pan) == 0 ) {
  Serial.print("Move Pan to ");
  Serial.println(pos);
  pan.write(pos);  
}

Code Arduino complet

Voici le code Arduino complet. Créer un nouveau sketch et modifier les paramètres dans le code avant de téléverser :

  • SSID, Password
  • Adresse IP broker MQTT
  • Broche servo Pan et tilt
/*
 * Node-RED + MQTT + ESP8266 + PTZ servo kit (Pan-Tilt)
 * How to move pan-tilt system with Node-RED - MQTT and ESP8266
 * - Use Node-RED UI Dashboard to move Pan and Tilt servos
 * - Node-RED sequencer : scan Pan and tilt 
 * - Node-RED reset position to 90°/90°
 * 
 * Comment déplacer le système pan-tilt avec Node-RED - MQTT et ESP8266
 * - Utilisez le tableau de bord Node-RED UI pour déplacer les servos Pan et Tilt
 * - Séquenceur Node-RED: balayage panoramique (Pan) et inclinaison (Tilt)
 * - Reset des positions depuis Node-RED (90°/90°)
 * - Tutoriel complet
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 * 
 * Licence : MIT
 * Copyright (C) 2017 : www.projetsdiy.fr and www.diyprojects.io
 */
#include 
#include 
#include  
Servo pan; 
Servo tilt;

// Paramètre WiFi, MQTT - WiFi, MQTT parameters
const char* ssid = "yourSSID";                // WiFi SSID
const char* password = "yourPASSWORD";        // WiFi Password
const char* mqtt_server = "xxx.xxx.xxx.xxx";  // IP Broker MQTT
const char* topic_pan = "servo/pan";          // Topic MQTT pour servo Pan - Topic MQTT for Pan servor
const char* topic_tilt = "servo/tilt";        // Topic MQTT pour servo Tilt - Topic MQTT for Tilt servor
 
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup() {

  Serial.begin(115200);
  pan.attach(D5);
  tilt.attach(D6);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
 String string;
 // Affiche le topic entrant - display incoming Topic
 Serial.print("Message arrived [");
 Serial.print(topic);
 Serial.print("] ");
 // décode le message - decode payload message
 for (int i = 0; i < length; i++) {
 string+=((char)payload[i]); 
 }
 // Affiche le message entrant - display incoming message
 Serial.print(string);
 Serial.print(" toInt ");
 // Conversion de la position en entier - convert position as an Integer
 int pos = string.toInt(); 
 Serial.println(pos);

 // Détermine quel servo doit être bougé - Determines which servo should be moved
 if ( strcmp(topic, topic_pan) == 0 ) {
 Serial.print("Move Pan to ");
 Serial.println(pos);
 pan.write(pos); 
 }
 if ( strcmp(topic, topic_tilt) == 0 ) {
 Serial.print("Move Tilt to ");
 Serial.println(pos);
 tilt.write(pos); 
 }
 delay(15); 
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      client.subscribe(topic_pan); 
      client.subscribe(topic_tilt); 
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {   
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  delay(100);
}

Flow Node-RED

Pour ce flow Nod-RED, vous aurez besoin d’installer plusieurs modules. Ouvrez le gestionnaire de palette (tutoriel complet) et installez les modules suivants

Importez ce code Node-RED directement sur un flow

[{"id":"34545562.076d7a","type":"mqtt out","z":"2555341e.a5485c","name":"","topic":"servo/pan","qos":"1","retain":"false","broker":"ceed5755.206c58","x":680,"y":220,"wires":[]},{"id":"f9a85217.59893","type":"ui_slider","z":"2555341e.a5485c","name":"","label":"Pan","group":"9c59c1e2.8235f","order":4,"width":"6","height":"1","passthru":true,"topic":"","min":0,"max":"180","step":1,"x":510,"y":240,"wires":[["34545562.076d7a","d604bc1e.67691"]]},{"id":"d604bc1e.67691","type":"ui_gauge","z":"2555341e.a5485c","name":"","group":"9c59c1e2.8235f","order":5,"width":"6","height":"6","gtype":"donut","title":"Pan servo position ","label":"position ","format":"{{value}}","min":0,"max":"180","colors":["#00b500","#e6e600","#ca3838"],"x":680,"y":280,"wires":[]},{"id":"3a393dcd.afa272","type":"comment","z":"2555341e.a5485c","name":"ESP8266 MQTT Pan Tilt PTZ Control Servo","info":"","x":210,"y":680,"wires":[]},{"id":"4316c9.3c84a938","type":"mqtt out","z":"2555341e.a5485c","name":"","topic":"servo/tilt","qos":"1","retain":"false","broker":"ceed5755.206c58","x":680,"y":440,"wires":[]},{"id":"9087f6a0.b7e938","type":"ui_slider","z":"2555341e.a5485c","name":"","label":"Tilt","group":"9c59c1e2.8235f","order":4,"width":"6","height":"1","passthru":true,"topic":"","min":0,"max":"180","step":1,"x":410,"y":480,"wires":[["4316c9.3c84a938","584bfb6e.86a1d4"]]},{"id":"584bfb6e.86a1d4","type":"ui_gauge","z":"2555341e.a5485c","name":"","group":"9c59c1e2.8235f","order":5,"width":"6","height":"6","gtype":"donut","title":"Tilt servo position ","label":"position ","format":"{{value}}","min":0,"max":"180","colors":["#00b500","#e6e600","#ca3838"],"x":680,"y":500,"wires":[]},{"id":"6a8f2c2b.64bdb4","type":"ui_button","z":"2555341e.a5485c","name":"","group":"9c59c1e2.8235f","order":0,"width":0,"height":0,"label":"Reset","color":"","icon":"fa-undo","payload":"","payloadType":"str","topic":"","x":270,"y":620,"wires":[["743cd12b.d31b5"]]},{"id":"798e642.411779c","type":"mqtt out","z":"2555341e.a5485c","name":"","topic":"servo/tilt","qos":"1","retain":"false","broker":"ceed5755.206c58","x":640,"y":600,"wires":[]},{"id":"743cd12b.d31b5","type":"function","z":"2555341e.a5485c","name":"Pan (90°) - Tilt(90°)","func":"msg.payload = 90;\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":620,"wires":[["d604bc1e.67691","4336088.add08f8","798e642.411779c","d2553ca9.80fdd","584bfb6e.86a1d4"]]},{"id":"d2553ca9.80fdd","type":"mqtt out","z":"2555341e.a5485c","name":"","topic":"servo/pan","qos":"1","retain":"false","broker":"ceed5755.206c58","x":640,"y":660,"wires":[]},{"id":"3ba0147.632dcec","type":"link in","z":"2555341e.a5485c","name":"","links":["4336088.add08f8","56f0b2a7.b1c81c"],"x":415,"y":240,"wires":[["f9a85217.59893"]]},{"id":"4336088.add08f8","type":"link out","z":"2555341e.a5485c","name":"","links":["3ba0147.632dcec","8117306f.30c94"],"x":595,"y":720,"wires":[]},{"id":"8117306f.30c94","type":"link in","z":"2555341e.a5485c","name":"","links":["4336088.add08f8","56f0b2a7.b1c81c"],"x":315,"y":480,"wires":[["9087f6a0.b7e938"]]},{"id":"89f37cd6.f1a11","type":"comment","z":"2555341e.a5485c","name":"www.projetsdiy.fr | www.diyprojects.io","info":"# Version française \nwww.projetsdiy.fr\n\n# English version\nwww.diyprojects.io","x":190,"y":720,"wires":[]},{"id":"e5657f97.89ffd","type":"repeat","z":"2555341e.a5485c","name":"","repetitions":"18","elseOutput":false,"outputs":1,"x":460,"y":80,"wires":[["68869228.30d7fc"]]},{"id":"d4ee4d5e.0ffcd","type":"ui_button","z":"2555341e.a5485c","name":"","group":"9c59c1e2.8235f","order":0,"width":"6","height":"1","label":"Scan Pan","color":"","icon":"","payload":"-10","payloadType":"num","topic":"","x":180,"y":160,"wires":[["68869228.30d7fc"]]},{"id":"68869228.30d7fc","type":"function","z":"2555341e.a5485c","name":"+10°","func":"var pos = msg.payload;\npos += 10;\nmsg.payload = pos;\nreturn msg;","outputs":1,"noerr":0,"x":370,"y":160,"wires":[["18dca8a0.64c977","f9a85217.59893"]]},{"id":"18dca8a0.64c977","type":"delay","z":"2555341e.a5485c","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":160,"wires":[["e5657f97.89ffd"]]},{"id":"549400d6.d1279","type":"repeat","z":"2555341e.a5485c","name":"","repetitions":"18","elseOutput":false,"outputs":1,"x":360,"y":320,"wires":[["593953a.80dfbac"]]},{"id":"fe1eebc1.8d83c8","type":"ui_button","z":"2555341e.a5485c","name":"","group":"9c59c1e2.8235f","order":0,"width":"6","height":"1","label":"Scan Tilt","color":"","icon":"","payload":"-10","payloadType":"num","topic":"","x":100,"y":400,"wires":[["593953a.80dfbac"]]},{"id":"593953a.80dfbac","type":"function","z":"2555341e.a5485c","name":"+10°","func":"var pos = msg.payload;\npos += 10;\nmsg.payload = pos;\nreturn msg;","outputs":1,"noerr":0,"x":270,"y":400,"wires":[["4cfcf34c.3c7d3c","9087f6a0.b7e938"]]},{"id":"4cfcf34c.3c7d3c","type":"delay","z":"2555341e.a5485c","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":420,"y":400,"wires":[["549400d6.d1279"]]},{"id":"ceed5755.206c58","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""},{"id":"9c59c1e2.8235f","type":"ui_group","z":"","name":"PTZ Servo Control","tab":"84e11c13.a96e9","order":2,"disp":true,"width":"12"},{"id":"84e11c13.a96e9","type":"ui_tab","z":"","name":"ESP8266","icon":"dashboard","order":2}]

Que fait ce flow ?

On dispose de 2 sliders pour commander la position du servo Pan et du servo Tilt. Un bouton Reset permet de repositionner les 2 servos à 90°. Enfin une boucle repeat permet de faire un scan avec un pas de 10° dans chaque direction (Pan et Tilt).

Réception des messages MQTT

Vous pouvez écouter les messages qui transitent sur le réseau MQTT à l’aide d’un client. Vous pouvez par exemple utiliser MQTT.fx (disponible pour Linux,  macOS et Windows, la page de téléchargement). Lancez MQTT.fx et connectez vous au Broker. Souscrivez (subscribe) au topic servo/#. Le # permet de récupérer tous les messages de l’arborescence. Ici pan et tilt.

Test en fonctionnement

Après avoir téléversez le programme dans l’ESP8266 et déployé le flow Node-RED, ouvrez le dashboard depuis n’importe quel navigateur internet. Vous pouvez également piloter le système articulé depuis le navigateur internet d’une tablette ou d’un smartphone. Ouvrez la page à l’adresse

http://IP_NODERED:1880/ui

Voici une petite vidéo de démonstration pour terminer l’article.

Avez-vous aimé cet article ?