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

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

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

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

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

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

ESP-NOW с ESP8266 NodeMCU: отправка данных на несколько плат (один ко многим) — обзор проекта
  • Одна ESP8266 выступает в роли отправителя;

  • Несколько плат ESP8266 выступают в роли приёмников. Мы протестировали эту схему с двумя платами ESP8266 одновременно. Вы можете добавить больше плат в свою конфигурацию;

  • ESP8266-отправитель получает сообщение о подтверждении, если сообщения были успешно доставлены. Вы знаете, какие платы получили сообщение, а какие нет;

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

В этом руководстве рассматриваются два сценария:

  • отправка одного и того же сообщения на все платы;

  • отправка различного сообщения на каждую плату.

Вам также может быть интересно прочитать: Двусторонняя связь ESP-NOW между платами ESP8266.

Необходимые условия

Мы будем программировать ESP8266 с помощью Arduino IDE, поэтому перед тем, как продолжить работу с этим руководством, у вас должно быть установлено дополнение ESP8266 в вашей Arduino IDE. Следуйте следующему руководству:

Необходимые компоненты

Для выполнения этого руководства вам понадобятся несколько плат ESP8266.

Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

Получение MAC-адреса плат

Для отправки сообщений через ESP-NOW вам нужно знать MAC-адрес плат-приёмников. Каждая плата имеет уникальный MAC-адрес (узнайте, как получить и изменить MAC-адрес 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-адрес должен отобразиться в мониторе последовательного порта (Serial Monitor).

MAC-адрес платы ESP32/ESP8266 NodeMCU в мониторе последовательного порта Arduino IDE

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

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

Следующий код отправляет данные на несколько (две) платы ESP через ESP-NOW. Вам следует изменить код, указав MAC-адреса ваших плат-приёмников. Также следует добавить или удалить строки кода в зависимости от количества плат-приёмников.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-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 broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t broadcastAddress2[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Structure example to send data
// Must match the receiver structure
typedef struct test_struct {
    int x;
    int y;
} test_struct;

// Create a struct_message called test to store variables to be sent
test_struct test;

unsigned long lastTime = 0;
unsigned long timerDelay = 2000;  // send readings timer

// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  char macStr[18];
  Serial.print("Packet to:");
  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: ");
  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;
  }

  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(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
  esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    // Set values to send
    test.x = random(1, 50);
    test.y = random(1, 50);

    // Send message via ESP-NOW
    esp_now_send(0, (uint8_t *) &test, sizeof(test));

    lastTime = millis();
  }
}

Посмотреть исходный код

Как работает код

Сначала подключите библиотеки espnow.h и ESP8266WiFi.h.

#include <ESP8266WiFi.h>
#include <espnow.h>

MAC-адреса приёмников

Вставьте MAC-адреса приёмников. В нашем примере мы отправляем данные на две платы.

uint8_t broadcastAddress1[] = {0x5C, 0xCF, 0x7F, 0x99, 0xA1, 0x70};
uint8_t broadcastAddress2[] = {0x5C, 0xCF, 0x7F, 0x99, 0x9A, 0xEA};

Затем создайте структуру, содержащую данные, которые мы хотим отправить. Мы назвали эту структуру test_struct, и она содержит две целочисленные переменные. Вы можете изменить её для отправки любых типов переменных.

typedef struct test_struct {
  int x;
  int y;
} test_struct;

Создайте новую переменную типа test_struct с именем test, которая будет хранить значения переменных.

test_struct test;

Функция обратного вызова OnDataSent()

Далее определите функцию OnDataSent(). Это функция обратного вызова, которая выполняется при отправке сообщения. В данном случае эта функция выводит информацию о том, было ли сообщение успешно доставлено или нет, и для какого MAC-адреса. Таким образом, вы знаете, какие платы получили сообщение, а какие нет.

void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  char macStr[18];
  Serial.print("Packet to:");
  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: ");
  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;
}

Установите роль ESP8266. Это плата-отправитель, поэтому установите её роль как ESP_NOW_ROLE_CONTROLLER:

esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);

Зарегистрируйте функцию обратного вызова, которая будет вызываться при отправке сообщения. В данном случае зарегистрируйте созданную ранее функцию OnDataSent().

esp_now_register_send_cb(OnDataSent);

Добавление пиров

После этого нам нужно выполнить сопряжение с другими устройствами ESP-NOW для отправки данных. Именно это мы делаем в следующих строках – регистрируем пиры:

esp_now_add_peer(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

Если вы хотите добавить больше пиров, просто продублируйте эти строки и передайте MAC-адрес пира:

loop()

В функции loop() мы отправляем сообщение через ESP-NOW каждые 2 секунды (вы можете изменить это время задержки в переменной timerDelay).

Присвойте значение каждой переменной:

test.x = random(0,20);
test.y = random(0,20);

Помните, что test – это структура. Здесь вы присваиваете значения, которые хотите отправить, внутри структуры. В данном случае мы просто отправляем случайные значения. В практическом приложении их следует заменить командами или показаниями датчиков, например.

Отправка одинаковых данных на несколько плат

Наконец, отправьте сообщение следующим образом:

esp_now_send(0, (uint8_t *) &test, sizeof(test));

Первый аргумент функции esp_now_send() – это MAC-адрес приёмника. Если вы передадите 0 в качестве аргумента, она отправит одно и то же сообщение всем зарегистрированным пирам. Если вы хотите отправить различное сообщение каждому пиру, следуйте следующему разделу.

Проверьте, было ли сообщение успешно отправлено:

Функция loop() выполняется каждые 2000 миллисекунд (2 секунды).

if ((millis() - lastTime) > timerDelay) {
  // Set values to send
  test.x = random(1, 50);
  test.y = random(1, 50);

  // Send message via ESP-NOW
  esp_now_send(0, (uint8_t *) &test, sizeof(test));

  lastTime = millis();
}

Отправка различных данных на каждую плату

Код для отправки различного сообщения на каждую плату очень похож на предыдущий. Поэтому мы рассмотрим только отличия.

Если вы хотите отправить различное сообщение на каждую плату, вам нужно создать структуру данных для каждой из ваших плат, например:

test_struct test;
test_struct test2;

В данном случае мы отправляем один и тот же тип структуры (test_struct). Вы можете отправить структуру другого типа, при условии, что код приёмника подготовлен для получения такого типа структуры.

Затем присвойте различные значения переменным каждой структуры. В этом примере мы просто устанавливаем их в случайные числа.

test.x = random(0,20);
test.y = random(0,20);
test2.x = random(0,20);
test2.y = random(0,20);

Наконец, вам нужно вызвать функцию esp_now_send() для каждого приёмника.

Например, отправьте структуру test на плату, чей MAC-адрес – broadcastAddress1.

esp_now_send(broadcastAddress1, (uint8_t *) &test, sizeof(test));

Сделайте то же самое для других плат. Для второй платы отправьте структуру test2:

esp_now_send(broadcastAddress2, (uint8_t *) &test2, sizeof(test2));

Вот полный код, который отправляет различное сообщение на каждую плату.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-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 broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t broadcastAddress2[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Structure example to send data
// Must match the receiver structure
typedef struct test_struct {
    int x;
    int y;
} test_struct;

// Create a struct_message called test to store variables to be sent
test_struct test;
test_struct test2;

unsigned long lastTime = 0;
unsigned long timerDelay = 2000;  // send readings timer

// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  char macStr[18];
  Serial.print("Packet to:");
  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: ");
  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;
  }

  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(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
  esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    // Set values to send
    test.x = random(1, 50);
    test.y = random(1, 50);
    test2.x = random(1, 50);
    test2.y = random(1, 50);

    // Send message via ESP-NOW
    esp_now_send(broadcastAddress1, (uint8_t *) &test, sizeof(test));
    esp_now_send(broadcastAddress2, (uint8_t *) &test2, sizeof(test2));
    lastTime = millis();
  }
}

Посмотреть исходный код

Код приёмника ESP8266 NodeMCU (ESP-NOW)

Загрузите следующий код на платы-приёмники (в нашем примере мы использовали две платы-приёмника).

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-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 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);
  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() {

}

Посмотреть исходный код

Как работает код

Аналогично отправителю, начните с подключения библиотек:

#include <ESP8266WiFi.h>
#include <espnow.h>

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

typedef struct test_struct {
    int x;
    int y;
} test_struct;

Создайте переменную test_struct с именем myData.

test_struct myData;

Создайте функцию обратного вызова, которая вызывается, когда ESP8266 получает данные через ESP-NOW. Функция называется OnDataRecv() и должна принимать несколько параметров следующим образом:

void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {

Скопируйте содержимое переменной данных incomingData в переменную myData.

memcpy(&myData, incomingData, sizeof(myData));

Теперь структура myData содержит несколько переменных внутри со значениями, отправленными ESP8266-отправителем. Чтобы получить доступ к переменной 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 станции и отключите Wi-Fi.

WiFi.mode(WIFI_STA);
WiFi.disconnect();

Инициализируйте ESP-NOW:

if (esp_now_init() != 0) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

Установите роль ESP8266. Это плата-приёмник, поэтому установите её роль как ESP_NOW_ROLE_SLAVE.

esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);

Зарегистрируйте функцию обратного вызова, которая будет вызываться при получении данных. В данном случае мы регистрируем созданную ранее функцию OnDataRecv().

esp_now_register_recv_cb(OnDataRecv);

Демонстрация

Включив все ваши платы, откройте монитор последовательного порта Arduino IDE для COM-порта, к которому подключён отправитель.

Вы должны начать получать сообщения «Delivery Success» с соответствующим MAC-адресом приёмника в мониторе последовательного порта.

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

Если вы отключите питание одной из плат, вы получите сообщение «Delivery Fail» для этой конкретной платы. Таким образом, вы можете быстро определить, какая плата не получила сообщение.

ESP8266 NodeMCU ESP-NOW — отправка данных на несколько плат, статус доставки Failed в мониторе последовательного порта

Если вы хотите проверить, действительно ли платы получают сообщения, вы можете открыть монитор последовательного порта для COM-порта, к которому они подключены, или вы можете использовать PuTTY для установления последовательной связи с вашими платами.

Если вы используете PuTTY, выберите Serial communication (последовательную связь), введите номер COM-порта и скорость передачи (115200), как показано ниже, и нажмите Open.

Последовательная связь с платами ESP8266 NodeMCU через PuTTY

Затем вы должны увидеть получаемые сообщения.

Откройте последовательное соединение для каждой из ваших плат и убедитесь, что они получают сообщения.

ESP8266 NodeMCU ESP-NOW — отправка данных на несколько плат, успешная демонстрация доставки

Заключение

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

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

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

Спасибо за прочтение.