ESP-NOW с ESP32: отправка данных на несколько плат (один-ко-многим)
В этом руководстве вы узнаете, как использовать протокол связи ESP-NOW для отправки данных с одной платы ESP32 на несколько плат ESP32 или ESP8266 (конфигурация один-ко-многим). Платы будут программироваться с помощью Arduino IDE.
Чтобы начать работу с ESP-NOW на ESP32 или ESP8266, сначала прочитайте следующие руководства по ESP-NOW:
Обзор проекта
В этом руководстве показано, как отправлять данные с одной платы ESP32 на несколько плат ESP32 или ESP8266 с помощью ESP-NOW (конфигурация один-ко-многим).
Одна плата ESP32 выступает в роли отправителя;
Несколько плат ESP32 или ESP8266 выступают в роли приёмников. Мы протестировали эту конфигурацию с двумя платами ESP32 и одной платой ESP8266 одновременно. Вы можете добавить больше плат в свою конфигурацию;
ESP32-отправитель получает подтверждение, если сообщения были успешно доставлены. Вы знаете, какие платы получили сообщение, а какие нет;
Вам нужно загрузить немного отличающийся код приёмника в зависимости от того, используете ли вы ESP32 или ESP8266;
В качестве примера мы будем обмениваться случайными значениями между платами. Вы должны модифицировать этот пример для отправки команд или показаний датчиков (обмен показаниями датчиков с помощью ESP-NOW).
В этом руководстве рассматриваются два сценария:
отправка одного и того же сообщения на все платы;
отправка разных сообщений на каждую плату.
Вам также может быть интересно: Двусторонняя связь ESP-NOW между платами ESP32.
Предварительные требования
Мы будем программировать платы ESP32/ESP8266 с помощью Arduino IDE, поэтому перед тем как продолжить, убедитесь, что эти платы установлены в вашей Arduino IDE.
Необходимые компоненты
Для выполнения этого руководства вам понадобятся несколько плат ESP32 и/или ESP8266.
ESP32 (читайте Лучшие платы разработки ESP32)
ESP8266 (читайте Лучшие платы разработки ESP8266)
Вы можете использовать ссылки выше или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Получение MAC-адреса плат
Для отправки сообщений через ESP-NOW вам необходимо знать MAC-адрес плат-приёмников. Каждая плата имеет уникальный MAC-адрес (узнайте, как получить и изменить MAC-адрес ESP32).
Загрузите следующий код на каждую из ваших плат-приёмников, чтобы получить её MAC-адрес.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.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.
*/
#ifdef ESP32
#include <WiFi.h>
#include <esp_wifi.h>
#else
#include <ESP8266WiFi.h>
#endif
void setup(){
Serial.begin(115200);
Serial.print("ESP Board MAC Address: ");
#ifdef ESP32
WiFi.mode(WIFI_STA);
WiFi.STA.begin();
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");
}
#else
Serial.println(WiFi.macAddress());
#endif
}
void loop(){
}
После загрузки кода нажмите кнопку RST/EN, и MAC-адрес должен отобразиться в мониторе порта (Serial Monitor).
Вы можете записать MAC-адреса плат на наклейке, чтобы чётко идентифицировать каждую плату.
Код отправителя ESP32 (ESP-NOW)
Следующий код отправляет данные на несколько (три) плат ESP через ESP-NOW. Вы должны изменить код, указав MAC-адреса ваших плат-приёмников. Также добавьте или удалите строки кода в зависимости от количества плат-приёмников.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp32-esp8266/
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 ESP RECEIVER'S MAC ADDRESS
uint8_t broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t broadcastAddress2[] = {0xFF, , , , , };
uint8_t broadcastAddress3[] = {0xFF, , , , , };
typedef struct test_struct {
int x;
int y;
} test_struct;
test_struct test;
esp_now_peer_info_t peerInfo;
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
Serial.print("Packet to: ");
// Copies the sender mac address to a string
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);
Serial.print(" send status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
// register peer
peerInfo.channel = 0;
peerInfo.encrypt = false;
// register first peer
memcpy(peerInfo.peer_addr, broadcastAddress1, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// register second peer
memcpy(peerInfo.peer_addr, broadcastAddress2, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
/// register third peer
memcpy(peerInfo.peer_addr, broadcastAddress3, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
}
void loop() {
test.x = random(0,20);
test.y = random(0,20);
esp_err_t result = esp_now_send(0, (uint8_t *) &test, sizeof(test_struct));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(2000);
}
Как работает код
Сначала подключите библиотеки esp_now.h и WiFi.h.
#include <esp_now.h>
#include <WiFi.h>
MAC-адреса приёмников
Вставьте MAC-адреса приёмников. В нашем примере мы отправляем данные на три платы.
uint8_t broadcastAddress1[] = {0x3C, 0x71, 0xBF, 0xC3, 0xBF, 0xB0};
uint8_t broadcastAddress2[] = {0x24, 0x0A, 0xC4, 0xAE, 0xAE, 0x44};
uint8_t broadcastAddress3[] = {0x80, 0x7D, 0x3A, 0x58, 0xB4, 0xB0};
Затем создайте структуру, содержащую данные, которые мы хотим отправить. Мы назвали эту структуру test_struct, и она содержит две целочисленные переменные. Вы можете изменить её для отправки любых типов переменных.
typedef struct test_struct {
int x;
int y;
} test_struct;
Создайте новую переменную типа test_struct с именем test, которая будет хранить значения переменных.
test_struct test;
Создайте переменную типа esp_now_peer_info_t для хранения информации о пире (peer).
esp_now_peer_info_t peerInfo;
Функция обратного вызова OnDataSent()
Далее определите функцию OnDataSent(). Это функция обратного вызова, которая будет выполняться при отправке сообщения. В данном случае эта функция выводит информацию о том, было ли сообщение успешно доставлено или нет и для какого MAC-адреса. Таким образом, вы знаете, какие платы получили сообщение, а какие нет.
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
Serial.print("Packet from: ");
// Copies the sender mac address to a string
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);
Serial.print(" send status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
setup()
В setup() инициализируйте монитор порта для отладки:
Serial.begin(115200);
Установите устройство в режим Wi-Fi станции:
WiFi.mode(WIFI_STA);
Инициализируйте ESP-NOW:
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
После успешной инициализации ESP-NOW зарегистрируйте функцию обратного вызова, которая будет вызываться при отправке сообщения. В данном случае зарегистрируйте ранее созданную функцию OnDataSent().
esp_now_register_send_cb(OnDataSent);
Добавление пиров (peers)
После этого нам нужно выполнить сопряжение с другими устройствами ESP-NOW для отправки данных. Именно это мы делаем в следующих строках – регистрируем пиров:
// register peer
peerInfo.channel = 0;
peerInfo.encrypt = false;
// register first peer
memcpy(peerInfo.peer_addr, broadcastAddress1, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// register second peer
memcpy(peerInfo.peer_addr, broadcastAddress2, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
/// register third peer
memcpy(peerInfo.peer_addr, broadcastAddress3, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
Если вы хотите добавить больше пиров, просто продублируйте эти строки и передайте MAC-адрес пира:
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
loop()
В loop() мы будем отправлять сообщение через ESP-NOW каждые 2 секунды (вы можете изменить это время задержки).
Присвойте значение каждой переменной:
test.x = random(0,20);
test.y = random(0,20);
Помните, что test – это структура. Здесь присвойте значения, которые вы хотите отправить, внутри структуры. В данном случае мы просто отправляем случайные значения. В реальном приложении их следует заменить командами или показаниями датчиков, например.
Отправка одинаковых данных на несколько плат
Наконец, отправьте сообщение следующим образом:
esp_err_t result = esp_now_send(0, (uint8_t *) &test, sizeof(test_struct));
Первый аргумент функции esp_now_send() – это MAC-адрес приёмника. Если вы передадите 0 в качестве аргумента, она отправит одно и то же сообщение всем зарегистрированным пирам. Если вы хотите отправить разное сообщение каждому пиру, следуйте инструкциям в следующем разделе.
Проверьте, было ли сообщение успешно отправлено:
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Цикл loop() выполняется каждые 2000 миллисекунд (2 секунды).
delay(2000);
Отправка разных данных на каждую плату
Код для отправки разных сообщений на каждую плату очень похож на предыдущий. Поэтому мы рассмотрим только отличия.
Если вы хотите отправить разное сообщение на каждую плату, вам нужно создать структуру данных для каждой из ваших плат, например:
test_struct test;
test_struct test2;
test_struct test3;
В данном случае мы отправляем один и тот же тип структуры (test_struct). Вы можете отправлять разные типы структур, при условии что код приёмника подготовлен для получения этого типа структуры.
Затем присвойте разные значения переменным каждой структуры. В этом примере мы просто устанавливаем их в случайные числа.
test.x = random(0,20);
test.y = random(0,20);
test2.x = random(0,20);
test2.y = random(0,20);
test3.x = random(0,20);
test3.y = random(0,20);
Наконец, вам нужно вызвать функцию esp_now_send() для каждого приёмника.
Например, отправьте структуру test на плату с MAC-адресом broadcastAddress1.
esp_err_t result1 = esp_now_send(
broadcastAddress1,
(uint8_t *) &test,
sizeof(test_struct));
if (result1 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Сделайте то же самое для остальных плат. Для второй платы отправьте структуру test2:
esp_err_t result2 = esp_now_send(
broadcastAddress2,
(uint8_t *) &test2,
sizeof(test_struct));
if (result2 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
И наконец, для третьей платы отправьте структуру test3:
esp_err_t result3 = esp_now_send(
broadcastAddress3,
(uint8_t *) &test3,
sizeof(test_struct));
if (result3 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Вот полный код, который отправляет разные сообщения на каждую плату.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp32-esp8266/
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 ESP RECEIVER'S MAC ADDRESS
uint8_t broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t broadcastAddress2[] = {0xFF, , , , , };
uint8_t broadcastAddress3[] = {0xFF, , , , , };
typedef struct test_struct {
int x;
int y;
} test_struct;
esp_now_peer_info_t peerInfo;
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
Serial.print("Packet to: ");
// Copies the sender mac address to a string
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);
Serial.print(" send status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
// register peer
peerInfo.channel = 0;
peerInfo.encrypt = false;
memcpy(peerInfo.peer_addr, broadcastAddress1, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
memcpy(peerInfo.peer_addr, broadcastAddress2, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
memcpy(peerInfo.peer_addr, broadcastAddress3, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
}
void loop() {
test_struct test;
test_struct test2;
test_struct test3;
test.x = random(0,20);
test.y = random(0,20);
test2.x = random(0,20);
test2.y = random(0,20);
test3.x = random(0,20);
test3.y = random(0,20);
esp_err_t result1 = esp_now_send(
broadcastAddress1,
(uint8_t *) &test,
sizeof(test_struct));
if (result1 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(500);
esp_err_t result2 = esp_now_send(
broadcastAddress2,
(uint8_t *) &test2,
sizeof(test_struct));
if (result2 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(500);
esp_err_t result3 = esp_now_send(
broadcastAddress3,
(uint8_t *) &test3,
sizeof(test_struct));
if (result3 == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(2000);
}
Код приёмника ESP32 (ESP-NOW)
Загрузите следующий код на платы-приёмники (в нашем примере мы использовали три платы-приёмника).
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp32-esp8266/
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 test_struct {
int x;
int y;
} test_struct;
//Create a struct_message called myData
test_struct 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("x: ");
Serial.println(myData.x);
Serial.print("y: ");
Serial.println(myData.y);
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(esp_now_recv_cb_t(OnDataRecv));
}
void loop() {
}
Как работает код
Аналогично отправителю, начните с подключения библиотек:
#include <esp_now.h>
#include <WiFi.h>
Создайте структуру для получения данных. Эта структура должна быть такой же, как определённая в скетче отправителя.
typedef struct test_struct {
int x;
int y;
} test_struct;
Создайте переменную test_struct с именем myData.
test_struct myData;
Создайте функцию обратного вызова, которая вызывается, когда ESP32 получает данные через ESP-NOW. Функция называется OnDataRecv() и должна принимать несколько параметров следующим образом:
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Скопируйте содержимое переменной incomingData в переменную myData.
memcpy(&myData, incomingData, sizeof(myData));
Теперь структура myData содержит несколько переменных со значениями, отправленными ESP32-отправителем. Чтобы получить доступ к переменной x, например, вызовите myData.x.
В этом примере мы выводим полученные данные, но в реальном приложении вы можете отобразить данные на OLED-дисплее, например.
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("x: ");
Serial.println(myData.x);
Serial.print("y: ");
Serial.println(myData.y);
Serial.println();
В setup() инициализируйте монитор порта.
Serial.begin(115200);
Установите устройство в режим Wi-Fi станции.
WiFi.mode(WIFI_STA);
Инициализируйте ESP-NOW:
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Зарегистрируйте функцию обратного вызова, которая будет вызываться при получении данных. В данном случае мы регистрируем ранее созданную функцию OnDataRecv().
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
Код приёмника ESP8266 (ESP-NOW)
Если вы используете плату ESP8266 в качестве приёмника, загрузите вместо этого следующий код.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp32-esp8266/
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 <ESP8266WiFi.h>
#include <espnow.h>
//Structure example to receive data
//Must match the sender structure
typedef struct test_struct {
int x;
int y;
} test_struct;
//Create a struct_message called myData
test_struct myData;
//callback function that will be executed when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
memcpy(&myData, incomingData, sizeof(myData));
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("x: ");
Serial.println(myData.x);
Serial.print("y: ");
Serial.println(myData.y);
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() != 0) {
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_set_self_role(ESP_NOW_ROLE_SLAVE);
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
}
За исключением небольших деталей, этот код очень похож на код приёмника ESP32. Поэтому мы не будем объяснять, как он работает. Чтобы узнать больше, вы можете прочитать наше Руководство по началу работы с ESP-NOW для ESP8266 NodeMCU.
Демонстрация
Включив все ваши платы, откройте монитор порта Arduino IDE для COM-порта, к которому подключён отправитель.
Вы должны начать получать сообщения «Delivery Success» с соответствующим MAC-адресом приёмника в мониторе порта.
Если вы отключите питание одной из плат, вы получите сообщение «Delivery Fail» для этой конкретной платы. Таким образом, вы можете быстро определить, какая плата не получила сообщение.
Если вы хотите проверить, действительно ли платы получают сообщения, вы можете открыть монитор порта для COM-порта, к которому они подключены, или использовать PuTTY для установления последовательной связи с вашими платами.
Если вы используете PuTTY, выберите последовательную связь (Serial), укажите номер COM-порта и скорость передачи (115200), как показано ниже, и нажмите Open.
Затем вы должны увидеть получаемые сообщения.
Откройте последовательную связь для каждой из ваших плат и убедитесь, что они получают сообщения.
Заключение
В этом руководстве вы узнали, как отправлять данные на несколько плат ESP32 или ESP8266 с одной платы ESP32, используя ESP-NOW (связь один-ко-многим).
Надеемся, что это руководство оказалось для вас полезным. У нас есть и другие руководства по ESP-NOW, которые могут вас заинтересовать:
Двусторонняя связь ESP-NOW между платами ESP32 (показания датчиков)
ESP-NOW с ESP32: получение данных от нескольких плат (многие-к-одному)
Узнайте больше об ESP32 с помощью наших ресурсов:
Спасибо за чтение.
Источник: ESP-NOW with ESP32: Send Data to Multiple Boards (one-to-many)