howto:arduino-esp:esp-now

Ceci est une ancienne révision du document !


ESP-NOW

ESP-NOW est un protocole d'échange de données entre ESP-32 (ou ESP8266) que l'on peut programmer avec l'IDE Arduino. C'est un protocole développé par Espressif, le fabricant des puces ESP, et qui permet d'échanger de courts paquets de données directement et simplement. Il est possible de réaliser des échanges entre plusieurs ESP dans les deux sens (émission et réception) sans intermédiaire central (sans routeur).

Ce qui va suivre s'inspire très fort de l'excellent article de Random Nerd Tutorials: https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/.

Tel qu'il est décrit sur le site d'Espressif, le protocole ESP-NOW permet d'échanger des paquets sans Wifi, s'approchant en cela du système utilisé par les objets connectés en 2.4Ghz (souris ou claviers sans fil, en particulier). L'appairage entre modules est nécessaire (nous verrons plus loin qu'il faut utiliser l'adresse MAC des ESP), mais une fois que cet appairage est réalisé la connexion s'effectue très rapidement, sans “handshake”. Dis plus simplement, lorsque l'appairage est effectué, on peut éteindre ou redémarrer un module, la reconnexion sera automatique et immédiate.

Il est possible de réaliser un schéma ou un module central envoie des informations à de nombreux autres modules (one to many), ou au contraire de nombreux modules envoient à un module central (many to one), mais aussi un réseau maillé ou chaque module peut envoyer à tous les autres.

Il est possible de chiffrer les communications, et de mélanger des communications chiffrées ou non dans un même réseau. Le nombre de modules doit rester en dessous de 10 lorsque l'on utilise le chiffrement et 20 lorsque l'on échange en clair.

Il n'est possible d'échanger que 250 octets au maximum à chaque envoi. Une fonction de rappel peut être envoyée pour confirmer la bonne réception.

La première étape va consister à noter les adresses MAC de chaque appareil que nous voulons faire communiquer. Pour mémoire, l'adresse MAC (Media Access Control) est un identifiant unique matériel qui identifie chaque appareil sur un réseau. Chaque ESP32 possède, en sortie d'usine, une adresse MAC différente, composée de 6 octets. Il est possible de changer de manière logicielle l'adresse MAC de l'ESP, mais cela ne survit pas à un reboot, il faut donc l'inclure dans le code exécuté à chaque fois (tuto ici).

Uploader le code suivant dans chaque ESP, noter soigneusement le résultat qui va s'afficher dans la console.

#include "WiFi.h"
 
void setup(){
  Serial.begin(115200);
  WiFi.mode(WIFI_MODE_STA);
  Serial.println(WiFi.macAddress());
}
 
void loop(){

}

Pour commencer, nous allons simplement envoyer des informations d'un ESP vers un autre. Par commodité, nous les appellerons “Émetteur” et “Récepteur”.

Côté émetteur:

  • Initialiser ESP-NOW.
  • Enregistrer une fonction de rappel, qui sera exécutée quant un message est envoyé. Cela nous permettra de vérifier la bonne livraison du message.
  • On ajoute l'adresse MAC du récepteur, pour l'appairage.
  • On envoie le message.

Côté récepteur:

  • Initialiser ESP-NOW.
  • Enregistrer une fonction de rappel, qui sera exécutée quant un message est reçu.
  • Dans cette fonction de rappel, on sauve le contenu du message dans une variable pour en faire quelque chose.

esp_now_init() Initialise ESP-NOW. Il faut initialiser le wifi avant d'initialiser ESP-NOW.
esp_now_add_peer() On appelle cette fonction pour appairer un ESP, on passe son adresse MAC en argument.
esp_now_send() Envoie des données avec ESP-NOW.
esp_now_register_send_cb() Enregistre une fonction de rappel qui sera déclenchée lorsque l'on envoie des données.
esp_now_register_rcv_cb() Enregistre une fonction de rappel qui sera déclenchée lorsque l'on reçoit des données.

Ci-dessous, le code tel que proposé par Rui Santos, de Random Nerd Tutorials:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <esp_now.h>
#include <WiFi.h>

// REPLACE WITH YOUR RECEIVER MAC Address
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
  char a[32];
  int b;
  float c;
  bool d;
} struct_message;

// Create a struct_message called myData
struct_message myData;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
 
void setup() {
  // Init Serial Monitor
  Serial.begin(115200);
 
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);
  
  // Register peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
}
 
void loop() {
  // Set values to send
  strcpy(myData.a, "THIS IS A CHAR");
  myData.b = random(1,20);
  myData.c = 1.2;
  myData.d = false;
  
  // Send message via ESP-NOW
  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
   
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending the data");
  }
  delay(2000);
}

Quelques explications:

D'abord, inclure les librairies:

#include <esp_now.h>
#include <WiFi.h>

Ensuite, nous stockons dans une variable l'adresse MAC du récepteur:

uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};

Pensez évidemment à remplacer l'adresse MAC par celle spécifique de votre ESP.

Ensuite, il faut créer une structure qui contiendra les données envoyées. Elle s'appelera *struct_message* et contiendra 4 types de variables différents. Il faudra modifier cette partie pour l'adapter aux spécificités de votre pojet.

typedef struct struct_message {
  char a[32];
  int b;
  float c;
  bool d;
} struct_message;

On créé ensuite une variable de type *struct_message* et que l'on appelera *myData*. Cette variable servira à stocker… des variables, celles à envoyer.

struct_message myData;

Enfin, nous allons définir notre fonction de rappel, qui sera exécutée automatiquement lorsqu'un message aura été envoyé. Ici, on affiche seulement la réussite ou l'échec de la livraison.

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

Viens maintenant le moment de créer notre fonction *setup()*, où, après avoir initialisé la communication série avec *Serial.begin(115200)*, nous allons allumer le wifi de l'ESP, puis initialiser ESP-NOW:

WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

On enregistre ensuite la fonction de rappel, créée précédemment:

esp_now_register_send_cb(OnDataSent);

Nous devons ensuite appairer notre émetteur avec son récepteur, en définissant son adresse MAC:

//Register peer
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;

//Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
  Serial.println("Failed to add peer");
  return;
}

Enfin, la boucle *loop()* va envoyer un message toutes les deux secondes, contenant des valeurs arbitraires, mais que vous pourrez relier par la suite à des capteurs, par exemple.

D'abord, donc, initialisons les variables:

strcpy(myData.a, "THIS IS A CHAR");
myData.b = random(1,20);
myData.c = 1.2;
myData.d = false;

Pour mémoire, myData est une structure. Nous assignons donc nos valeurs, aux membres qui sont de différents types. Pour en savoir un peu plus sur l'utilisation des structures, je vous renvoie à cette page de Locoduino. Cette manière de travailler permet d'envoyer en un seul bloc les différentes données, de l amanière suivante:

esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));

On peut vérifier que le message a bien été envoyé:

if (result == ESP_OK) {
  Serial.println("Sent with success");
}
else {
  Serial.println("Error sending the data");
}

The loop() is executed every 2000 milliseconds (2 seconds).

delay(2000);

Voici le programme à uploader dans l'ESP qui servira de récepteur.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <esp_now.h>
#include <WiFi.h>

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
    char a[32];
    int b;
    float c;
    bool d;
} struct_message;

// Create a struct_message called myData
struct_message myData;

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Char: ");
  Serial.println(myData.a);
  Serial.print("Int: ");
  Serial.println(myData.b);
  Serial.print("Float: ");
  Serial.println(myData.c);
  Serial.print("Bool: ");
  Serial.println(myData.d);
  Serial.println();
}
 
void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(OnDataRecv);
}
 
void loop() {

}

Le code se passe presque d'explications, les étapes sont à peu de chose près les mêmes que celles de l'émetteur, à la différence que la fonction de rappel va être exécutée à la réception d'un message. Par conséquent, l'orsqu'il arrive, il faut copier le contenu de la structure qui arrive dans la variable que nous avons déclarée pour pouvoir en exploiter le contenu:

memcpy(&myData, incomingData, sizeof(myData));

Lorsque vous regarderez les console série des deux esp, vous constaterez que les messages sont bien expédiés, bien reçus et leur contenu correctement interpreté côté récepteur. Les différentes sources trouvées sur le net parlent d'une portée en extérieur supérieur à 200 mètres, avec les deux antennes pointant l'une vers l'autre. À tester, les essais réalisés autour de notre projet de compteur ont plutôt été concluants jusqu'à 100 mètres environ.

Nous vous renvoyons aux exemples disponibles dans la bibliothèque Arduino, et à nouveaux aux articles de Random Nerd Tutorials pour étudier les réseaux avec plusieurs nœuds, et les échanges bi-directionnels.

  • howto/arduino-esp/esp-now.1633785843.txt.gz
  • Dernière modification : 2021/10/09 15:24
  • de guillaume