Serveur Web ESP32 ESP-NOW : contrôlez plusieurs cartes ESP32

Serveur Web ESP32 ESP-NOW : contrôlez plusieurs cartes ESP32

Découvrez comment créer un serveur Web ESP32 qui utilise ESP-NOW pour contrôler les GPIO de plusieurs cartes ESP32. Le serveur Web agit comme une télécommande pour envoyer des commandes ESP-NOW à d’autres cartes afin de contrôler leurs GPIO. Nous contrôlerons deux cartes ESP32 et deux GPIO de chacune. Cela peut être facilement étendu pour contrôler plus de cartes ESP32 ou plus de GPIO.

Serveur Web ESP32 ESP-NOW : contrôlez plusieurs cartes ESP32

Vous aimerez peut-être aussi lire : Construire un serveur Web ESP32 : le guide complet pour les débutants.

Table des matières:

Aperçu du projet

Voici un aperçu du projet que nous allons construire.

Serveur Web ESP32 ESP-NOW - Contrôler plusieurs cartes : aperçu du projet
  • Dans ce projet, une carte ESP32 (contrôleur) hébergera un serveur Web qui affiche une page Web avec des boutons permettant de contrôler deux GPIO à partir de deux autres cartes ESP32.
  • La carte contrôleur ESP32 agira comme un émetteur ESP-NOW qui enverra des données pour contrôler les GPIO des autres cartes ESP32. Nous contrôlerons les GPIO 2 et 4 sur ESP32 #1, et les GPIO 20 et 21 sur ESP32 #2.
  • La carte contrôleur ESP32 agit comme un émetteur ESP-NOW et les autres cartes agissent comme des récepteurs ESP-NOW.
  • La carte contrôleur envoie des messages à une carte spécifique en se référant à son adresse MAC.
  • Pour contrôler les tableaux, le serveur Web affiche deux cartes, une pour chaque tableau. Chaque carte contient deux interrupteurs à bascule pour contrôler deux GPIO de chaque carte.
  • Si la commande ESP-NOW n’est pas délivrée avec succès, le commutateur à bascule revient au dernier état délivré avec succès.

Vous débutez avec le protocole de communication ESP-NOW ? Lisez ce guide : Premiers pas avec ESP-NOW (ESP32 avec Arduino IDE).

Conditions préalables

Avant de continuer, assurez-vous de respecter les prérequis. Vous devez suivre toutes les étapes, sinon votre projet ne fonctionnera pas.

1) Pièces requises

Pour ce projet, vous avez besoin des pièces suivantes :

Circuit de cartes émetteur-récepteur ESP32 ESP-NOW

Vous pouvez utiliser les liens précédents ou vous rendre directement sur MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !

1783632667 43 Serveur Web ESP32 ESP NOW controlez plusieurs cartes ESP32

2) Installez les cartes ESP32 dans l’IDE Arduino

Logo ArduinoIDE2

Nous allons programmer l’ESP32 à l’aide de l’IDE Arduino. Assurez-vous que les cartes ESP32 sont installées. Suivez le tutoriel suivant :

3) Installez les bibliothèques ESPAsyncWebServer

Pour créer le serveur Web ESP32, nous utiliserons les bibliothèques suivantes.

Vous pouvez installer ces bibliothèques dans Arduino Library Manager. Ouvrez le gestionnaire de bibliothèque en cliquant sur l’icône Bibliothèque dans la barre latérale gauche.

Recherchez ESPAsyncWebServer et installez ESPAsyncWebServer par ESP32Async.

Installation de l'IDE Arduino ESPAsyncWebServer ESP32

Recherchez AsyncTCP et installez AsyncTCP par ESP32Async.

Installation de l'IDE Arduino AsyncTCP ESP32

1) Préparation des cartes réceptrices

Suivez cette procédure pour chacune de vos cartes réceptrices ESP32. Dans cet exemple, nous utilisons deux cartes réceptrices ESP32, mais vous ne pouvez en utiliser qu’une, trois ou plus.

Deux cartes ESP32 avec adresse MAC LED

Obtenir l’adresse MAC des cartes ESP32

Pour communiquer via ESP-NOW, vous devez connaître l’adresse MAC de vos cartes réceptrices. Pour obtenir l’adresse MAC de vos cartes, téléchargez le code suivant sur chacune de vos cartes.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/get-change-esp32-esp8266-mac-address-arduino/
  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 
#include 

void readMacAddress(){
  uint8_t baseMac[6];
  esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
  if (ret == ESP_OK) {
    Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
                  baseMac[0], baseMac[1], baseMac[2],
                  baseMac[3], baseMac[4], baseMac[5]);
  } else {
    Serial.println("Failed to read MAC address");
  }
}

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

  WiFi.mode(WIFI_STA);
  WiFi.STA.begin();

  Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
  readMacAddress();
}
 
void loop(){

}

Afficher le code brut

Après le téléchargement, ouvrez le moniteur série et réinitialisez l’ESP32. Son adresse MAC sera imprimée. Enregistrez-le car vous en aurez besoin sur le code de l’expéditeur du serveur Web ESP32.

Obtenez l’adresse MAC de toutes vos cartes réceptrices.

Par exemple, dans mon cas, j’obtiens :

  • Carte récepteur ESP32 1: 30:ae:a4:f6:7d:4c
  • Carte récepteur ESP32 2 : 34:85:18:40:2f:cc
ESP32 Obtenir l'adresse MAC du moniteur série

Câblage du circuit

Nous contrôlerons deux GPIO sur chaque carte ESP32. Nous contrôlerons les GPIO 2 et 4 sur notre carte ESP32 DOIT, et les GPIO 20 et 21 sur notre ESP32S3. Vous pouvez contrôler n’importe quel autre GPIO.

Nous connecterons une LED à chaque GPIO que nous souhaitons contrôler.

ESP32 #1 ESP32 #2
GPIO2 GPIO20
GPIO4 GPIO21

Voici un exemple de schéma montrant comment connecter deux LED à votre carte.

ESP32 avec deux LED connectées - câblage du circuit

Code – Cartes réceptrices ESP-NOW ESP32

Téléchargez le code suivant sur chacune de vos cartes réceptrices. Ce code fonctionnera pour tous les GPIO que vous souhaitez contrôler.

/*********
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-esp-now-web-server-multiple-boards/
  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 
#include 

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
    int gpio;
    bool state;
} 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("Setting GPIO: ");
  Serial.println(myData.gpio);
  Serial.print("state to: ");
  Serial.println(myData.state);
  // Control GPIO
  pinMode(myData.gpio, OUTPUT);
  digitalWrite(myData.gpio, myData.state);
  Serial.println();
}
 
void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  
  // Initialize WIFI STA interface
  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(esp_now_recv_cb_t(OnDataRecv));
}
 
void loop() {

}

Afficher le code brut

Comment fonctionne le code ?

Jetons un coup d’œil rapide au fonctionnement du code du récepteur. Vous pouvez également passer à la section suivante.

Inclure les bibliothèques

Tout d’abord, incluez les bibliothèques esp_now.h et WiFi.h (requises pour initialiser l’interface de la station Wi-Fi afin d’utiliser ESP-NOW).

#include 
#include 

Structure de données pour recevoir des données

Créez une structure pour recevoir les données. Nous l’appelons struct_message. Cette structure doit être la même que celle définie dans le schéma de l’expéditeur. La structure contiendra les données reçues via ESP-NOW. Les données contiennent le numéro GPIO que nous voulons contrôler (int gpio) et son état (bool state).

typedef struct struct_message {
    int gpio;
    bool state;
} struct_message;

Après avoir créé la structure, créez une variable struct_message appelée myData, où nous stockerons les données reçues via ESP-NOW.

struct_message myData;

Fonction de rappel de réception ESP-NOW

Créez une fonction de rappel qui est appelée lorsque l’ESP32 reçoit les données via ESP-NOW. La fonction s’appelle onDataRecv() et doit accepter plusieurs paramètres : l’adresse MAC de l’expéditeur, le message entrant (structure des données) et la longueur du message.

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {

Cette ligne copie le contenu de la variable de données incomingData dans la variable de structure myData.

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

Désormais, la structure myData contient deux variables avec les valeurs envoyées par la carte émettrice. Vous pouvez accéder à la variable gpio avec myData.gpio et accéder à l’état avec myData.state. Nous imprimons ces données dans le Serial Monitor.

Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Setting GPIO: ");
Serial.println(myData.gpio);
Serial.print("state to: ");
Serial.println(myData.state);

Après avoir reçu les données, nous contrôlons le GPIO spécifié avec l’état reçu. Tout d’abord, nous définissons le GPIO comme SORTIE.

pinMode(myData.gpio, OUTPUT);

Et enfin, on le contrôle à l’aide de la fonction digitalWrite() et en passant en argument le GPIO (myData.gpio) et l’état (myData.state)

digitalWrite(myData.gpio, myData.state);

installation()

Dans setup(), initialisez le Serial Monitor.

Serial.begin(115200);

Configurez l’appareil comme station Wi-Fi.

WiFi.mode(WIFI_STA);

Initialisez ESP-NOW :

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

Enregistrez la fonction de rappel qui sera appelée lorsque les données seront reçues. Dans ce cas, nous nous inscrivons à la fonction OnDataRecv() créée précédemment.

esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

boucle()

Puisque nous nous sommes inscrits à une fonction de rappel lors de la réception de données, l’ESP32 écoutera les paquets ESP-NOW et exécutera automatiquement la fonction de rappel lorsqu’un paquet est reçu.

Dans ce cas, nous n’avons rien besoin d’ajouter à la boucle(). Il est vide, mais vous pouvez ajouter n’importe quel code dont vous avez besoin pour exécuter votre carte.

void loop() {

}

2) Carte contrôleur/expéditeur ESP32 du serveur Web ESP-NOW

La carte contrôleur ESP32 hébergera un serveur web pour envoyer des données afin de contrôler les GPIO de deux autres cartes ESP32. Voici le code :

/*********
  Rui Santos & Sara Santos - Raspberryme.com
  Complete instructions at https://Raspberryme.com/esp32-esp-now-web-server-multiple-boards/
  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 
#include 
#include 

// REPLACE WITH YOUR WI-FI CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// REPLACE WITH YOUR RECEIVER'S MAC ADDRESS
uint8_t board1Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t board2Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Structure to hold GPIO state
typedef struct struct_message {
    int gpio;
    bool state;
} struct_message;

struct_message board1Data;
struct_message board2Data;

// Confirmed delivered states
bool board1Btn1 = false;
bool board1Btn2 = false;
bool board2Btn1 = false;
bool board2Btn2 = false;

// Pending messages delivery tracking
struct PendingUpdate {
  bool active = false;
  int board;
  int gpio;
  bool newState;
  bool oldState;
};

PendingUpdate pendingUpdate;

// GPIO pin definitions
const int board1Pin1 = 2;
const int board1Pin2 = 4;
const int board2Pin1 = 20;
const int board2Pin2 = 21;

// Global variables
esp_now_peer_info_t peerInfo;
AsyncWebServer server(80);

// Function to get a pointer to the state variable based on board and GPIO
bool* 
(int board, int gpio) {
  if (board == 1 && gpio == board1Pin1) return &board1Btn1;
  if (board == 1 && gpio == board1Pin2) return &board1Btn2;
  if (board == 2 && gpio == board2Pin1) return &board2Btn1;
  if (board == 2 && gpio == board2Pin2) return &board2Btn2;
  return nullptr;
}

// ESP-NOW callback function when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  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("Packet to: ");
  Serial.print(macStr);
  Serial.print(" send status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");

  if (!pendingUpdate.active) return;

  // Ensure the pending update corresponds to the correct board and GPIO
  bool* statePtr = getStatePtr(pendingUpdate.board, pendingUpdate.gpio);
  if (statePtr == nullptr) {
    pendingUpdate.active = false;
    return;
  }

  // Commit or revert the state based on delivery status
  if (status == ESP_NOW_SEND_SUCCESS) {
    *statePtr = pendingUpdate.newState;
    Serial.printf("State committed: board=%d gpio=%d state=%d\n",
                  pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.newState);
  } else {
    *statePtr = pendingUpdate.oldState;
    Serial.printf("Delivery failed — state reverted: board=%d gpio=%d back to %d\n",
                  pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.oldState);
  }
  pendingUpdate.active = false;
}

// Function to send ESP-NOW message
esp_err_t sendEspNowMessage(const uint8_t *boardMacAddress, const struct_message &message) {
  esp_err_t result = esp_now_send(boardMacAddress, (const uint8_t *) &message, sizeof(message));
  // Handle the result of the send operation
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
    Serial.println("Error: ESP-NOW not initialized");
  } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
    Serial.println("Error: Peer not found");
  } else {
    Serial.printf("Error sending the data: %d\n", result);
  }
  return result;
}

// HTML Web Page
String renderHTML() {
  String html = R"rawliteral(



  ESP32 - ESP-NOW Controller
  
  


  

ESP32 - ESP-NOW Controller

)rawliteral"; html += R"rawliteral( )rawliteral"; html += R"rawliteral( )rawliteral"; html += R"rawliteral( )rawliteral"; return html; } void setup() { Serial.begin(115200); // Set device as a Wi-Fi Station and establish a Wi-Fi connection WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi Connected!"); // Print the ESP32 IP Address Serial.print("Access ESP32 IP Address: http://"); Serial.println(WiFi.localIP()); // 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(esp_now_send_cb_t(OnDataSent)); // Register peers peerInfo.channel = 0; peerInfo.encrypt = false; // Register first peer memcpy(peerInfo.peer_addr, board1Address, 6); if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Failed to add peer"); return; } // Register second peer memcpy(peerInfo.peer_addr, board2Address, 6); if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("Failed to add peer"); return; } // Root URL handler server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/html", renderHTML()); }); // LED Control via ESP-NOW server.on("/control", HTTP_GET, [](AsyncWebServerRequest *request){ // Validate URL parameters if (request->hasParam("board") && request->hasParam("gpio") && request->hasParam("state")) { int board = request->getParam("board")->value().toInt(); int gpio = request->getParam("gpio")->value().toInt(); bool newState = request->getParam("state")->value().toInt() == 1; bool* statePtr = getStatePtr(board, gpio); if (statePtr == nullptr) { request->send(400, "text/plain", "Invalid board/gpio"); return; } // Set up the pending update for delivery tracking pendingUpdate.active = true; pendingUpdate.board = board; pendingUpdate.gpio = gpio; pendingUpdate.newState = newState; pendingUpdate.oldState = *statePtr; struct_message msg; msg.gpio = gpio; msg.state = newState; uint8_t* target = (board == 1) ? board1Address : board2Address; esp_err_t result = sendEspNowMessage(target, msg); if (result != ESP_OK) { // If sending failed, revert the GPIO state immediately pendingUpdate.active = false; request->send(500, "text/plain", "Send failed (not transmitted)"); } else { // If sending succeeded, wait for delivery confirmation in the callback request->send(200, "text/plain", "Sent, awaiting delivery confirmation"); } } else { request->send(400, "text/plain", "Missing parameters"); } }); // Get confirmed states of each GPIO by board server.on("/states", HTTP_GET, [](AsyncWebServerRequest *request){ // Return the confirmed states of each GPIO by board in JSON format String json = "{"; json += "\"b1Btn1\":" + String(board1Btn1 ? "true" : "false") + ","; json += "\"b1Btn2\":" + String(board1Btn2 ? "true" : "false") + ","; json += "\"b2Btn1\":" + String(board2Btn1 ? "true" : "false") + ","; json += "\"b2Btn2\":" + String(board2Btn2 ? "true" : "false"); json += "}"; request->send(200, "application/json", json); }); server.begin(); Serial.println("ESP32 Web Server is Ready!"); } void loop() { }

Afficher le code brut

Avant de télécharger le code suivant sur votre carte contrôleur ESP32, vous devez insérer votre SSID et votre mot de passe ainsi que les adresses MAC des cartes réceptrices dans les lignes suivantes :

// REPLACE WITH YOUR WI-FI CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// REPLACE WITH YOUR RECEIVER'S MAC ADDRESS
uint8_t board1Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t board2Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

Comment fonctionne le code ?

Continuez à lire pour découvrir comment fonctionne le code ou passez à la section de démonstration.

Y compris les bibliothèques

Incluez les bibliothèques pour utiliser ESP-NOW et pour créer le serveur Web.

#include 
#include 
#include 

Informations d’identification réseau

Incluez vos informations d’identification réseau dans les lignes suivantes afin que l’ESP32 puisse se connecter à votre routeur via Wi-Fi.

// REPLACE WITH YOUR WI-FI CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Adresse MAC des cartes réceptrices

Modifiez les lignes suivantes avec l’adresse MAC de vos cartes réceptrices. Par exemple, dans mon cas :

  • Carte récepteur ESP32 1: 30:ae:a4:f6:7d:4c
  • Carte récepteur ESP32 2 : 34:85:18:40:2f:cc

Donc, ce sera comme ça :

// REPLACE WITH YOUR RECEIVER'S MAC Address
uint8_t board1Address[] = {0x30, 0xAE, 0xA4, 0xF6, 0x7D, 0x4C};
uint8_t board2Address[] = {0x34, 0x85, 0x18, 0x40, 0x2F, 0xCC};

Structure de données pour envoyer des données

Créez une structure de données pour envoyer des données aux autres cartes ESP32 afin de contrôler leurs GPIO. Il doit s’agir de la même structure que celle définie dans les cartes réceptrices. La structure contient le GPIO à contrôler (int gpio) et l’état (bool state), soit ON (true) soit OFF (false).

// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
    int gpio;
    bool state;
} struct_message;

Maintenant, créez deux variables de type struct_message, une pour chaque carte : board1Data et board2Data.

struct_message board1Data;
struct_message board2Data;

Confirmation des états livrés

Pour confirmer si l’état de chaque bouton est fourni, nous créons une variable pour chaque bouton sur la page du serveur Web.

// Confirmed delivered states
bool board1Btn1 = false;
bool board1Btn2 = false;
bool board2Btn1 = false;
bool board2Btn2 = false;

Nous créons également une structure qui nous aidera à savoir si le message a été livré, quel GPIO nous voulons contrôler, le nouvel état du GPIO et son ancien état. Cela aidera à décider s’il faut modifier l’état GPIO sur l’interface en fonction de l’état de livraison du message.

// Pending messages delivery tracking
struct PendingUpdate {
  bool active = false;
  int board;
  int gpio;
  bool newState;
  bool oldState;
};

Nous créons une nouvelle variable appelée endingUpdate de type PendingUpdate.

PendingUpdate pendingUpdate;

Variables globales

Nous créons des variables globales pour faire référence aux GPIO que nous voulons contrôler. Nous contrôlerons les GPIO 2 et 4 depuis la carte 1, et les GPIO 20 et 21 depuis la carte 2. Vous pouvez ajuster pour contrôler tous les GPIO de votre choix.

// GPIO pin definitions
const int board1Pin1 = 2;
const int board1Pin2 = 4;
const int board2Pin1 = 20;
const int board2Pin2 = 21;

Créez une variable de type esp_now_peer_info qui sera utilisée pour contenir des données sur les pairs ESP-NOW (les cartes réceptrices).

esp_now_peer_info_t peerInfo;

Créez une instance AsyncWebServer appelée serveur sur le port 80 qui fait référence à notre serveur Web.

AsyncWebServer server(80);

Fonction de rappel OnDataSent()

La fonction de rappel OnDataSent() s’exécutera lorsque nous enverrons un message ESP-NOW.

// ESP-NOW callback function when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  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("Packet to: ");
  Serial.print(macStr);
  Serial.print(" send status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");

  if (!pendingUpdate.active) return;

  // Ensure the pending update corresponds to the correct board and GPIO
  bool* statePtr = getStatePtr(pendingUpdate.board, pendingUpdate.gpio);
  if (statePtr == nullptr) {
    pendingUpdate.active = false;
    return;
  }

  // Commit or revert the state based on delivery status
  if (status == ESP_NOW_SEND_SUCCESS) {
    *statePtr = pendingUpdate.newState;
    Serial.printf("State committed: board=%d gpio=%d state=%d\n",
                  pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.newState);
  } else {
    *statePtr = pendingUpdate.oldState;
    Serial.printf("Delivery failed — state reverted: board=%d gpio=%d back to %d\n",
                  pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.oldState);
  }
  pendingUpdate.active = false;
}

Cette fonction vérifie si le message a été transmis avec succès et imprime le résultat dans le moniteur série.

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  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("Packet to: ");
  Serial.print(macStr);
  Serial.print(" send status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");

Ensuite, il vérifie également s’il existe un changement d’état en attente. Cela se produit lorsque nous cliquons sur les interrupteurs à bascule et attendons la confirmation de livraison.

if (!pendingUpdate.active) return;

Si le message est livré avec succès, nous validons le nouvel état. Sinon, nous revenons à l’ancien état.

// Commit or revert the state based on delivery status
if (status == ESP_NOW_SEND_SUCCESS) {
  *statePtr = pendingUpdate.newState;
  Serial.printf("State committed: board=%d gpio=%d state=%d\n",
                pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.newState);
} else {
  *statePtr = pendingUpdate.oldState;
  Serial.printf("Delivery failed — state reverted: board=%d gpio=%d back to %d\n",
                pendingUpdate.board, pendingUpdate.gpio, pendingUpdate.oldState);
}

Enfin, nous définissons la variable endingUpdate sur False car nous avons déjà confirmé la livraison.

pendingUpdate.active = false;

Le schéma suivant vous aidera à mieux comprendre son fonctionnement.

Serveur Web ESP-NOW - Envoyez des données pour contrôler d'autres cartes avec commentaires

Envoyer un message ESP-NOW

Le sendespNowMessage() envoie un message via ESP-NOW. Il accepte l’adresse MAC de la carte réceptrice et le message à délivrer comme arguments. Il imprime et renvoie le résultat.

// Function to send ESP-NOW message
esp_err_t sendEspNowMessage(const uint8_t *boardMacAddress, const struct_message &message) {
  esp_err_t result = esp_now_send(boardMacAddress, (const uint8_t *) &message, sizeof(message));
  // Handle the result of the send operation
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
    Serial.println("Error: ESP-NOW not initialized");
  } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
    Serial.println("Error: Peer not found");
  } else {
    Serial.printf("Error sending the data: %d\n", result);
  }
  return result;
}

Rendre la page HTML

La fonction renderHTML() crée la page Web avec HTML, CSS et JavaScript.

Serveur Web ESP32 ESP-NOW - contrôlez plusieurs cartes

La page Web est concaténée avec les GPIO que nous souhaitons contrôler (board1Pin1, board1Pin2, board2Pin1, board2Pin2). Cela facilite l’ajustement de la page Web à tous les GPIO que vous souhaitez contrôler.

String renderHTML() {
  String html = R"rawliteral(


(...)

)rawliteral";

  return html;
}

Styliser la page Web

Tout d’abord, nous incluons le CSS pour styliser la page Web entre les balises.

Toggle Switches

Then, we have the HTML section to create a card with two toggle switches for board 1.

  html += R"rawliteral(
  
)rawliteral";

Notez que pour créer la page Web, nous concaténons la valeur des variables board1Pin1 et board1Pin2 avec le reste de la page Web. Cela rend le serveur Web plus polyvalent. Il vous suffit de modifier les valeurs des variables pour contrôler d’autres GPIO.

Par exemple, dans notre cas :

board1Pin1 = 2;

Donc ce bout de code :

GPIO )rawliteral";
  html += String(board1Pin1);
  html += R"rawliteral(

Devient :

GPIO 2

Et cette rubrique :

html += R"rawliteral(
      

Ce sera comme ceci :

Il en va de même pour les autres interrupteurs à bascule.

Poignée interrupteur à bascule

Lorsque nous cliquons sur les interrupteurs à bascule, ils appelleront la fonction JavaScript toggleSwitch(). Ils passent comme arguments la carte à laquelle le commutateur à bascule fait référence, le GPIO et l’élément de commutateur à bascule (this) pour identifier le commutateur sur lequel vous avez cliqué.

La fonction toggleSwitch() est définie dans la section du HTML.

function toggleSwitch(board, gpio, element) {
      if (!element) {
        console.error("Element is undefined");
        return;
      }
      const state = element.checked;

      fetch(`/control?board=${board}&gpio=${gpio}&state=${state ? 1 : 0}`)
        .then(response => response.text())
        .then(data => console.log(data))
        .catch(err => {
          console.error(err);
          element.checked = !element.checked;
        });
    }

Par exemple, si vous cliquez sur le premier interrupteur à bascule, board=1, gpio=2 et element = interrupteur à bascule sur lequel vous avez cliqué. Ainsi, il exécutera :

toggleSwitch(1, 2, this);

Il vérifie l’état de l’interrupteur à bascule :

const state = element.checked;

Et puis, nous faisons une demande auprès du conseil approprié, GPIO, et précisons :

fetch(`/control?board=${board}&gpio=${gpio}&state=${state ? 1 : 0}`)

Par exemple, dans le cas du premier interrupteur à bascule, il fera une requête comme celle-ci :

/control?board=1&gpio=2&state=1

Lorsque l’ESP32 envoie une réponse, nous la convertissons en texte et l’imprimons sur la console JavaScript.

.then(response => response.text())
        .then(data => console.log(data))

Si la demande échoue (l’ESP32 n’a pas reçu la demande), nous remettons l’interrupteur à bascule à son état précédent.

.catch(err => {
  console.error(err);
  element.checked = !element.checked;

Mise à jour des états des commutateurs à bascule

Lorsque la page se charge pour la première fois, le navigateur ne connaît pas les états GPIO réels stockés sur l’ESP32. Toutes les cases à cocher peuvent initialement apparaître décochées (les interrupteurs à bascule sont désactivés).

Lors du premier chargement de la fenêtre, nous appelons la fonction loadStates() pour définir les états des commutateurs à bascule en fonction des états GPIO.

window.onload = function() {
  loadStates();
  setInterval(loadStates, 5000);

La fonction loadStates() est également appelée toutes les cinq secondes. Ceci est fait pour mettre à jour la page Web en fonction des états des GPIO (au cas où le message ne serait pas délivré).

setInterval(loadStates, 5000);

Donc, ce qui se passe est le suivant : la page Web se charge et JavaScript appelle la fonction loadStates() qui effectue une requête sur l’URL /states :

async function loadStates() {
  try {
      const response = await fetch('/states');
      const states = await response.json();
      applyStates(states);
    } catch (err) {
      console.error("Failed to load states:", err);
    }
  }

L’ESP32 répond avec JSON contenant les états actuels. Nous lisons ces valeurs (applyStates()) :

const states = await response.json();
applyStates(states);

La propriété .checked de chaque case à cocher est mise à jour pour refléter les états GPIO actuels. La page Web reflète désormais l’état actuel de l’appareil avec les valeurs des commutateurs à bascule correspondant à l’état correspondant reçu par l’ESP32.

function applyStates(states) {
  document.getElementById('b1Btn1').checked = states.b1Btn1 || false;
  document.getElementById('b1Btn2').checked = states.b1Btn2 || false;
  document.getElementById('b2Btn1').checked = states.b2Btn1 || false;
  document.getElementById('b2Btn2').checked = states.b2Btn2 || false;
}

installation()

Dans setup(), initialisez le Serial Monitor pour le débogage.

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

Définissez l’ESP32 comme station Wi-Fi, connectez-le à votre routeur et imprimez l’adresse IP de l’ESP32.

// Set device as a Wi-Fi Station and establish a Wi-Fi connection
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.println("\nWi-Fi Connected!");
// Print the ESP32 IP Address
Serial.print("Access ESP32 IP Address: http://");
Serial.println(WiFi.localIP());

Initialisez ESP-NOW.

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

Fonction de rappel ESP-NOW

Inscrivez-vous à une fonction de rappel d’envoi ESP-NOW. Dans ce cas, il s’agit de OnDataSent() que nous avons vu précédemment.

esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));

Pairs ESP-NOW

Enregistrez les cartes réceptrices en tant que pairs ESP-NOW.

// Register peers
peerInfo.channel = 0;
peerInfo.encrypt = false;

// Register first peer  
memcpy(peerInfo.peer_addr, board1Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
  Serial.println("Failed to add peer");
  return;
}
// Register second peer  
memcpy(peerInfo.peer_addr, board2Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
  Serial.println("Failed to add peer");
  return;
}

Gérer le serveur Web

Voyons maintenant ce qui se passe lorsque vous accédez au serveur Web ESP32 sur votre navigateur.

Lorsque vous accédez à la root/URL (en tapant l’adresse IP ESP32 dans le navigateur Web), nous enverrons la page HTML rendue avec les états GPIO actuels (fonction renderHTML()).

// Root URL handler
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/html", renderHTML());
});

Les lignes suivantes gèrent ce qui se passe lorsque l’ESP32 reçoit une requête sur le point de terminaison de l’URL /states. Dans ce cas, nous préparons une variable JSON contenant l’état actuel de chaque bouton.

Par exemple, si board1Btn1 est vrai, le JSON renvoyé est :

"b1Btn1":true

La même chose se produit pour les autres variables.

Lorsque la variable JSON est prête, nous envoyons une réponse avec la chaîne JSON.

request->send(200, "application/json", json);

Enfin, nous démarrons le serveur Web.

server.begin();

boucle()

Parce que tout fonctionne de manière asynchrone avec les fonctions de rappel, nous n’avons pas besoin d’ajouter quoi que ce soit à la boucle().

void loop() {
  
}

Test du serveur Web

Téléchargez le code du serveur Web sur la carte contrôleur ESP32. Après le téléchargement, ouvrez le moniteur série à une vitesse de transmission de 115 200 bauds et appuyez sur le bouton ESP32 RST. Son adresse IP sera imprimée.

ESP32 ESP-NOW Expéditeur Arduino IDE Moniteur série Imprimer Adresse IP

Ouvrez un navigateur Web sur votre réseau local (sur votre ordinateur ou smartphone) et saisissez l’adresse IP de l’ESP32. Vous aurez accès à la page Web suivante pour contrôler les GPIO des autres cartes ESP32.

ESP32 ESP-NOW Web Server contrôle plusieurs cartes

Utilisez les interrupteurs à bascule pour contrôler les GPIO des autres cartes ESP32.

Si vous ouvrez une fenêtre Serial Monitor pour chacune de vos cartes, vous verrez le message envoyé et le message délivré sur la carte réceptrice.

ESP32 ESP-NOW Émetteur Récepteur Moniteur série Arduino IDE Exemple de démonstration

Si le message n’est pas transmis avec succès, l’interrupteur à bascule reviendra à son état précédent.

Aperçu du projet de récepteur de carte d'envoi ESP32 ESP-NOW

Vous pouvez regarder la démonstration vidéo rapide suivante.

Conclusion

Dans ce didacticiel, vous avez créé un serveur Web ESP32 qui contrôle d’autres cartes ESP32 à l’aide d’ESP-NOW. Cela démontre à quel point le protocole ESP-NOW est polyvalent. À notre avis, c’est l’un des meilleurs protocoles de communication pour que plusieurs cartes ESP32 communiquent entre elles.

Voici d’autres tutoriels connexes qui pourraient vous plaire :

Apprenez-en plus sur l’ESP32 avec nos ressources :

Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

Youtube video

  • Lot de 2 cartes de développement ESP32 USB C avec puce CH340 NodeMCU WROOM 32, WiFi 2,4 GHz + microcontrôleur Bluetooth
  • ELEGOO 3PCS Carte de Développement ESP-32 Type-C, 2,4 GHz WiFi + Bluetooth