ESP-NOW с ESP8266: прием данных от нескольких плат (много-к-одному)
Это руководство показывает, как настроить плату ESP8266 для приема данных от нескольких плат ESP8266 через протокол связи ESP-NOW (конфигурация «много-к-одному»). Эта конфигурация идеально подходит, если вы хотите собирать данные с нескольких сенсорных узлов на одну плату ESP8266. Платы будут программироваться с использованием Arduino IDE.
Другие руководства по ESP-NOW с ESP8266:
Обзор проекта
Это руководство показывает, как настроить плату ESP8266 для приема данных от нескольких плат ESP8266 через протокол связи ESP-NOW (конфигурация «много-к-одному»), как показано на следующем рисунке.
Одна плата ESP8266 выступает в роли приемника/ведомого (receiver/slave);
Несколько плат ESP8266 выступают в роли отправителей/ведущих (senders/masters). В качестве примера мы будем использовать два отправителя. Вы сможете добавить больше плат в свою систему;
Плата-отправитель получает сообщение-подтверждение, указывающее, было ли сообщение успешно доставлено или нет;
Плата-приемник ESP8266 получает сообщения от всех отправителей и определяет, какая плата отправила сообщение;
В качестве примера мы будем обмениваться случайными значениями между платами. Вы должны модифицировать этот пример для отправки команд или показаний датчиков.
Примечание: в документации ESP-NOW нет такого понятия, как «отправитель/ведущий» и «приемник/ведомый». Каждая плата может быть отправителем или приемником. Однако, чтобы было понятнее, мы будем использовать термины «отправитель» и «приемник» или «ведущий» (master) и «ведомый» (slave).
Необходимые условия
Мы будем программировать платы ESP8266 с использованием Arduino IDE, поэтому перед тем, как продолжить это руководство, убедитесь, что у вас установлены эти платы в вашей Arduino IDE.
Получение MAC-адреса платы-приемника
Для отправки сообщений через ESP-NOW вам необходимо знать MAC-адрес платы-приемника. Каждая плата имеет уникальный MAC-адрес (узнайте, как получить и изменить MAC-адрес ESP8266).
Загрузите следующий код на вашу плату-приемник ESP8266, чтобы получить ее 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-адрес должен отобразиться в мониторе последовательного порта.
Код отправителя ESP8266 NodeMCU (ESP-NOW)
Приемник может идентифицировать каждого отправителя по его уникальному MAC-адресу. Однако работа с различными MAC-адресами на стороне приемника для определения того, какая плата отправила какое сообщение, может быть немного сложной.
Поэтому, чтобы упростить задачу, мы будем идентифицировать каждую плату уникальным номером (id), начиная с 1. Если у вас три платы, одна будет иметь ID номер 1, другая — номер 2 и, наконец, номер 3. ID будет отправляться на приемник вместе с другими переменными.
В качестве примера мы будем обмениваться структурой, содержащей номер id платы и два случайных числа x и y, как показано на рисунке ниже.
Загрузите следующий код на каждую из ваших плат-отправителей. Не забудьте увеличить номер id для каждой платы-отправителя.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp-now-many-to-one-esp8266-nodemcu/
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>
// REPLACE WITH RECEIVER MAC Address
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 2
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
int id;
int x;
int y;
} struct_message;
// Create a struct_message called test to store variables to be sent
struct_message myData;
unsigned long lastTime = 0;
unsigned long timerDelay = 10000;
// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("\r\nLast Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
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_CONTROLLER);
// Once ESPNow is successfully init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(OnDataSent);
// Register peer
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
}
void loop() {
if ((millis() - lastTime) > timerDelay) {
// Set values to send
myData.id = BOARD_ID;
myData.x = random(1, 50);
myData.y = random(1, 50);
// Send message via ESP-NOW
esp_now_send(0, (uint8_t *) &myData, sizeof(myData));
lastTime = millis();
}
}
Как работает код
Подключите библиотеки ESP8266WiFi и espnow.
#include <ESP8266WiFi.h>
#include <espnow.h>
Вставьте MAC-адрес приемника в следующую строку.
uint8_t broadcastAddress[] = {0x5C, 0xCF, 0x7F, 0x99, 0xA1, 0x70};
Установите ID платы в переменной BOARD_ID. Не забудьте изменить эту переменную для каждой из ваших плат-отправителей.
// Set your Board ID (ESP8266 Sender #1 = BOARD_ID 1, ESP8266 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 2
Затем создайте структуру, содержащую данные, которые мы хотим отправить. Мы назвали эту структуру struct_message, и она содержит три целочисленные переменные: board id, x и y. Вы можете изменить это для отправки любых типов переменных (но не забудьте изменить это и на стороне приемника тоже).
typedef struct struct_message {
int id;
int x;
int y;
} struct_message;
Создайте новую переменную типа struct_message с именем myData, которая будет хранить значения переменных.
struct_message myData;
Callback-функция OnDataSent()
Далее определите функцию OnDataSent(). Это callback-функция, которая будет выполняться при отправке сообщения. В данном случае эта функция выводит, было ли сообщение успешно доставлено или нет.
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("\r\nLast Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
setup()
В setup() инициализируйте монитор последовательного порта для целей отладки:
Serial.begin(115200);
Установите устройство как Wi-Fi станцию и отключите Wi-Fi:
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Инициализируйте ESP-NOW:
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
Установите роль платы. Это плата-отправитель, поэтому мы установим ее как ESP_NOW_ROLE_CONTROLLER.
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
После успешной инициализации ESP-NOW зарегистрируйте callback-функцию, которая будет вызываться при отправке сообщения. В данном случае зарегистрируйте ранее созданную функцию OnDataSent().
esp_now_register_send_cb(OnDataSent);
Добавление однорангового устройства
Для отправки данных на другую плату (приемник) необходимо добавить ее в качестве однорангового устройства (peer). Следующая строка регистрирует новое одноранговое устройство.
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
Функция esp_now_add_peer() принимает следующие аргументы в указанном порядке: MAC-адрес, роль однорангового устройства, канал Wi-Fi, ключ и длина ключа.
loop()
В loop() мы будем отправлять сообщение через ESP-NOW каждые 10 секунд (вы можете изменить эту задержку в переменной timerDelay).
Присвойте значение каждой переменной.
myData.id = BOARD_ID;
myData.x = random(1, 50);
myData.y = random(1, 50);
Не забудьте изменить id для каждой платы-отправителя.
Помните, что myData — это структура. Здесь мы присваиваем значения, которые хотим отправить, внутри структуры. В данном случае мы просто отправляем id и случайные значения x и y. В практическом применении их следует заменить командами или показаниями датчиков, например.
Отправка сообщения ESP-NOW
Наконец, отправьте сообщение через ESP-NOW.
esp_now_send(0, (uint8_t *) &myData, sizeof(myData));
Код приемника ESP8266 NodeMCU (ESP-NOW)
Загрузите следующий код на вашу плату-приемник ESP8266. Код подготовлен для приема данных от двух разных плат. Вы можете легко модифицировать код для приема данных от другого количества плат.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp-now-many-to-one-esp8266-nodemcu/
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 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;
// Create an array with all the structures
struct_message boardsStruct[2] = {board1, board2};
// Callback function that will be executed when data is received
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t 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);
WiFi.disconnect();
// 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(){
// Access 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;
*/
}
Как работает код
Аналогично отправителю, начните с подключения библиотек:
#include <ESP8266WiFi.h>
#include <espnow.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 boardsStruct[2] = {board1, board2};
onDataRecv()
Создайте callback-функцию, которая вызывается, когда ESP8266 получает данные через ESP-NOW. Функция называется onDataRecv() и должна принимать несколько параметров следующим образом:
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
Получите MAC-адрес платы-отправителя:
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 содержит несколько переменных со значениями, отправленными одним из отправителей ESP8266. Мы можем определить, какая плата отправила пакет, по ее 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 станцию и отключите Wi-Fi.
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Инициализируйте ESP-NOW:
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Установите роль платы. Это плата-приемник, поэтому мы установим ее как ESP_NOW_ROLE_SLAVE.
Зарегистрируйте callback-функцию, которая будет вызываться при получении данных. В данном случае мы регистрируем ранее созданную функцию OnDataRecv().
esp_now_register_recv_cb(OnDataRecv);
Следующие строки, закомментированные в loop(), показывают, что нужно сделать, если вы хотите получить доступ к переменным каждой структуры платы. Например, чтобы получить доступ к значению x платы board1:
int board1X = boardsStruct[0].x;
Демонстрация
Загрузите код отправителя на каждую из ваших плат-отправителей. Не забудьте назначить каждой плате свой ID.
Загрузите код приемника на плату-приемник ESP8266. Не забудьте модифицировать структуру в соответствии с количеством плат-отправителей.
В мониторе последовательного порта отправителей вы должны получить сообщение «Delivery Success», если сообщения были успешно доставлены.
На плате-приемнике вы должны получать пакеты от всех остальных плат. В этом тесте мы получали данные от 2 разных плат.
Заключение
В этом руководстве вы узнали, как настроить ESP8266 для приема данных от нескольких плат ESP8266 с использованием ESP-NOW (конфигурация «много-к-одному»). Вы можете сделать аналогичное с платами ESP32 (ESP32 ESP-NOW: много-к-одному).
В качестве примера мы обменивались случайными числами. В практическом применении их следует заменить реальными показаниями датчиков или командами. Это идеально подходит, если вы хотите собирать данные с нескольких сенсорных узлов.
У нас есть другие руководства, связанные с ESP-NOW, которые могут вам понравиться:
Узнайте больше о ESP8266 с нашими ресурсами: