Outils pour utilisateurs

Outils du site


mesh_et_mqtt

Réseau MESH avec des ESP et MQtt

L'objectif était de mettre en œuvre les informations contenues dans un article paru dans HACKABLE Magazine 27 dans le contexte de l'internet des objets.

La structure est la suivante :

Choix du matériel.

Pour ce faire j'ai opté pour l'utilisation de ce type de carte : ESP8266 ESP-01 Module de relais WiFi 2 canaux Module de relais 2 canaux pour contrôleur d'application de téléphone intelligent IOT disponible en chine .

Le programmateur utilisé est celui-ci :

CH340 USB à ESP8266 série ESP-01 ESP-01S ESP01 ESP01S sans fil Wifi développement Module de carte pour Arduino programmeur adaptateur

Une carte Raspberry-PI ancien modèle avec une carte WI-FI sur Port USB – Système Raspbian BUSTER.

La carte ESP + Relais

Présentation

La carte est vendue comme pilotable en WIFI depuis un téléphone via une application, mais ce n'est pas ceci qui nous intéresse ici. Cependant ajoutée, aux limitations d'E/S de l'ESP 01 présent la structure de la carte est moins commune. Nota: A ce jour je n'ai pas trouvé le schéma de cette carte, donc la rétro-ingénierie sera restreinte.

Pour l'utilisation de cette carte, il faut retenir ceci la programmation de l'ESP est classiquement possible avec l'environnement Arduino IDE. La commande des relais se fait par envoi d'une séquence de caractères via le port série.

Programmation des relais

Deux relais sont disponibles, les instructions de commandes sont donc

  1. Relais 1
  2. * Fermeture : A0 01 01 A2
  3. * Ouverture : A0 01 00 A1
  4. Relais 2
  5. * Fermeture : A0 02 01 A3
  6. * Ouverture : A0 02 00 A2

Ce qui au niveau du code engendre ceci dans le setup()

void setup() {
  delay(5000);
  //INIT de la liaison série pour le controle des relais
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);

et pour la commande des relais là ou vous souhaitez dans le code, ici la fermeture du relais 1 :

    Serial.write(0xA0);
    Serial.write(0x01);
    Serial.write(0x01);
    Serial.write(0xA2);

Le MESH

Le principe du mesh

D'après wikipédia : Réseau MESH

Le réseau maillé1 (ou maillage en réseau2) est une topologie de réseau (filaire et sans fil) où tous les hôtes sont connectés pair à pair sans hiérarchie centrale, formant ainsi une structure en forme de filet.

Les librairies pour ESP8266

Pour le MESH : PainlessMesh

La gestion de la communication MQTT : Lien externe|PubSubClient

Le gestionnaire de taches TaskScheduler

Le MQtt

Rappels sur le protocole et les outils

APPLICATION

Configuration du RaspberryPI

Sur la PI installation de

Notes :

Par défaut je n'ai rien modifié dans la configuration de mosquitto.

Ajout du dashboard dans Node-Red.

Exemple de flow pour Node-Red

flows.zip

Le fichier json pour Node-Red.

Un exemple de bouton poussoir Node-red avec changement de couleur et init à la mise sous-tension.

Illustration de boutons dynamique.

Reste à gérer les interaction entre les boutons, dans le cas d'une carte disposant de trois boutons R1/R2/R1 et 2 et la création d'un subflow paramétrable. Aide bienvenue – MERCI

[{"id":"5e5c2e88.71aea","type":"ui_button","z":"d68fea03.31b0c8","name":"Bouton Poussoir","group":"65e2fa0d.4fb70c","order":0,"width":0,"height":0,"passthru":false,"label":"{{msg.state}}","tooltip":"","color":"","bgcolor":"{{msg.background}}","icon":"","payload":"true","payloadType":"bool","topic":"","x":450,"y":280,"wires":[["d450f8d3.7b7b1"]]},{"id":"7e81c5e9.f34c3c","type":"inject","z":"d68fea03.31b0c8","name":"","topic":"","payload":"reset","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":400,"y":220,"wires":[["d450f8d3.7b7b1"]]},{"id":"d450f8d3.7b7b1","type":"function","z":"d68fea03.31b0c8","name":"gestion BP","func":"\nvar state=context.get(\"state\") || \"OFF\";\n\nvar color = '';\nif (msg.payload===\"reset\") {\n    state=\"OFF\"\n    color = 'red';\n    txt = \"OFF\"\n    msg.state=\"ON\";\n}\nelse{\n\nif ((msg.payload===true) && (state==\"OFF\")) {\n    state=!state;\n    context.set(\"state\",\"ON\") ;\n    msg.payload=\"RON#1\";\n    color = \"green\";\n    txt = \"ON\"\n    msg.state=\"OFF\";\n\n} \nelse {\n    msg.payload=\"ROFF#1\";\n    context.set(\"state\",\"OFF\") ;\n    color = 'red';\n    txt = \"OFF\"\n    msg.state=\"ON\";\n}\n}\n\n node.status({\n    \tfill : color,\n    \tshape : 'dot',\n    \ttext : txt\n });\nnode.send(msg);\nmsg.background=color;\n\n\n\nreturn msg;\n","outputs":1,"noerr":0,"x":690,"y":280,"wires":[["5e5c2e88.71aea"]]},{"id":"65e2fa0d.4fb70c","type":"ui_group","z":"","name":"Carte 1 V2","tab":"3939b51c.a31712","disp":true,"width":"6","collapse":true},{"id":"3939b51c.a31712","type":"ui_tab","z":"","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

La partie iptable n'est pas mise en oeuvre du fait d'un changement dans Buster iptables vers nftables

Autres ressources

Installation de node-red sur Raspbian ici

La passerelle MQTT <--> MESH

Réalisée avec un ESP8266. Doit se connecter au MESH et au Broker pour assurer la passerelle.

//*****************************************************************************************************
// FICHIER DE FONCTIONNEMENT POUR ESP8266 COMME PONT
//
//    MQTT BROKER <---> MESH
//           
//    François-Marie BILLARD
//
//    Test à faire 
//        envoyer un message depuis BROKER (via NODE- RED) vers le MESH OK le 2 avril 2020
//
//        envoyer un message depuis MESH vers BROKER : OK le 2 avril 2020
//
//*****************************************************************************************************

#include <Arduino.h>
#include <painlessMesh.h>
#include <PubSubClient.h>
#include <WiFiClient.h>

#define   MESH_PREFIX     "meshexoposition"
#define   MESH_PASSWORD   "passwordmesh"
#define   MESH_PORT       5555

#define   STATION_SSID     "EXPOSITION"
#define   STATION_PASSWORD "1234567890"

#define HOSTNAME "raspberrypi"

// Prototypes
void receivedCallback( const uint32_t &from, const String &msg );
void mqttCallback(char* topic, byte* payload, unsigned int length);

IPAddress getlocalIP();

IPAddress myIP(0,0,0,0);
IPAddress mqttBroker(192, 168, 50, 15);
boolean ErreurBroker = true;


painlessMesh  mesh;
WiFiClient wifiClient;
PubSubClient mqttClient(mqttBroker, 1883, mqttCallback, wifiClient);

void setup() {
  Serial.begin(115200);

  mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION );  // set before init() so that you can see startup messages

  // Channel set to 6. Make sure to use the same channel for your mesh and for you other
  // network (STATION_SSID)
  mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6 );
  mesh.onReceive(&receivedCallback);

  mesh.stationManual(STATION_SSID, STATION_PASSWORD);
  mesh.setHostname(HOSTNAME);

  // Bridge node, should (in most cases) be a root node. See [the wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation) for some background
  mesh.setRoot(true);
  // This node and all other nodes should ideally know the mesh contains a root, so call this on all nodes
  mesh.setContainsRoot(true);
}

void loop() {
  mesh.update();
  mqttClient.loop();

  
  
  if (myIP != getlocalIP()) {               //pas d'adresse IP correcte
    myIP = getlocalIP();                    //mise a jour de l'IP locale
    if (!mqttClient.connected() ) {         //pas de connexion au Broker 
       if (mqttClient.connect("wifiClient")) {  //tentative de connexion 
          // si connexion établie
          mqttClient.publish("painlessMesh/etatBridge","1");      // envoi une information de connexion via TOPIC
          mqttClient.subscribe("painlessMesh/to/#");              // abonnement au TOPIC
          ErreurBroker= false;
          }
          else {
            ErreurBroker= true;
          }
      }
    }

  //ajout d'une gestion de la deconnexion.

}


//Reception depuis le MESH renvoi vers le MQTT
void receivedCallback( const uint32_t &from, const String &msg ) {
  Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
  String topic = "painlessMesh/from/" + String(from);
  mqttClient.publish(topic.c_str(), msg.c_str());
}


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

//  Cette fonction va renvoyer sur le MESH les messages en provenance du BRIDGE

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

void mqttCallback(char* topic, uint8_t* payload, unsigned int length) {
  char* cleanPayload = (char*)malloc(length+1);
  payload[length] = '\0';
  memcpy(cleanPayload, payload, length+1);
  String msg = String(cleanPayload);
  free(cleanPayload);

  String targetStr = String(topic).substring(16);

  if(targetStr == "gateway")
  {
    if(msg == "getNodes")
    { // renvoi les informations sur les noeuds connectés si 
      // message est envoyé au TOPIC /painlessMesh/to/gateway avec message getNodes
      auto nodes = mesh.getNodeList(true);
      String str;
      for (auto &&id : nodes)
        str += String(id) + String(" ");
      mqttClient.publish("painlessMesh/from/gateway", str.c_str());
    }
  }
  else if(targetStr == "broadcast") 
  {
    mesh.sendBroadcast(msg);
  }
  else
  {
    uint32_t target = strtoul(targetStr.c_str(), NULL, 10);
    if(mesh.isConnected(target))
    {
      mesh.sendSingle(target, msg);
    }
    else
    {
      mqttClient.publish("painlessMesh/etatBridge", "0");
    }
  }
}

IPAddress getlocalIP() {
  return IPAddress(mesh.getStationIP());
}

Noeud du MESH

Réalisée avec un ESP8266 avec deux relais dans mon cas. Doit se connecter au MESH de manière automatique.

L'objet de la librairie PainlessMesh pour l'initialisation du MESH dans le setup(). Puis de la mise à jour du MESH dans le loop(), via mesh.update.

La fonction receivedCallback est appelé en cas de reception d'un message en provenance du MESH.

L'utilisation de l'ordonnanceur de tâches pour la gestion des opérations d'envoi des messages et de mise à jour des relais dans le setup() et son appel dans le loop() userScheduler.execute();

//************************************************************
// Exemple d'usage de la librairie PainlessMESH
// François-Marie BILLARD
//
//  Le 25 MArs 2020
//
// 1. Mesh sur le cana 6
// 2. La commande des relais doit être du type 
//    RON + chiffre : Allume en fonction des données ci-dessous sans extinction des autres
//    ROFF + chiffre : Eteint en fonction des données ci-dessous sans extinction des autres
//    RONA : Allume tous les relais chiffrés
//    ROFFA : Eteint tous les relais chiffrés
//
//  suivit d'un chiffre qui donne les relais actifs sous forme binaire
//      0 Tout éteint
//      1 Relais 1 allumé
//      2 Relais 2 allumé
//      3 Relais 1 et 2 allumés
//
//  FONCTIONNELLE le 6 AVRIL 2020
//
//************************************************************
#include "painlessMesh.h"

#define   MESH_PREFIX     "meshexoposition"
#define   MESH_PASSWORD   "passwordmesh"
#define   MESH_PORT       5555

#define   MAX_RELAIS   2
#define MAX_RELAIS_ON 3

int etatRelais; // code l'état des 16 relais chaque bit un relais 0 repos, 1 actif



String message; 

Scheduler userScheduler; // Controluer 
painlessMesh  mesh;

void maj_Relais() ; // Prototype pour le traitement des relais

Task taskRelais( TASK_SECOND * 1 , TASK_FOREVER, &maj_Relais );

void maj_Relais() {
   //traitement de l'état des relais du tableau etatRelais en fonction de la valeur ON
  //cette fonction dépend du type de carte
  for (int cpt=1;cpt<=MAX_RELAIS;cpt++) {
   
    if ((etatRelais & cpt) == cpt) { //Mise à un du relais
    switch (cpt) {
       case 1:          
          //Allumage relais 1
          Serial.write(0xA0);
          Serial.write(0x01);
          Serial.write(0x01);
          Serial.write(0xA2);
          delay(10);
        break;
      case 2:
          //Allumage relais 2
          Serial.write(0xA0);
          Serial.write(0x02);
          Serial.write(0x01);
          Serial.write(0xA3);
          delay(10);
      break;
    }
    
    } else { //Mise à zéro du relais
    switch (cpt) {
       case 1:
          //Extinction relais 1
          Serial.write(0xA0);
          Serial.write(0x01);
          Serial.write(0x00);
          Serial.write(0xA1);
          delay(10);
        break;
      case 2:
          //Extinction relais 2
          Serial.write(0xA0);
          Serial.write(0x02);
          Serial.write(0x00);
          Serial.write(0xA2);
          delay(10);
      break;
    }
    }
}
}





// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain


Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

void sendMessage() {
  
  String msg = "Depuis le noeud  ";
  
  msg += mesh.getNodeId();
  msg += message;
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
  message="";
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg ) {
 //fonctionnel le 6 Avril 2020
 boolean fin =false;
 int cpt =0;
 int valeur = -1;

 while (!fin){
  if (isDigit(msg[cpt])) {
 
    fin=true;
    String temp=msg.substring(cpt);
    valeur= temp.toInt();
    msg=msg.substring(0,cpt);
   }
  else {
    if (cpt>msg.length()) {
    fin =true;
    cpt=-1;
  }
  else {
        cpt++;

  }
 }
}
 message = "recu : " + msg;
 if (msg=="RONA") {
    //MAJ_Relais(MAX_RELAIS);
    etatRelais=MAX_RELAIS_ON;
    message= message + "Traite RONA";
  } else 
    if (msg=="ROFFA") {
        etatRelais=0;
        message= message + "Traite ROFFA";
    } else 
      if (msg=="RON") {
        etatRelais=(etatRelais | valeur );
        message= message + "Traite RONx";
      } else 
        if (msg=="ROFF") {
                    etatRelais=(etatRelais & (~valeur) );
                    message= message + "Traite ROFFx";
                    }
                    else if (msg=="TYPE") {
                    //sendMessage
                    message="Relais_2";
                    }
}

void newConnectionCallback(uint32_t nodeId) {
    //Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
}

void changedConnectionCallback() {
  //Serial.printf("Changed connections\n");
}

void nodeTimeAdjustedCallback(int32_t offset) {
    ////Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  //Serial.begin(115200);
   //INIT de la liaison série pour le controle des relais
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
  etatRelais = 0; //relais tous au repos


//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages

  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT , WIFI_AP_STA, 6);
  //mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);


  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();

  userScheduler.addTask( taskRelais );
  taskRelais.enable();
}

void loop() {
   // tâche utilisateur et tâche mesh
  userScheduler.execute();
  mesh.update();
}
mesh_et_mqtt.txt · Dernière modification: 2020/06/07 13:25 par serge