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

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

ESP-NOW с ESP8266 NodeMCU прием данных от нескольких плат много-к-одному Arduino IDE

Другие руководства по ESP-NOW с ESP8266:

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

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

Обзор проекта ESP-NOW с ESP8266 NodeMCU прием данных от нескольких плат конфигурация много-к-одному
  • Одна плата 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-адрес должен отобразиться в мониторе последовательного порта.

Получение MAC-адреса платы-приемника ESP8266 NodeMCU в мониторе последовательного порта

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

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

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

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

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

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

Структура данных ESP-NOW ESP8266 NodeMCU массив

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», если сообщения были успешно доставлены.

ESP-NOW отправка данных от нескольких плат на один ESP8266 NodeMCU монитор последовательного порта приемника Arduino IDE

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

ESP-NOW прием данных от нескольких плат ESP8266 NodeMCU Arduino IDE монитор последовательного порта

Заключение

В этом руководстве вы узнали, как настроить ESP8266 для приема данных от нескольких плат ESP8266 с использованием ESP-NOW (конфигурация «много-к-одному»). Вы можете сделать аналогичное с платами ESP32 (ESP32 ESP-NOW: много-к-одному).

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

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

Узнайте больше о ESP8266 с нашими ресурсами: