ESP-NOW с ESP32: Приём данных от нескольких плат (многие-к-одному)

Это руководство показывает, как настроить плату ESP32 для приёма данных от нескольких плат ESP32 через протокол связи ESP-NOW (конфигурация многие-к-одному). Такая конфигурация идеальна, если вы хотите собирать данные с нескольких сенсорных узлов на одну плату ESP32. Платы будут программироваться с использованием Arduino IDE.

ESP-NOW с ESP32: Приём данных от нескольких плат (многие-к-одному) с Arduino IDE

У нас есть другие руководства, связанные с ESP-NOW, которые могут вас заинтересовать:

Обзор проекта

Это руководство показывает, как настроить плату ESP32 для приёма данных от нескольких плат ESP32 через протокол связи ESP-NOW (конфигурация многие-к-одному), как показано на следующем рисунке.

ESP-NOW с ESP32: Приём данных от нескольких плат (многие-к-одному) — обзор проекта
  • Одна плата 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).

Вы можете использовать приведённые выше ссылки или перейти непосредственно на 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).

MAC-адрес платы ESP32 в мониторе порта Arduino IDE

Код отправителя ESP32 (ESP-NOW)

Приёмник может идентифицировать каждого отправителя по его уникальному MAC-адресу. Однако работа с различными MAC-адресами на стороне приёмника для определения, какая плата отправила какое сообщение, может быть сложной.

Поэтому, чтобы упростить задачу, мы будем идентифицировать каждую плату уникальным номером (id), начиная с 1. Если у вас три платы, одна будет иметь ID номер 1, другая — номер 2, и наконец — номер 3. ID будет отправляться на приёмник вместе с другими переменными.

В качестве примера мы будем обмениваться структурой, содержащей номер id платы и два случайных числа x и y, как показано на рисунке ниже.

ESP-NOW с ESP32: Приём структур данных от нескольких плат — пример данных

Загрузите следующий код на каждую из ваших плат-отправителей. Не забудьте увеличивать номер 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. Возможно, вам поможет следующее изображение.

ESP-NOW приём данных от нескольких плат — массив структур

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», если сообщения доставляются корректно.

ESP-NOW отправка пакетов — сообщение об успешной доставке в мониторе порта ESP32

На плате-приёмнике вы должны получать пакеты от всех других плат. В этом тесте мы получали данные от 5 различных плат.

ESP-NOW приём данных от нескольких плат ESP32 в мониторе порта

Заключение

В этом руководстве вы узнали, как настроить 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)