ESP-NOW с ESP32: Приём данных от нескольких плат (многие-к-одному)
Это руководство показывает, как настроить плату ESP32 для приёма данных от нескольких плат ESP32 через протокол связи ESP-NOW (конфигурация многие-к-одному). Такая конфигурация идеальна, если вы хотите собирать данные с нескольких сенсорных узлов на одну плату ESP32. Платы будут программироваться с использованием Arduino IDE.
У нас есть другие руководства, связанные с ESP-NOW, которые могут вас заинтересовать:
Обзор проекта
Это руководство показывает, как настроить плату ESP32 для приёма данных от нескольких плат ESP32 через протокол связи ESP-NOW (конфигурация многие-к-одному), как показано на следующем рисунке.
Одна плата ESP32 выступает в роли приёмника/ведомого (receiver/slave);
Несколько плат ESP32 выступают в роли отправителей/ведущих (senders/masters). Мы протестировали этот пример с 5 платами-отправителями ESP32, и всё работало отлично. Вы можете добавить больше плат в вашу конфигурацию;
Плата-отправитель получает сообщение подтверждения, указывающее, было ли сообщение успешно доставлено или нет;
Плата-приёмник ESP32 получает сообщения от всех отправителей и определяет, какая плата отправила сообщение;
В качестве примера мы будем обмениваться случайными значениями между платами. Вам следует модифицировать этот пример для отправки команд или показаний датчиков (обмен показаниями датчиков с помощью ESP-NOW).
Примечание: в документации ESP-NOW нет такого понятия, как «отправитель/ведущий» и «приёмник/ведомый». Каждая плата может быть как отправителем, так и приёмником. Однако для ясности мы будем использовать термины «отправитель» и «приёмник» или «ведущий» и «ведомый».
Необходимые условия
Мы будем программировать платы ESP32 с помощью Arduino IDE, поэтому перед началом работы с этим руководством убедитесь, что эти платы установлены в вашей Arduino IDE.
Необходимые компоненты
Для выполнения этого руководства вам понадобятся несколько плат ESP32. Все модели ESP32 должны работать. Мы экспериментировали с различными моделями плат ESP32, и все работали хорошо (ESP32 DOIT board, TTGO T-Journal, ESP32 с OLED-дисплеем и ESP32-CAM).
ESP32 (читайте Лучшие платы разработки ESP32)
Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Получение MAC-адреса платы-приёмника
Для отправки сообщений через ESP-NOW вам нужно знать MAC-адрес платы-приёмника. У каждой платы есть уникальный MAC-адрес (узнайте, как получить и изменить MAC-адрес ESP32).
Загрузите следующий код на вашу плату-приёмник 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).
Код отправителя ESP32 (ESP-NOW)
Приёмник может идентифицировать каждого отправителя по его уникальному MAC-адресу. Однако работа с различными MAC-адресами на стороне приёмника для определения, какая плата отправила какое сообщение, может быть сложной.
Поэтому, чтобы упростить задачу, мы будем идентифицировать каждую плату уникальным номером (id), начиная с 1. Если у вас три платы, одна будет иметь ID номер 1, другая — номер 2, и наконец — номер 3. ID будет отправляться на приёмник вместе с другими переменными.
В качестве примера мы будем обмениваться структурой, содержащей номер id платы и два случайных числа x и y, как показано на рисунке ниже.
Загрузите следующий код на каждую из ваших плат-отправителей. Не забудьте увеличивать номер id для каждой платы-отправителя.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-many-to-one-esp32/
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 THE RECEIVER'S MAC Address
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
int id; // must be unique for each sender board
int x;
int y;
} struct_message;
// Create a struct_message called myData
struct_message myData;
// Create peer interface
esp_now_peer_info_t peerInfo;
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
}
void loop() {
// Set values to send
myData.id = 1;
myData.x = random(0,50);
myData.y = random(0,50);
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
delay(10000);
}
Как работает код
Подключите библиотеки WiFi и esp_now.
#include <esp_now.h>
#include <WiFi.h>
Вставьте MAC-адрес приёмника в следующую строку.
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x15, 0xC7, 0xFC};
Затем создайте структуру, содержащую данные, которые мы хотим отправить. Мы назвали эту структуру struct_message, и она содержит три целочисленные переменные: id платы, x и y. Вы можете изменить её для отправки любых типов переменных (но не забудьте изменить это и на стороне приёмника).
typedef struct struct_message {
int id; // must be unique for each sender board
int x;
int y;
} struct_message;
Создайте новую переменную типа struct_message с именем myData, которая будет хранить значения переменных.
struct_message myData;
Создайте переменную типа esp_now_peer_info_t для хранения информации о пире (peer).
esp_now_peer_info_t peerInfo;
Функция обратного вызова OnDataSent()
Далее определите функцию OnDataSent(). Это функция обратного вызова, которая будет выполняться при отправке сообщения. В данном случае эта функция выводит, было ли сообщение успешно доставлено или нет.
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");
}
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);
Добавление пира (peer)
Для отправки данных на другую плату (приёмник) необходимо подключить её как пир (peer). Следующие строки регистрируют и добавляют нового пира.
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
loop()
В функции loop() мы отправляем сообщение через ESP-NOW каждые 10 секунд (вы можете изменить время задержки).
Присвойте значение каждой переменной.
myData.id = 1;
myData.x = random(0,50);
myData.y = random(0,50);
Не забудьте изменить id для каждой платы-отправителя.
Помните, что myData — это структура. Здесь мы присваиваем значения, которые хотим отправить внутри структуры. В данном случае мы просто отправляем id и случайные значения x и y. В практическом применении они должны быть заменены командами или показаниями датчиков, например.
Отправка сообщения ESP-NOW
Наконец, отправьте сообщение через ESP-NOW.
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Код приёмника ESP32 (ESP-NOW)
Загрузите следующий код на вашу плату-приёмник ESP32. Код подготовлен для приёма данных от трёх различных плат. Вы можете легко модифицировать код для приёма данных от другого количества плат.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-many-to-one-esp32/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#include <esp_now.h>
#include <WiFi.h>
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int id;
int x;
int y;
}struct_message;
// Create a struct_message called myData
struct_message myData;
// Create a structure to hold the readings from each board
struct_message board1;
struct_message board2;
struct_message board3;
// Create an array with all the structures
struct_message boardsStruct[3] = {board1, board2, board3};
// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
char macStr[18];
Serial.print("Packet received from: ");
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.println(macStr);
memcpy(&myData, incomingData, sizeof(myData));
Serial.printf("Board ID %u: %u bytes\n", myData.id, len);
// Update the structures with the new incoming data
boardsStruct[myData.id-1].x = myData.x;
boardsStruct[myData.id-1].y = myData.y;
Serial.printf("x value: %d \n", boardsStruct[myData.id-1].x);
Serial.printf("y value: %d \n", boardsStruct[myData.id-1].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() {
// Acess the variables for each board
/*int board1X = boardsStruct[0].x;
int board1Y = boardsStruct[0].y;
int board2X = boardsStruct[1].x;
int board2Y = boardsStruct[1].y;
int board3X = boardsStruct[2].x;
int board3Y = boardsStruct[2].y;*/
delay(10000);
}
Как работает код
Аналогично отправителю, начните с подключения библиотек:
#include <esp_now.h>
#include <WiFi.h>
Создайте структуру для приёма данных. Эта структура должна быть такой же, как определённая в скетче отправителя.
typedef struct struct_message {
int id;
int x;
int y;
} struct_message;
Создайте переменную struct_message с именем myData, которая будет хранить полученные данные.
struct_message myData;
Затем создайте переменную struct_message для каждой платы, чтобы мы могли присвоить полученные данные соответствующей плате. Здесь мы создаём структуры для трёх плат-отправителей. Если у вас больше плат-отправителей, вам нужно создать больше структур.
struct_message board1;
struct_message board2;
struct_message board3;
Создайте массив, содержащий все структуры плат. Если вы используете другое количество плат, вам нужно это изменить.
struct_message boardsStruct[3] = {board1, board2, board3};
onDataRecv()
Создайте функцию обратного вызова, которая вызывается, когда ESP32 получает данные через ESP-NOW. Функция называется onDataRecv() и должна принимать несколько параметров следующим образом:
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Получите MAC-адрес платы:
char macStr[18];
Serial.print("Packet received from: ");
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.println(macStr);
Скопируйте содержимое переменной incomingData в переменную myData.
memcpy(&myData, incomingData, sizeof(myData));
Теперь структура myData содержит несколько переменных со значениями, отправленными одним из отправителей ESP32. Мы можем определить, какая плата отправила пакет, по её ID: myData.id.
Таким образом, мы можем присвоить полученные значения соответствующим платам в массиве boardsStruct:
boardsStruct[myData.id-1].x = myData.x;
boardsStruct[myData.id-1].y = myData.y;
Например, представьте, что вы получили пакет от платы с id 2. Значение myData.id равно 2.
Значит, вы хотите обновить значения структуры board2. Структура board2 — это элемент с индексом 1 в массиве boardsStruct. Поэтому мы вычитаем 1, потому что массивы в C имеют индексацию с 0. Возможно, вам поможет следующее изображение.
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;
}
Зарегистрируйте функцию обратного вызова, которая будет вызываться при получении данных. В данном случае мы регистрируем ранее созданную функцию OnDataRecv().
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
Следующие строки, закомментированные в loop(), показывают, что нужно сделать, если вы хотите получить доступ к переменным каждой структуры платы. Например, для доступа к значению x платы board1:
int board1X = boardsStruct[0].x;
Демонстрация
Загрузите код отправителя на каждую из ваших плат-отправителей. Не забудьте присвоить каждой плате свой уникальный ID.
Загрузите код приёмника на плату-приёмник ESP32. Не забудьте изменить структуру в соответствии с количеством плат-отправителей.
В мониторе порта отправителей вы должны увидеть сообщение «Delivery Success», если сообщения доставляются корректно.
На плате-приёмнике вы должны получать пакеты от всех других плат. В этом тесте мы получали данные от 5 различных плат.
Заключение
В этом руководстве вы узнали, как настроить ESP32 для приёма данных от нескольких плат ESP32 с помощью ESP-NOW (конфигурация многие-к-одному).
В качестве примера мы обменивались случайными числами. В реальном приложении они могут быть заменены фактическими показаниями датчиков или командами. Это идеально подходит, если вы хотите собирать данные с нескольких сенсорных узлов. Вы можете развить этот проект и создать веб-сервер на плате-приёмнике для отображения полученных сообщений.
Для одновременного использования Wi-Fi для создания веб-сервера и ESP-NOW необходимо настроить ESP32 одновременно как Wi-Fi станцию и точку доступа. Кроме того, необходимо установить разные каналы Wi-Fi: один для ESP-NOW и другой для станции. Вы можете следовать следующему руководству:
У нас есть другие руководства, связанные с ESP-NOW, которые могут вам понравиться:
Узнайте больше об ESP32 из наших ресурсов:
Источник: ESP-NOW with ESP32: Receive Data from Multiple Boards (many-to-one)