LOCODUINO

Éclairer son réseau

Éclairer le réseau (2)

Connexion au WiFi et Multi Cast DNS

.
Par : Jean-Luc

DIFFICULTÉ :

Nous continuons cette série par l’infrastructure logicielle avec la connexion au WiFi et l’usage de mDNS.

Le logiciel Arduino de l’ESP32, mais c’est aussi vrai des autres cartes compatibles Arduino qui possèdent une connexion WiFi, fournit de multiples fonctions pour communiquer via le réseau. Nous allons examiner ici et dans les articles suivants :

  • La connexion WiFi ;
  • Le multi-cast DNS ;
  • l’OTA ;
  • MQTT.

Mais tout d’abord occupons nous d’installer le paquet logiciel permettant de programmer un ESP32 depuis l’IDE Arduino. Si vous avez déjà installé de nouveaux paquets de gestion de cartes, la procédure est familière. Il s’agit d’abord de se rendre dans les préférence de l’IDE Arduino et cliquer sur le bouton représentant une fenêtre ombrée (voir figure 1). Il faut ajouter l’URL suivante dans la fenêtre qui s’est ouverte en prenant soin de mettre une URL par ligne.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Figure 1 : Ajout d'un gestionnaire de cartes dans les préférences.
Figure 1 : Ajout d’un gestionnaire de cartes dans les préférences.

Ce n’est que la première étape. Pour installer tout le nécessaire pour programmer un ESP32, il faut se rendre ensuite dans le Outils puis Type de carte : ... et sélectionner dans le sous-menu l’item Gestionnaire de cartes. Une fenêtre s’ouvre et dans la case de recherche en haut à droite, on tape « ESP32 ». Puis on clique sur le bouton Installer.

Figure 2 : Installation du paquet pour la prise en charge des ESP32.
Figure 2 : Installation du paquet pour la prise en charge des ESP32.
Dans cette capture d’écran, le nécessaire est déjà installé et le bouton Supprimer apparaît au lieu de Installer.

Enfin, il faut sélectionner la carte ESP32 que l’on souhaite programmer. Ici, il s’agit de la « MH ET LIVE ESP32MiniKit », voir figure 3 ci-dessous.

Figure 3 : sélection de la carte adéquate.
Figure 3 : sélection de la carte adéquate.

La connexion WiFi

Il y a plusieurs façons d’initialiser le WiFi mais nous n’allons ici nous préoccuper que du mode station ; c’est à dire que l’ESP32 va rejoindre le réseau WiFi de votre domicile comme un ordinateur, un téléphone ou une tablette. Le type de protection, WPA, WPA2, ... est géré automatiquement. Le nom du réseau (ssid, ici polaris) et son mot de passe sont placés en dur dans le sketch. Il va sans dire que vous devez substituer à polaris le nom de votre réseau et à ************ votre mot de passe.

Le sketch minimum pour que l’ESP32 se connecte au WiFi est le suivant :

#include <WiFi.h>

/* Nom du réseau WiFi */
const char ssid[] = "polaris";
/* Mot de passe du réseau WiFi */
const char pass[] = "************";

void setup() {
  /* Pour le debug */
  Serial.begin(115200);
  /* Connexion au Wifi */
  WiFi.begin(ssid, pass);
  /* Boucle d'attente que la connexion soit ok */
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println();
  Serial.print("Connecté à ");
  Serial.println(ssid);
}

void loop() {
}

Ceci a l’avantage de la simplicité mais présente plusieurs défauts [1] :

  1. En cas de perte de la connexion, rien ne garantit que l’ESP32 se reconnectera de lui même ;
  2. Même en améliorant le programme pour demander une reconnexion, rien ne garantit que les couches logicielle du WiFi soient dans un état cohérent et permettent cette reconnexion ;
  3. On bloque sur la connexion WiFi sans assurer une fonction minimum. Par exemple, pour notre application d’éclairage, le minimum serait que l’éclairage se mette en route sans pour autant qu’il soit pilotable.

Pour détecter la perte de connexion, on peut utiliser à intervalles réguliers WiFi.status() qui retournera WL_CONNECTED si la connexion est fonctionnelle, pour plus de détails voir la documentation.

Pour 1, on devrait normalement utiliser WiFi.reconnect() lorsque WiFi.status() ne retourne pas WL_CONNECTED. C’est d’ailleurs ce que l’on trouve sur de nombreux sites pour reconnecter l’ESP32. Mais l’expérience menée avec mon téléphone comme borne WiFi [2], en coupant et en remettant le partage de connexion montre que l’ESP32 ne se reconnecte pas et qu’il faut en réalité repasser par la fonction WiFi.begin(...) pour avoir un fonctionnement satisfaisant. Pour 2, il est nécessaire de compter le nombre de tentatives qui échouent et, quand il dépasse un seuil, redémarrer l’ESP32. On peut également compter le nombre de déconnexions qui ont lieu trop fréquemment. Par exemple si la durée entre une déconnexion et la suivante est inférieure à un intervalle de temps, on incrémente une variable. Si cette variable dépasse un seuil, l’ESP32 est redémarré. Enfin pour 3, on va découpler la connexion au réseau WiFi et l’application qui s’exécute sur la carte.

Mais si le but de 3 est d’assurer un service minimum d’éclairage non pilotable, il est en conflit avec 2 car à chaque redémarrage les rubans de LED blanches s’éteindront une fraction de seconde [3]. Nous allons donc laisser de côté le point 2.

Voici donc une classe, Connection qui découple l’établissement et le maintien de la connexion du reste du logiciel et qui rétablit la connexion quand elle est perdue. Elle est basée sur une machine à états comprenant 3 états : OFFLINE correspondant à une connexion absente, WIFI_STBY correspondant à l’attente de l’établissement de la connexion et WIFI_OK lorsque la connexion est établie. La machine à état est montrée à la figure 4.

Figure 4 : Machine à états de gestion de la connexion WiFi
Figure 4 : Machine à états de gestion de la connexion WiFi

La déclaration de la classe est la suivante :

class Connection {
public:
  typedef enum { OFFLINE, WIFI_STBY, WIFI_OK } State;

private:
  static State sState;
  static uint32_t sLastDate;
  static uint32_t sPeriod;
  static char *sSsid;
  static char *sPass;

  Connection() {}
  
  static void update();

public:
  static void setup(const char *inSsid, const char *inPass, const uint32_t inPeriod = 1000ul);
  static void loop();
  static bool isOnline();
};

On trouve la définition, sous forme d’enum, des 3 états précédemment évoqués puis 5 variables de classe permettant de :

  • Mémoriser l’état courant (sState) ;
  • Mémoriser la dernière date d’appel de update (sLastDate) ;
  • Spécifier la période d’appel de update (sPeriod) ;
  • Pointer vers le le nom et le mot de passe du réseau WiFi (sSsid et sPass) ;

Le constructeur, Connection() est déclaré privé de manière à empêcher de créer une instance de cette classe. En effet, elle n’est pas destinée à être instanciée et ne sert qu’à encapsuler les fonctions et les variables employées pour la connexion au WiFi.

update() implémente la machine d’états présentée figure 4. setup() est destiné à être appelé dans la fonction homonyme du sketch avec pour argument le nom du réseau, son mot de passe et, de manière optionnelle, la période d’appel de update() avec une valeur par défaut d’une seconde. loop() doit être appelé dans le loop() du sketch. Enfin, isOnline() sert à tester si la connexion est opérationnelle.

L’implémentation est donnée ci-dessous :

Connection::State Connection::sState = OFFLINE;
uint32_t Connection::sLastDate = 0ul;
uint32_t Connection::sPeriod = 1000ul;
char *Connection::sSsid = NULL;
char *Connection::sPass = NULL;

void Connection::update()
{
  switch (sState) {
    case OFFLINE:
      if (sSsid != NULL && sPass != NULL) {
        DP("Connexion à : ");
        WiFi.begin(sSsid, sPass);
        sState = WIFI_STBY;
      }
      break;
      
    case WIFI_STBY:
      if (WiFi.status() == WL_CONNECTED) {
        DP(' ');
        DPLN(sSsid);
        sState = WIFI_OK;
      } else {
        DP('.');
      }
      break;
      
    case WIFI_OK:
      if (WiFi.status() != WL_CONNECTED) {
        DPLN("Perte de connexion");
        sState = OFFLINE;
      }
      break;
  }
}

void Connection::setup(const char *inSsid, const char *inPass, const uint32_t inPeriod)
{
  sPeriod = inPeriod;
  sSsid = (char *)inSsid;
  sPass = (char *)inPass;
}

void Connection::loop()
{
  const uint32_t currentDate = millis();
  if ((currentDate - sLastDate) >= sPeriod) {
    update();
    sLastDate = currentDate;
  }
}

bool Connection::isOnline()
{ 
  return (sState == WIFI_OK);
}

On notera l’usage de macros (DP et DPLN) pour « Debug Print » et « Debug PrintLN » permettant d’afficher des messages afin de débugger. Ces macros sont définies comme ceci :

#define DEBUG

#ifdef DEBUG
#define DSB(v) Serial.begin(v)
#define DP(m) Serial.print(m)
#define DPLN(m) Serial.println(m)
#else
#define DSB(v)
#define DP(m)
#define DPLN(m)
#endif

Le fait de supprimer le #define DEBUG au début permettra d’enlever ces messages lorsque le programme sera terminé.

Dans cette première version ne s’occupant que d’établir et de maintenir la connexion WiFI, le sketch se réduit à ceci :

void setup() {
  /* Pour le debug */
  DSB(115200);
  /* Connexion */
  Connection::setup(ssid, pass);
}

void loop() {
  Connection::loop();
}

Le Multicast DNS

Le DNS (Domain Name Server ou serveur de nom de domaine) est un service qui, sur Internet, permet de convertir un nom de machine, par exemple www.locoduino.org en adresse IP. Ainsi quand vous tapez de manière fort avisée http://www.locoduino.org dans votre navigateur, ce dernier demande au système d’exploitation de votre ordinateur de trouver l’adresse IP de Locoduino. Le système d’exploitation possède l’adresse IP d’un DNS auquel il s’adresse pour obtenir la réponse. Généralement c’est votre box qui remplit ce rôle. Elle-même s’adresse au DNS de votre fournisseur d’accès et, de proche en proche, on aboutit au DNS du domaine .org qui connait Locoduino. La réponse suit le chemin inverse et atteint votre navigateur qui peut alors ouvrir une connexion TCP avec Locoduino. Dans le processus la chaine de DNS mémorise la réponse pour un temps afin d’éviter de s’adresser au DNS de .org systématiquement.

Chez vous, par contre, c’est différent car il n’existe pas de DNS pour faire la correspondance entre un nom de machine/téléphone/tablette/imprimante/ESP32 et son adresse IP. C’est là qu’intervient le service Multicast DNS ou mDNS. Ici chaque machine connaît son nom qui va forcément se terminer par .local. Dès qu’une requête de DNS concerne un nom se terminant par .local, votre machine, au lieu de s’adresser au DNS, va envoyer sur votre réseau local une requête à toutes les machines qui s’y trouvent. Celle qui a le nom spécifié répond « C’est moi et voici mon adresse IP ».

Il s’agit donc ici de donner un petit nom à l’ESP32, ce qui sera quand même plus sympathique qu’une adresse IP et ce d’autant plus que cette dernière peut changer entre deux mises sous tension de l’ESP32 étant donné que c’est votre box internet qui attribue cette adresse.

Mettre en œuvre mDNS sur ESP32 est très simple. Il suffit d’inclure l’entête de la bibliothèque MDNS : #include <ESPmDNS.h> puis, à l’endroit approprié du code, d’appeler MDNS.begin(...); avec comme argument le nom de machine sans le .local. C’est tout.

Mais il faut l’intégrer au processus de connexion afin de lancer le mDNS après que la connexion WiFi soit établie. Cela conduit à ajouter un état à notre automate, entre WIFI_STBY et WIFI_OK tel que montré à la figure 5.

Figure 5 : Machine à états de gestion de la connexion WiFi avec service mDNS.
Figure 5 : Machine à états de gestion de la connexion WiFi avec service mDNS.

Un état est donc ajouté à l’enum :

typedef enum { OFFLINE, WIFI_STBY, WIFI_OK, MDNS_OK } State;

La classe Connection reçoit une nouvelle variable membre pour disposer du nom mDNS de l’ESP :

static char *sName;

Connection::setup reçoit un argument supplémentaire pour le nom :

static void setup(const char *inSsid, const char *inPass, const char *inName, const uint32_t inPeriod = 1000ul);

Connection::update() est modifié pour prendre en charge ce nouvel état :

void Connection::update()
{
  switch (sState) {
    case OFFLINE:
      if (sSsid != NULL && sPass != NULL) {
        DP("Connexion à : ");
        WiFi.begin(sSsid, sPass);
        sState = WIFI_STBY;
      }
      break;
      
    case WIFI_STBY:
      if (WiFi.status() == WL_CONNECTED) {
        DP(' ');
        DPLN(sSsid);
        sState = WIFI_OK;
      } else {
        DP('.');
      }
      break;
      
    case WIFI_OK:
      if (WiFi.status() == WL_CONNECTED) {
        if (sName != NULL) {
          MDNS.begin(sName);
          DP("Demarrage de mDNS sous le nom ");
          DP(sName);
          DPLN(".local");
          sState = MDNS_OK;
        }
      } else {
        DPLN("Perte de connexion");
        sState = OFFLINE;
      }
      break;

    case MDNS_OK:
      if (WiFi.status() != WL_CONNECTED) {
        DPLN("Perte de connexion");
        sState = OFFLINE;
      }
      break;
  }
}

La ligne suivante est ajoutée à Connection::setup(...)

sName = (char *)inName;

Et enfin le setup() du sketch devient :

void setup() {
  /* Pour le debug */
  DSB(115200);
  /* Connexion */
  Connection::setup(ssid, pass, "Eclairage");
}

Si vous utilisez un Mac ou un Linux, un tour dans le terminal vous informe que l’ESP répond bien par son petit nom sur le réseau (figure 6). Avec Windows, l’Invite de commande remplit le même rôle pourvu que vous possédiez au moins Windows 10 ≥ TH2 (1511). Si vous utilisez une version de Windows < Windows 10 TH2 (1511), il vous faudra installer une service mDNS de tierce partie (il semblerait qu’installer iTunes pour Windows fasse le job) ou bien vous résigner à utiliser l’adresse IP attribué à l’ESP32.

Figure 6 : Ping de l'ESP sur le réseau local.
Figure 6 : Ping de l’ESP sur le réseau local.

Ci dessous vous pouvez télécharger ce sketch complet. N’oubliez pas d’y mettre votre ssid et votre mot de passe avant de le compiler !

Sketch d’essai de la connexion WiFi et du mDNS

[1Je parle ici en connaissance de cause car j’ai subi quelques déboires concernant la robustesse.

[2C’est un moyen simple de mener des tests sans déclencher des hurlements chez les membres de la famille.

[3Mais pas les rubans à base de WS2811 où chacun d’entre eux possède 3 PWM autonomes.

2 Messages

Réagissez à « Éclairer le réseau (2) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

Les derniers articles

Les articles les plus lus