Ce guide montre comment créer un serveur Web ESP32 et utiliser simultanément le protocole de communication ESP-NOW. Nous vous montrerons comment établir une communication bidirectionnelle entre le maître (serveur Web) et les esclaves, et comment ajouter automatiquement des cartes au réseau (auto-appairage).

Ce tutoriel est une amélioration de ce qui suit :
La nouvelle version comprend :
- Communication bidirectionnelle entre le serveur et les esclaves ;
- Pairage automatique des pairs : vous n’avez pas besoin de connaître les adresses MAC des cartes. Vous n’avez pas besoin d’ajouter des pairs manuellement. Il vous suffit d’exécuter les codes fournis et les cartes seront automatiquement ajoutées au réseau ESP-NOW.
Les améliorations ont été suggérées par un de nos lecteurs (Jean-Claude Servaye). Vous pouvez trouver les codes d’origine sur sa page GitHub.
Si vous débutez avec ESP-NOW, nous vous recommandons de vous familiariser d’abord avec les concepts et les fonctions d’ESP-NOW. Consultez les guides de démarrage suivants :
Utilisation simultanée d’ESP-NOW et du Wi-Fi (serveur Web)
Il y a quelques éléments dont vous devez tenir compte si vous souhaitez utiliser le Wi-Fi pour héberger un serveur Web et utiliser ESP-NOW simultanément pour recevoir les lectures des capteurs d’autres cartes :

- Les cartes émettrices ESP32/ESP8266 doivent utiliser le même canal Wi-Fi que la carte réceptrice (serveur).
- Le canal Wi-Fi de la carte récepteur est automatiquement attribué par votre routeur Wi-Fi.
- Le mode Wi-Fi de la carte réceptrice doit être point d’accès et station (WIFI_AP_STA).
- Vous pouvez configurer le même canal Wi-Fi manuellement, mais nous le ferons automatiquement. L’expéditeur essaiera différents canaux Wi-Fi jusqu’à ce qu’il obtienne une réponse du serveur.
Aperçu du projet
Voici un aperçu rapide de l’exemple que nous allons construire :

- Il existe deux cartes émettrices ESP (ESP32 ou ESP8266) qui envoient des lectures* via ESP-NOW à une carte réceptrice ESP32 (configuration ESP-NOW plusieurs à un) ;
- La carte réceptrice reçoit les paquets et affiche les lectures sur une page Web ;
- La page Web est mise à jour automatiquement chaque fois qu’elle reçoit une nouvelle lecture à l’aide des événements envoyés par le serveur (SSE) ;
- Le récepteur envoie également des données à l’expéditeur, ceci pour illustrer comment établir une communication bidirectionnelle. Par exemple, nous enverrons des valeurs arbitraires, mais vous pouvez facilement les remplacer par des lectures de capteurs ou toute autre donnée comme des valeurs de seuil, ou des commandes pour activer/désactiver les GPIO.
* nous enverrons des valeurs de température et d’humidité arbitraires – nous n’utiliserons pas de capteur réel. Après avoir testé le projet et vérifié que tout fonctionne comme prévu, vous pouvez utiliser un capteur de votre choix (il n’est pas nécessaire que ce soit la température ou l’humidité).
Couplage automatique
Voici comment fonctionne l’auto-appairage avec les pairs (cartes expéditeur (serveur)/esclave) :

- Le pair envoie un message de type PAIRING au serveur (1) en utilisant l’adresse MAC de diffusion ff:ff:ff:ff:ff:ff. Lorsque vous envoyez des données à cette adresse MAC, tous les appareils ESP-NOW reçoivent le message. Pour que le serveur reçoive le message, il doit communiquer sur le même canal Wi-Fi.
- Si le pair ne reçoit pas de message du serveur, il essaie d’envoyer le même message sur un canal Wi-Fi différent. Il répète le processus jusqu’à ce qu’il reçoive un message du serveur.
- Le serveur reçoit le message et l’adresse du pair (2).
- Le serveur ajoute l’adresse du pair à sa liste de pairs (3).
- Le serveur répond au pair par un message de type PAIRING avec ses informations (adresse MAC et canal) (4).
- Le pair reçoit le message et le WiFi.macAddress du serveur (5).
- Le pair ajoute l’adresse reçue du serveur à sa liste de pairs (6).
- Le pair essaie d’envoyer un message à l’adresse du serveur mais il ne parvient pas à le transmettre*.
- Le pair ajoute le WiFi.softAPmacAddress du serveur à sa liste de pairs.
- Le pair envoie un message au serveur WiFi.softAPmacAddress.
- Le serveur reçoit le message du pair.
- Ils peuvent désormais communiquer de manière bidirectionnelle (6).
* ESP32 en mode WIFI_AP_STA répond avec son WiFi.macAddress mais il utilise WiFi.softAPmacAddress pour recevoir du pair ESP8266.
WiFi.softAPmacAddress est créé à partir de WiFi.macAddress en ajoutant 1 au dernier octet—vérifier la documentation.
Conditions préalables
Avant de poursuivre ce projet, assurez-vous de vérifier les prérequis suivants.
EDI Arduino
Nous allons programmer les cartes ESP32 et ESP8266 à l’aide de l’IDE Arduino, donc avant de poursuivre ce tutoriel, assurez-vous que les cartes ESP32 et ESP8266 sont installées dans votre IDE Arduino.
Bibliothèques de serveurs Web asynchrones
Pour construire le serveur Web, vous devez installer les bibliothèques suivantes :
Ces bibliothèques ne peuvent pas être installées via le gestionnaire de bibliothèque Arduino, vous devez donc copier les fichiers de bibliothèque dans le dossier Bibliothèques d’installation Arduino. Alternativement, dans votre IDE Arduino, vous pouvez aller à Sketch> Inclure la bibliothèque> Ajouter une bibliothèque .zip et sélectionner les bibliothèques que vous venez de télécharger.
Bibliothèque Arduino_JSON
Nos exemples utiliseront les Bibliothèque ArduinoJSON par Benoit Blanchon version 6.18.3. Vous pouvez installer cette bibliothèque dans le gestionnaire de bibliothèque Arduino IDE. Allez simplement dans Sketch > Inclure la bibliothèque > Gérer les bibliothèques et recherchez le nom de la bibliothèque ArduinoJSON comme suit :

Pièces requises
Pour tester ce projet, vous avez besoin d’au moins trois cartes ESP. Une carte ESP32 pour agir en tant que serveur et deux cartes ESP émettrices/esclaves qui peuvent être ESP32 ou ESP8266.
Serveur ESP32
Voici les fonctionnalités du serveur :
- Se couple automatiquement avec des pairs (autres cartes ESP-NOW);
- Reçoit des paquets de pairs ;
- Héberge un serveur Web pour afficher les derniers paquets reçus ;
- Renvoie également les données aux autres cartes (communication bidirectionnelle avec les pairs).
Code serveur ESP32
Téléchargez le code suivant sur votre carte ESP32. Cela peut recevoir des données de plusieurs cartes. Cependant, la page Web est simplement préparée pour afficher les données de deux tableaux. Vous pouvez facilement modifier la page Web pour accueillir plus de tableaux.
/*
Rui Santos
Complete project details at https://Raspberryme.com/?s=esp-now
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.
Based on JC Servaye example: https://github.com/Servayejc/esp_now_web_server/
*/
#include <esp_now.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include "AsyncTCP.h"
#include <ArduinoJson.h>
// Replace with your network credentials (STATION)
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
esp_now_peer_info_t slave;
int chan;
enum MessageType {PAIRING, DATA,};
MessageType messageType;
int counter = 0;
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
uint8_t msgType;
uint8_t id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
typedef struct struct_pairing { // new structure for pairing
uint8_t msgType;
uint8_t id;
uint8_t macAddr[6];
uint8_t channel;
} struct_pairing;
struct_message incomingReadings;
struct_message outgoingSetpoints;
struct_pairing pairingData;
AsyncWebServer server(80);
AsyncEventSource events("/events");
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP-NOW DASHBOARD</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.reading { font-size: 2.8rem; }
.packet { color: #bebebe; }
.card.temperature { color: #fd7e14; }
.card.humidity { color: #1b78e2; }
</style>
</head>
<body>
<div class="topnav">
<h3>ESP-NOW DASHBOARD</h3>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<h4><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</h4><p><span class="reading"><span id="t1"></span> °C</span></p><p class="packet">Reading ID: <span id="rt1"></span></p>
</div>
<div class="card humidity">
<h4><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</h4><p><span class="reading"><span id="h1"></span> %</span></p><p class="packet">Reading ID: <span id="rh1"></span></p>
</div>
<div class="card temperature">
<h4><i class="fas fa-thermometer-half"></i> BOARD #2 - TEMPERATURE</h4><p><span class="reading"><span id="t2"></span> °C</span></p><p class="packet">Reading ID: <span id="rt2"></span></p>
</div>
<div class="card humidity">
<h4><i class="fas fa-tint"></i> BOARD #2 - HUMIDITY</h4><p><span class="reading"><span id="h2"></span> %</span></p><p class="packet">Reading ID: <span id="rh2"></span></p>
</div>
</div>
</div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
document.getElementById("rh"+obj.id).innerHTML = obj.readingId;
}, false);
}
</script>
</body>
</html>)rawliteral";
void readDataToSend() {
outgoingSetpoints.msgType = DATA;
outgoingSetpoints.id = 0;
outgoingSetpoints.temp = random(0, 40);
outgoingSetpoints.hum = random(0, 100);
outgoingSetpoints.readingId = counter++;
}
// ---------------------------- esp_ now -------------------------
void printMAC(const uint8_t * mac_addr){
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print(macStr);
}
bool addPeer(const uint8_t *peer_addr) { // add pairing
memset(&slave, 0, sizeof(slave));
const esp_now_peer_info_t *peer = &slave;
memcpy(slave.peer_addr, peer_addr, 6);
slave.channel = chan; // pick a channel
slave.encrypt = 0; // no encryption
// check if the peer exists
bool exists = esp_now_is_peer_exist(slave.peer_addr);
if (exists) {
// Slave already paired.
Serial.println("Already Paired");
return true;
}
else {
esp_err_t addStatus = esp_now_add_peer(peer);
if (addStatus == ESP_OK) {
// Pair success
Serial.println("Pair success");
return true;
}
else
{
Serial.println("Pair failed");
return false;
}
}
}
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("Last Packet Send Status: ");
Serial.print(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success to " : "Delivery Fail to ");
printMAC(mac_addr);
Serial.println();
}
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
Serial.print(len);
Serial.print(" bytes of data received from : ");
printMAC(mac_addr);
Serial.println();
StaticJsonDocument<1000> root;
String payload;
uint8_t type = incomingData[0]; // first message byte is the type of message
switch (type) {
case DATA : // the message is data type
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
// create a JSON document with received data and send it by event to the web page
root["id"] = incomingReadings.id;
root["temperature"] = incomingReadings.temp;
root["humidity"] = incomingReadings.hum;
root["readingId"] = String(incomingReadings.readingId);
serializeJson(root, payload);
Serial.print("event send :");
serializeJson(root, Serial);
events.send(payload.c_str(), "new_readings", millis());
Serial.println();
break;
case PAIRING: // the message is a pairing request
memcpy(&pairingData, incomingData, sizeof(pairingData));
Serial.println(pairingData.msgType);
Serial.println(pairingData.id);
Serial.print("Pairing request from: ");
printMAC(mac_addr);
Serial.println();
Serial.println(pairingData.channel);
if (pairingData.id > 0) { // do not replay to server itself
if (pairingData.msgType == PAIRING) {
pairingData.id = 0; // 0 is server
// Server is in AP_STA mode: peers need to send data to server soft AP MAC address
WiFi.softAPmacAddress(pairingData.macAddr);
pairingData.channel = chan;
Serial.println("send response");
esp_err_t result = esp_now_send(mac_addr, (uint8_t *) &pairingData, sizeof(pairingData));
addPeer(mac_addr);
}
}
break;
}
}
void initESP_NOW(){
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
Serial.println();
Serial.print("Server MAC Address: ");
Serial.println(WiFi.macAddress());
// Set the device as a Station and Soft Access Point simultaneously
WiFi.mode(WIFI_AP_STA);
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Server SOFT AP MAC Address: ");
Serial.println(WiFi.softAPmacAddress());
chan = WiFi.channel();
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
initESP_NOW();
// Start Web server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
// Events
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
// start server
server.begin();
}
void loop() {
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
readDataToSend();
esp_now_send(NULL, (uint8_t *) &outgoingSetpoints, sizeof(outgoingSetpoints));
}
}
Comment fonctionne le code
Nous avons déjà expliqué en détail le fonctionnement du code serveur dans un projet précédent. Nous allons donc jeter un coup d’œil aux parties pertinentes pour l’appairage automatique.
Types de messages
Le serveur et les expéditeurs peuvent échanger deux types de messages : des messages avec des données d’appariement avec l’adresse MAC, le canal et l’identifiant de la carte, et des messages avec les données réelles comme les relevés de capteur.
Nous créons donc un type énuméré qui contient les types de messages entrants possibles (PAIRING et DATA).
enum MessageType {PAIRING, DATA,};
« Un type énuméré est un type de données (généralement défini par l’utilisateur) constitué d’un ensemble de constantes nommées appelées énumérateurs. L’action de créer un type énuméré définit une énumération. Lorsqu’un identifiant tel qu’une variable est déclaré comme ayant un type énuméré, la variable peut être affectée à n’importe lequel des énumérateurs comme valeur ». Source : https://playground.arduino.cc/Code/Enum/
Après cela, nous créons une variable de ce type que nous venons de créer appelée messageType. Rappelez-vous que cette variable ne peut avoir que deux valeurs possibles : PAIRING ou DATA.
MessageType messageType;
Structure de données
Créez une structure qui contiendra les données que nous recevrons. Nous avons appelé cette structure struct_message et elle contient le type de message (afin que nous sachions si nous avons reçu un message avec des données ou avec des informations sur les pairs), l’ID de la carte, les lectures de température et d’humidité et l’ID de lecture.
typedef struct struct_message {
uint8_t msgType;
uint8_t id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
Nous avons également besoin d’une autre structure pour contenir les informations d’homologue pour appairer l’homologue. Nous appelons cette structure struct_pairing. Cette structure contiendra le type de message, l’identifiant de la carte, l’adresse mac de la carte émettrice et le canal Wi-Fi.
typedef struct struct_pairing { // new structure for pairing
uint8_t msgType;
uint8_t id;
uint8_t macAddr[6];
uint8_t channel;
} struct_pairing;
Nous créons deux variables de type struct_message, une appelée entrantReadings qui stockera les lectures provenant des esclaves, et une autre appelée outgoingSetpoints qui contiendra les données à envoyer aux esclaves.
struct_message incomingReadings;
struct_message outgoingSetpoints;
Nous créons également une variable de type struct_pairing pour contenir les informations sur les pairs.
struct_pairing pairingData;
Fonction readDataToSend()
Le readDataToSend() doit être utilisé pour obtenir des données de n’importe quel capteur que vous utilisez et les mettre sur la structure associée à envoyer aux cartes esclaves.
void readDataToSend() {
outgoingSetpoints.msgType = DATA;
outgoingSetpoints.id = 0;
outgoingSetpoints.temp = random(0, 40);
outgoingSetpoints.hum = random(0, 100);
outgoingSetpoints.readingId = counter++;
}
Le msgType doit être DATA. L’id correspond à l’id de la carte (nous mettons l’ID de la carte serveur à 0, les autres cartes doivent avoir id=1, 2, 3, etc.). Enfin, la température et le bourdonnement maintiennent les lectures du capteur. Dans ce cas, nous les définissons sur des valeurs aléatoires. Vous devez remplacer cela par les fonctions correctes pour obtenir des données de votre capteur. Chaque fois que nous envoyons un nouvel ensemble de lectures, nous augmentons la variable de compteur.
Ajout d’un pair
Nous créons une fonction appelée addPeer() qui renverra une variable booléenne (vraie ou fausse) qui indique si le processus d’appariement a réussi ou non. Cette fonction essaie d’ajouter des pairs. Il sera appelé plus tard lorsque la carte recevra un message de type PAIRING. Si le pair est déjà sur la liste des pairs, il renvoie true. Elle renvoie également true si l’homologue est ajouté avec succès. Il renvoie false s’il ne parvient pas à ajouter le pair à la liste.
bool addPeer(const uint8_t *peer_addr) { // add pairing
memset(&slave, 0, sizeof(slave));
const esp_now_peer_info_t *peer = &slave;
memcpy(slave.peer_addr, peer_addr, 6);
slave.channel = chan; // pick a channel
slave.encrypt = 0; // no encryption
// check if the peer exists
bool exists = esp_now_is_peer_exist(slave.peer_addr);
if (exists) {
// Slave already paired.
Serial.println("Already Paired");
return true;
}
else {
esp_err_t addStatus = esp_now_add_peer(peer);
if (addStatus == ESP_OK) {
// Pair success
Serial.println("Pair success");
return true;
}
else
{
Serial.println("Pair failed");
return false;
}
}
}
Réception et traitement des messages ESP-NOW
La fonction OnDataRecv() sera exécutée lorsque vous recevrez un nouveau paquet ESP-NOW.
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
Dans cette fonction, imprimez la longueur du message et l’adresse MAC de l’expéditeur :
Serial.print(len);
Serial.print(" bytes of data received from : ");
printMAC(mac_addr);
Auparavant, nous avons vu que nous pouvions recevoir deux types de messages : PAIRING et DATA. Ainsi, nous devons gérer le contenu du message différemment selon le type de message. Nous pouvons obtenir le type de message comme suit :
uint8_t type = incomingData[0]; // first message byte is the type of message
Ensuite, nous exécuterons différents codes selon que le message est de type DATA ou PAIRING.
S’il est de type DATA, copiez les informations de la variable entrantData dans la variable de structure entranteReadings.
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Ensuite, créez un document JSON avec les informations reçues (root) :
// create a JSON document with received data and send it by event to the web page
root["id"] = incomingReadings.id;
root["temperature"] = incomingReadings.temp;
root["humidity"] = incomingReadings.hum;
root["readingId"] = String(incomingReadings.readingId);
Convertissez le document JSON en chaîne (charge utile) :
serializeJson(root, payload);
Après avoir rassemblé toutes les données reçues sur la variable de charge utile, envoyez ces informations au navigateur sous forme d’événement (« new_readings »).
events.send(payload.c_str(), "new_readings", millis());
Nous avons vu sur un projet précédent comment gérer ces événements côté client.
Si le message est de type PAIRING, il contient les informations sur l’homologue.
case PAIRING: // the message is a pairing request
Nous enregistrons les données reçues dans la variable comingData et imprimons les détails sur le Serial Monitor.
memcpy(&pairingData, incomingData, sizeof(pairingData));
Serial.println(pairingData.msgType);
Serial.println(pairingData.id);
Serial.print("Pairing request from: ");
printMAC(mac_addr);
Serial.println();
Serial.println(pairingData.channel);
Le serveur répond avec son adresse MAC (en mode point d’accès) et son canal, de sorte que le pair sache qu’il a envoyé les informations en utilisant le bon canal et peut ajouter le serveur en tant que pair.
if (pairingData.id > 0) { // do not replay to server itself
if (pairingData.msgType == PAIRING) {
pairingData.id = 0; // 0 is server
// Server is in AP_STA mode: peers need to send data to server soft AP MAC address
WiFi.softAPmacAddress(pairingData.macAddr);
pairingData.channel = chan;
Serial.println("send response");
esp_err_t result = esp_now_send(mac_addr, (uint8_t *) &pairingData, sizeof(pairingData));
Enfin, le serveur ajoute l’expéditeur à sa liste de pairs en utilisant la fonction addPeer() que nous avons créée précédemment.
addPeer(mac_addr);
Initialiser ESP-NOW
La fonction initESP_NOW() initialise ESP-NOW et enregistre les fonctions de rappel lorsque les données sont envoyées et reçues.
void initESP_NOW(){
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
}
installation()
Dans le setup(), imprimez l’adresse MAC de la carte :
Serial.println(WiFi.macAddress());
Réglez le récepteur ESP32 comme station et point d’accès logiciel simultanément :
WiFi.mode(WIFI_AP_STA);
Les lignes suivantes connectent l’ESP32 à votre réseau local et impriment l’adresse IP et le canal Wi-Fi :
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Imprimez l’adresse MAC de la carte en mode point d’accès, qui est différente de l’adresse MAC en mode station.
Serial.print("Server SOFT AP MAC Address: ");
Serial.println(WiFi.softAPmacAddress());
Obtenez le canal Wi-Fi de la carte et imprimez-le dans le moniteur série.
chan = WiFi.channel();
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
Initialisez ESP-NOW en appelant la fonction initESP_NOW() que nous avons créée précédemment.
initESP_NOW();
Envoi de messages de données aux cartes émettrices
Dans la boucle(), toutes les 5 secondes (EVENT_INTERVAL_MS) obtenez des données d’un capteur ou des données d’échantillon en appelant la fonction readDataToSend(). Il ajoute de nouvelles données à la structure outgoingSetpoints.
readDataToSend();
Enfin, envoyez ces données à tous les pairs enregistrés.
esp_now_send(NULL, (uint8_t *) &outgoingSetpoints, sizeof(outgoingSetpoints));
C’est à peu près ainsi que le code du serveur fonctionne lorsqu’il s’agit de gérer les messages ESP-NOW et d’ajouter automatiquement des pairs.
Tester le serveur
Après avoir téléchargé le code sur la carte du récepteur, appuyez sur le bouton EN/RST intégré. L’adresse IP ESP32 doit être imprimée sur le moniteur série ainsi que sur le canal Wi-Fi.

Vous pouvez accéder au serveur Web sur l’adresse IP de la carte. Pour le moment, aucune donnée ne sera affichée car nous n’avons pas encore préparé les cartes d’envoi. Laissez la carte serveur exécuter le code.
Expéditeur ESP32/ESP8266
Voici les caractéristiques de la carte émettrice :
- Se couple automatiquement avec le serveur ;
- Envoie des paquets avec des lectures de capteur au serveur ;
- Reçoit également des données du serveur (communication bidirectionnelle).
Couplage automatique
Voici comment fonctionne l’association automatique avec le serveur :
- L’expéditeur n’a pas accès au routeur ;
- L’expéditeur ne connaît pas l’adresse MAC du serveur ;
- Le serveur doit être en cours d’exécution pour que cela fonctionne (avec le code précédent) ;
- L’expéditeur définit esp maintenant sur le canal 1 ;
- Le serveur ajoute une entrée avec l’adresse de diffusion à sa liste d’homologues ;
- L’expéditeur envoie une demande de message PAIRING en mode diffusion :
- Si le serveur reçoit le message nous sommes sur le bon canal :
- Le serveur ajoute le MAC reçu à sa liste de pairs (section précédente) ;
- Le serveur répond à l’adresse MAC par un message contenant son numéro de canal et son adresse MAC (section précédente) ;
- L’expéditeur remplace l’adresse de diffusion par l’adresse du serveur dans sa liste de pairs.
- autre
- L’expéditeur répète le processus sur le canal suivant.
WiFi.softAPmacAddress est créé à partir de WiFi.macAddress en ajoutant 1 au dernier octet—vérifier la documentation.
Code expéditeur ESP32
Téléchargez le code suivant sur votre carte ESP32.
/*
Rui Santos
Complete project details at https://Raspberryme.com/?s=esp-now
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.
Based on JC Servaye example: https://github.com/Servayejc/esp_now_sender/
*/
#include <Arduino.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#include <EEPROM.h>
// Set your Board and Server ID
#define BOARD_ID 1
#define MAX_CHANNEL 11 // for North America // 13 in Europe
uint8_t serverAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
//Structure to send data
//Must match the receiver structure
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
uint8_t msgType;
uint8_t id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
typedef struct struct_pairing { // new structure for pairing
uint8_t msgType;
uint8_t id;
uint8_t macAddr[6];
uint8_t channel;
} struct_pairing;
//Create 2 struct_message
struct_message myData; // data to send
struct_message inData; // data received
struct_pairing pairingData;
enum PairingStatus {NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED,};
PairingStatus pairingStatus = NOT_PAIRED;
enum MessageType {PAIRING, DATA,};
MessageType messageType;
#ifdef SAVE_CHANNEL
int lastChannel;
#endif
int channel = 1;
// simulate temperature and humidity data
float t = 0;
float h = 0;
unsigned long currentMillis = millis();
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
unsigned long start; // used to measure Pairing time
unsigned int readingId = 0;
// simulate temperature reading
float readDHTTemperature() {
t = random(0,40);
return t;
}
// simulate humidity reading
float readDHTHumidity() {
h = random(0,100);
return h;
}
void addPeer(const uint8_t * mac_addr, uint8_t chan){
esp_now_peer_info_t peer;
ESP_ERROR_CHECK(esp_wifi_set_channel(chan ,WIFI_SECOND_CHAN_NONE));
esp_now_del_peer(mac_addr);
memset(&peer, 0, sizeof(esp_now_peer_info_t));
peer.channel = chan;
peer.encrypt = false;
memcpy(peer.peer_addr, mac_addr, sizeof(uint8_t[6]));
if (esp_now_add_peer(&peer) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
memcpy(serverAddress, mac_addr, sizeof(uint8_t[6]));
}
void printMAC(const uint8_t * mac_addr){
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print(macStr);
}
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 OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
Serial.print("Packet received from: ");
printMAC(mac_addr);
Serial.println();
Serial.print("data size = ");
Serial.println(sizeof(incomingData));
uint8_t type = incomingData[0];
switch (type) {
case DATA : // we received data from server
memcpy(&inData, incomingData, sizeof(inData));
Serial.print("ID = ");
Serial.println(inData.id);
Serial.print("Setpoint temp = ");
Serial.println(inData.temp);
Serial.print("SetPoint humidity = ");
Serial.println(inData.hum);
Serial.print("reading Id = ");
Serial.println(inData.readingId);
if (inData.readingId % 2 == 1){
digitalWrite(LED_BUILTIN, LOW);
} else {
digitalWrite(LED_BUILTIN, HIGH);
}
break;
case PAIRING: // we received pairing data from server
memcpy(&pairingData, incomingData, sizeof(pairingData));
if (pairingData.id == 0) { // the message comes from server
printMAC(mac_addr);
Serial.print("Pairing done for ");
printMAC(pairingData.macAddr);
Serial.print(" on channel " );
Serial.print(pairingData.channel); // channel used by the server
Serial.print(" in ");
Serial.print(millis()-start);
Serial.println("ms");
addPeer(pairingData.macAddr, pairingData.channel); // add the server to the peer list
#ifdef SAVE_CHANNEL
lastChannel = pairingData.channel;
EEPROM.write(0, pairingData.channel);
EEPROM.commit();
#endif
pairingStatus = PAIR_PAIRED; // set the pairing status
}
break;
}
}
PairingStatus autoPairing(){
switch(pairingStatus) {
case PAIR_REQUEST:
Serial.print("Pairing request on channel " );
Serial.println(channel);
// set WiFi channel
ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE));
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
}
// set callback routines
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
// set pairing data to send to the server
pairingData.msgType = PAIRING;
pairingData.id = BOARD_ID;
pairingData.channel = channel;
// add peer and send request
addPeer(serverAddress, channel);
esp_now_send(serverAddress, (uint8_t *) &pairingData, sizeof(pairingData));
previousMillis = millis();
pairingStatus = PAIR_REQUESTED;
break;
case PAIR_REQUESTED:
// time out to allow receiving response from server
currentMillis = millis();
if(currentMillis - previousMillis > 250) {
previousMillis = currentMillis;
// time out expired, try next channel
channel ++;
if (channel > MAX_CHANNEL){
channel = 1;
}
pairingStatus = PAIR_REQUEST;
}
break;
case PAIR_PAIRED:
// nothing to do here
break;
}
return pairingStatus;
}
void setup() {
Serial.begin(115200);
Serial.println();
pinMode(LED_BUILTIN, OUTPUT);
Serial.print("Client Board MAC Address: ");
Serial.println(WiFi.macAddress());
WiFi.mode(WIFI_STA);
WiFi.disconnect();
start = millis();
#ifdef SAVE_CHANNEL
EEPROM.begin(10);
lastChannel = EEPROM.read(0);
Serial.println(lastChannel);
if (lastChannel >= 1 && lastChannel <= MAX_CHANNEL) {
channel = lastChannel;
}
Serial.println(channel);
#endif
pairingStatus = PAIR_REQUEST;
}
void loop() {
if (autoPairing() == PAIR_PAIRED) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
//Set values to send
myData.msgType = DATA;
myData.id = BOARD_ID;
myData.temp = readDHTTemperature();
myData.hum = readDHTHumidity();
myData.readingId = readingId++;
esp_err_t result = esp_now_send(serverAddress, (uint8_t *) &myData, sizeof(myData));
}
}
}
Code expéditeur ESP8266
Si vous utilisez des cartes ESP8266, utilisez plutôt le code suivant. Il est similaire au code précédent mais utilise les fonctions ESP-NOW spécifiques à ESP8266.
/*
Rui Santos
Complete project details at https://Raspberryme.com/?s=esp-now
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.
Based on JC Servaye example: https://https://github.com/Servayejc/esp8266_espnow
*/
#include <ESP8266WiFi.h>
#include <espnow.h>
uint8_t channel = 1;
int readingId = 0;
int id = 2;
unsigned long currentMillis = millis();
unsigned long lastTime = 0;
unsigned long timerDelay = 2000; // send readings timer
uint8_t broadcastAddressX[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
enum PairingStatus {PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED, };
PairingStatus pairingStatus = PAIR_REQUEST;
enum MessageType {PAIRING, DATA,};
MessageType messageType;
// Define variables to store DHT readings to be sent
float temperature;
float humidity;
// Define variables to store incoming readings
float incomingTemp;
float incomingHum;
int incomingReadingsId;
// Updates DHT readings every 10 seconds
//const long interval = 10000;
unsigned long previousMillis = 0; // will store last time DHT was updated
//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
uint8_t msgType;
uint8_t id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
typedef struct struct_pairing { // new structure for pairing
uint8_t msgType;
uint8_t id;
uint8_t macAddr[6];
uint8_t channel;
} struct_pairing;
// Create a struct_message called myData
struct_message myData;
struct_message incomingReadings;
struct_pairing pairingData;
#define BOARD_ID 2
unsigned long start;
// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("Last Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
void printMAC(const uint8_t * mac_addr){
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print(macStr);
}
void printIncomingReadings(){
// Display Readings in Serial Monitor
Serial.println("INCOMING READINGS");
Serial.print("Temperature: ");
Serial.print(incomingTemp);
Serial.println(" ºC");
Serial.print("Humidity: ");
Serial.print(incomingHum);
Serial.println(" %");
Serial.print("Led: ");
Serial.print(incomingReadingsId);
}
// Callback when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
Serial.print("Size of message : ");
Serial.print(len);
Serial.print(" from ");
printMAC(mac);
Serial.println();
uint8_t type = incomingData[0];
switch (type) {
case DATA :
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Serial.print(len);
Serial.print(" Data bytes received from: ");
printMAC(mac);
Serial.println();
incomingTemp = incomingReadings.temp;
incomingHum = incomingReadings.hum;
printIncomingReadings();
if (incomingReadings.readingId % 2 == 1){
digitalWrite(LED_BUILTIN, LOW);
} else {
digitalWrite(LED_BUILTIN, HIGH);
}
break;
case PAIRING:
memcpy(&pairingData, incomingData, sizeof(pairingData));
if (pairingData.id == 0) { // the message comes from server
Serial.print("Pairing done for ");
printMAC(pairingData.macAddr);
Serial.print(" on channel " );
Serial.print(pairingData.channel); // channel used by the server
Serial.print(" in ");
Serial.print(millis()-start);
Serial.println("ms");
//esp_now_del_peer(pairingData.macAddr);
//esp_now_del_peer(mac);
esp_now_add_peer(pairingData.macAddr, ESP_NOW_ROLE_COMBO, pairingData.channel, NULL, 0); // add the server to the peer list
pairingStatus = PAIR_PAIRED ; // set the pairing status
}
break;
}
}
void getReadings(){
// Read Temperature
temperature = 22.5;
humidity = 55.5;
}
PairingStatus autoPairing(){
switch(pairingStatus) {
case PAIR_REQUEST:
Serial.print("Pairing request on channel " );
Serial.println(channel);
// clean esp now
esp_now_deinit();
WiFi.mode(WIFI_STA);
// set WiFi channel
wifi_promiscuous_enable(1);
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
//WiFi.printDiag(Serial);
WiFi.disconnect();
// Init ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
}
esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
// set callback routines
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
// set pairing data to send to the server
pairingData.id = BOARD_ID;
pairingData.channel = channel;
previousMillis = millis();
// add peer and send request
Serial.println(esp_now_send(broadcastAddressX, (uint8_t *) &pairingData, sizeof(pairingData)));
pairingStatus = PAIR_REQUESTED;
break;
case PAIR_REQUESTED:
// time out to allow receiving response from server
currentMillis = millis();
if(currentMillis - previousMillis > 100) {
previousMillis = currentMillis;
// time out expired, try next channel
channel ++;
if (channel > 11) {
channel = 0;
}
pairingStatus = PAIR_REQUEST;
}
break;
case PAIR_PAIRED:
//Serial.println("Paired!");
break;
}
return pairingStatus;
}
void setup() {
// Init Serial Monitor
Serial.begin(74880);
pinMode(LED_BUILTIN, OUTPUT);
// Init DHT sensor
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
Serial.println(WiFi.macAddress());
WiFi.disconnect();
// Init ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Set ESP-NOW Role
esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
// Register for a callback function that will be called when data is received
esp_now_register_recv_cb(OnDataRecv);
esp_now_register_send_cb(OnDataSent);
pairingData.id = 2;
}
void loop() {
if (autoPairing() == PAIR_PAIRED) {
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 10000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
Serial.print(".");
getReadings();
//Set values to send
myData.msgType = DATA;
myData.id = 2;
myData.temp = temperature;
myData.hum = humidity;
myData.readingId = readingId ++;
// Send message via ESP-NOW to all peers
esp_now_send(pairingData.macAddr, (uint8_t *) &myData, sizeof(myData));
lastEventTime = millis();
}
}
}
Comment fonctionne le code
L’ESP32 et l’ESP8266 sont légèrement différents en ce qui concerne les fonctions spécifiques à ESP-NOW. Mais ils sont structurés de manière similaire. Nous allons donc jeter un œil au code ESP32.
Nous examinerons les sections pertinentes qui gèrent le couplage automatique avec le serveur. Le reste du code a déjà été expliqué en détail dans un projet précédent.
Définir l’ID du tableau
Définissez l’ID de la carte émettrice. Chaque carte doit avoir un identifiant différent afin que le serveur sache qui a envoyé le message. L’ID de carte 0 est réservé au serveur, vous devez donc commencer à numéroter vos cartes émettrices à 1.
// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1
Définir le nombre maximum de canaux
L’expéditeur parcourra différents canaux Wi-Fi jusqu’à ce qu’il trouve le serveur. Alors, définissez le nombre maximum de canaux.
#define MAX_CHANNEL 11 // for North America // 13 in Europe
Adresse MAC du serveur
La carte émettrice ne connaît pas l’adresse MAC du serveur. Nous allons donc commencer par envoyer un message à l’adresse MAC de diffusion FF:FF:FF:FF:FF:FF sur différents canaux. Lorsque nous envoyons des messages à cette adresse MAC, tous les appareils ESP-NOW reçoivent ce message. Ensuite, le serveur répondra avec son adresse MAC réelle lorsque nous trouverons le bon canal Wi-Fi.
uint8_t serverAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
Structure de données
Comme pour le code serveur, nous créons deux structures. Un pour recevoir les données réelles et un autre pour recevoir les détails de l’appariement.
//Structure to send data
//Must match the receiver structure
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
uint8_t msgType;
uint8_t id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
typedef struct struct_pairing { // new structure for pairing
uint8_t msgType;
uint8_t id;
uint8_t macAddr[6];
uint8_t channel;
} struct_pairing;
//Create 2 struct_message
struct_message myData; // data to send
struct_message inData; // data received
struct_pairing pairingData;
Jumelage de statues
Ensuite, nous créons un type d’énumération appelé ParingStatus qui peut avoir les valeurs suivantes : NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED et PAIR_PAIRED. Cela nous aidera à suivre la situation du statut d’appariement.
enum PairingStatus {NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED,};
Nous créons une variable de ce type appelée pairingStatus. Lorsque la carte démarre pour la première fois, elle n’est pas couplée, elle est donc définie sur NOT_PAIRED.
PairingStatus pairingStatus = NOT_PAIRED;
Types de messages
Comme nous l’avons fait dans le serveur, nous créons également un MessageType afin que nous sachions si nous avons reçu un message d’appariement ou un message avec des données.
enum MessageType {PAIRING, DATA,};
MessageType messageType;
Ajout d’un pair
Cette fonction ajoute un nouveau pair à la liste. Il accepte comme arguments l’adresse MAC et le canal du pair.
void addPeer(const uint8_t * mac_addr, uint8_t chan){
esp_now_peer_info_t peer;
ESP_ERROR_CHECK(esp_wifi_set_channel(chan ,WIFI_SECOND_CHAN_NONE));
esp_now_del_peer(mac_addr);
memset(&peer, 0, sizeof(esp_now_peer_info_t));
peer.channel = chan;
peer.encrypt = false;
memcpy(peer.peer_addr, mac_addr, sizeof(uint8_t[6]));
if (esp_now_add_peer(&peer) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
memcpy(serverAddress, mac_addr, sizeof(uint8_t[6]));
}
Réception et traitement des messages ESP-NOW
La fonction OnDataRecv() sera exécutée lorsque vous recevrez un nouveau paquet ESP-NOW.
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
Dans cette fonction, imprimez la longueur du message et l’adresse MAC de l’expéditeur :
Serial.print("Packet received from: ");
printMAC(mac_addr);
Serial.println();
Serial.print("data size = ");
Serial.println(sizeof(incomingData));
Auparavant, nous avons vu que nous pouvions recevoir deux types de messages : PAIRING et DATA. Ainsi, nous devons gérer le contenu du message différemment selon le type de message. Nous pouvons obtenir le type de message comme suit :
uint8_t type = incomingData[0]; // first message byte is the type of message
Ensuite, nous exécuterons différents codes selon que le message est de type DATA ou PAIRING.
S’il est de type DATA, copiez les informations de la variable entrantData dans la variable de structure inData.
memcpy(&inData, incomingData, sizeof(inData));
Ensuite, nous imprimons simplement les données reçues sur le Serial Monitor. Vous pouvez effectuer toute autre tâche avec les données reçues qui pourrait être utile pour votre projet.
Serial.print("ID = ");
Serial.println(inData.id);
Serial.print("Setpoint temp = ");
Serial.println(inData.temp);
Serial.print("SetPoint humidity = ");
Serial.println(inData.hum);
Serial.print("reading Id = ");
Serial.println(inData.readingId);
Dans ce cas, nous faisons clignoter la LED intégrée chaque fois que l’ID de lecture est un nombre impair, mais vous pouvez effectuer toute autre tâche en fonction des données reçues.
if (incomingReadings.readingId % 2 == 1){
digitalWrite(LED_BUILTIN, LOW);
} else {
digitalWrite(LED_BUILTIN, HIGH);
}
break;
Si le message est de type PAIRING, nous vérifions d’abord si le message reçu provient du serveur et non d’une autre carte émettrice. Nous le savons car la variable id du serveur est 0.
case PAIRING: // we received pairing data from server
memcpy(&pairingData, incomingData, sizeof(pairingData));
if (pairingData.id == 0) { // the message comes from server
Ensuite, nous imprimons l’adresse MAC et le canal. Ces informations sont envoyées par le serveur.
Serial.print("Pairing done for ");
printMAC(pairingData.macAddr);
Serial.print(" on channel " );
Serial.print(pairingData.channel); // channel used by the server
Donc, maintenant que nous connaissons les détails du serveur, nous pouvons appeler la fonction addPeer() et passer comme arguments l’adresse MAC et le canal du serveur pour ajouter le serveur à la liste des pairs.
addPeer(pairingData.macAddr, pairingData.channel); // add the server to the peer list
Si l’appairage est réussi, nous changeons le pairingStatus en PAIR_PAIRED.
pairingStatus = PAIR_PAIRED; // set the pairing status
Couplage automatique
La fonction autoPairing() renvoie l’état de l’appairage.
PairingStatus autoPairing(){
Nous pouvons avoir différents scénarios. S’il est de type PAIR_REQUEST, il mettra en place les fonctions de rappel ESP-NOW et enverra le premier message de type PAIRING à l’adresse de diffusion sur un canal prédéfini (commençant à 1). Après cela, nous changeons le statut d’appariement en PAIR_REQUESTED (cela signifie que nous avons déjà envoyé une demande).
case PAIR_REQUEST:
Serial.print("Pairing request on channel " );
Serial.println(channel);
// set WiFi channel
ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE));
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
}
// set callback routines
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
// set pairing data to send to the server
pairingData.msgType = PAIRING;
pairingData.id = BOARD_ID;
pairingData.channel = channel;
// add peer and send request
addPeer(serverAddress, channel);
esp_now_send(serverAddress, (uint8_t *) &pairingData, sizeof(pairingData));
previousMillis = millis();
pairingStatus = PAIR_REQUESTED;
Après avoir envoyé un message d’appariement, nous attendons un certain temps pour voir si nous recevons un message du serveur. Si ce n’est pas le cas, nous essayons sur le canal Wi-Fi suivant et changeons à nouveau le pairingStatus en PAIR_REQUEST, afin que la carte envoie une nouvelle demande sur un canal Wi-Fi différent.
case PAIR_REQUESTED:
// time out to allow receiving response from server
currentMillis = millis();
if(currentMillis - previousMillis > 250) {
previousMillis = currentMillis;
// time out expired, try next channel
channel ++;
if (channel > MAX_CHANNEL){
channel = 1;
}
pairingStatus = PAIR_REQUEST;
}
break;
Si le pairingStatus est PAIR_PAIRED, ce qui signifie que nous sommes déjà jumelés avec le serveur, nous n’avons rien à faire.
case PAIR_PAIRED:
// nothing to do here
break;
Enfin, renvoyez le pairingStatus.
return pairingStatus;
installation()
Dans setup(), définissez le pairingStatus sur PAIR_REQUEST.
pairingStatus = PAIR_REQUEST;
boucle()
Dans la boucle (), vérifiez si la carte est couplée avec le serveur avant de faire quoi que ce soit d’autre.
if (autoPairing() == PAIR_PAIRED) {
Cela exécutera la fonction autoPairing() et gérera le couplage automatique avec le serveur. Lorsque la carte est appairée avec l’expéditeur (PAIR_PAIRED), on peut communiquer avec le serveur pour échanger des données avec des messages de type DATA.
Envoi de messages au serveur
Dans ce cas, nous envoyons des valeurs arbitraires de température et d’humidité, mais vous pouvez échanger toute autre donnée avec le serveur.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
//Set values to send
myData.msgType = DATA;
myData.id = BOARD_ID;
myData.temp = readDHTTemperature();
myData.hum = readDHTHumidity();
myData.readingId = readingId++;
esp_err_t result = esp_now_send(serverAddress, (uint8_t *) &myData, sizeof(myData));
}
Test des cartes émettrices
Maintenant, vous pouvez tester les cartes émettrices. Nous vous recommandons d’ouvrir une communication série avec le serveur sur un autre logiciel comme PuTTY par exemple afin de pouvoir voir ce qui se passe sur le serveur et l’expéditeur simultanément.
Après avoir lancé le serveur, vous pouvez télécharger le code de l’expéditeur sur les autres cartes.
Après avoir téléchargé le code, ouvrez le moniteur série à un débit en bauds de 115200 et appuyez sur le bouton RST pour que la carte commence à exécuter le code.
C’est ce que l’expéditeur doit renvoyer.

Comme vous pouvez le voir, il envoie d’abord une demande d’appariement en utilisant différents canaux jusqu’à ce qu’il obtienne une réponse du serveur. Dans ce cas, il utilise le canal 6.
Après cela, nous commençons à recevoir des messages du serveur. Nous envoyons également des messages au serveur.
Côté serveur, voici ce qui se passe :

Le serveur reçoit une demande d’appariement de l’expéditeur. Il sera couplé avec l’expéditeur. Dans mon cas, il était déjà couplé car j’avais déjà exécuté ce code. Après les données, nous commençons à envoyer et à recevoir des données.
Vous pouvez télécharger le code de l’expéditeur sur plusieurs cartes et elles seront toutes automatiquement couplées avec le serveur. Les cartes émettrices peuvent être des cartes ESP32 ou ESP8266. Assurez-vous d’utiliser le bon code pour la carte que vous utilisez.
Maintenant, vous pouvez accéder à l’adresse IP du serveur pour voir les lectures des cartes d’envoi affichées sur le tableau de bord. La page Web est préparée pour afficher les lectures de deux tableaux. Si vous souhaitez afficher plus de lectures, vous devez modifier la page Web.

Conclusion
Dans ce didacticiel, nous vous avons montré comment créer un serveur Web ESP-NOW, l’associer automatiquement à des pairs et établir une communication bidirectionnelle entre les cartes serveur et expéditeur.
Vous pouvez adapter les parties de code qui traitent de l’association automatique et les utiliser dans vos exemples ESP-NOW.
Nous tenons à remercier Jean-Claude Servaye d’avoir partagé avec nous ses esquisses de code d’appairage automatique ESP-NOW. Nous n’avons apporté que quelques modifications aux croquis. Vous pouvez trouver les codes d’origine sur sa page GitHub.
Tu pourrais aussi aimer:
Merci d’avoir lu.
Cette vidéo vous emmène dans l’histoire de Raspberry Pi :
