Начало работы с ESP-NOW (ESP8266 NodeMCU с Arduino IDE)

В этой статье мы покажем вам, как использовать ESP-NOW для обмена данными между платами ESP8266 NodeMCU, программируемыми в Arduino IDE. ESP-NOW — это протокол связи без установления соединения, разработанный Espressif, который обеспечивает передачу коротких пакетов и может использоваться с платами ESP8266 и ESP32.

Начало работы с ESP-NOW ESP8266 NodeMCU с Arduino IDE

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

Arduino IDE

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

Примечание: у нас есть аналогичное руководство для ESP32: Начало работы с ESP-NOW (ESP32 с Arduino IDE).

Знакомство с ESP-NOW

В следующем видео представлено введение в ESP-NOW. Это видео было записано для ESP32, но большинство концепций также применимы к плате ESP8266 NodeMCU.

Цитируя сайт Espressif, ESP-NOW — это «протокол, разработанный Espressif, который позволяет нескольким устройствам связываться друг с другом без использования Wi-Fi. Протокол аналогичен маломощному беспроводному подключению 2,4 ГГц (…). Перед началом обмена данными необходимо выполнить сопряжение между устройствами. После завершения сопряжения соединение является безопасным и одноранговым (peer-to-peer), при этом подтверждение связи (handshake) не требуется».

ESP-NOW - логотип ESP32

Это означает, что после сопряжения устройства друг с другом соединение является постоянным. Другими словами, если одна из ваших плат внезапно потеряет питание или перезагрузится, при перезапуске она автоматически подключится к своему партнёру для продолжения связи.

ESP-NOW поддерживает следующие возможности:

  • Зашифрованная и незашифрованная одноадресная (unicast) связь;

  • Смешанные зашифрованные и незашифрованные устройства-партнёры;

  • До 250 байт полезной нагрузки;

  • Функция обратного вызова при отправке, которая может быть настроена для информирования прикладного уровня об успешной или неудачной передаче.

Технология ESP-NOW также имеет следующие ограничения:

  • Ограниченное количество зашифрованных партнёров. В режиме Station поддерживается не более 10 зашифрованных партнёров; в режиме SoftAP или SoftAP + Station — не более 6;

  • Поддерживается несколько незашифрованных партнёров, однако их общее количество должно быть менее 20, включая зашифрованных партнёров;

  • Полезная нагрузка ограничена 250 байтами.

Простыми словами, ESP-NOW — это быстрый протокол связи, который можно использовать для обмена небольшими сообщениями (до 250 байт) между платами ESP8266.

ESP-NOW очень универсален, и вы можете организовать одностороннюю или двустороннюю связь в различных конфигурациях.

Односторонняя связь ESP-NOW

Например, при односторонней связи возможны следующие сценарии:

  • Одна плата ESP8266 отправляет данные другой плате ESP8266

ESP8266 ESP-NOW Односторонняя связь

Эта конфигурация очень проста в реализации и отлично подходит для отправки данных от одной платы к другой, таких как показания датчиков или команды включения/выключения для управления GPIO.

  • «Мастер» ESP8266 отправляет данные нескольким «подчинённым» ESP8266

Одна плата ESP8266 отправляет одинаковые или различные команды разным платам ESP8266. Эта конфигурация идеально подходит для создания чего-то вроде пульта дистанционного управления. У вас может быть несколько плат ESP8266 по всему дому, которые управляются одной основной платой ESP8266.

ESP-NOW мастер ESP8266 отправляет данные нескольким подчинённым ESP8266
  • Один «подчинённый» ESP8266 принимает данные от нескольких «мастеров»

Эта конфигурация идеально подходит, если вы хотите собирать данные от нескольких сенсорных узлов на одной плате ESP8266. Её можно настроить как веб-сервер для отображения данных со всех остальных плат, например.

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

Примечание: в документации ESP-NOW нет понятий «отправитель/мастер» и «приёмник/подчинённый». Каждая плата может быть отправителем или приёмником. Однако для ясности мы будем использовать термины «отправитель» и «приёмник» или «мастер» и «подчинённый».

Двусторонняя связь ESP-NOW

При использовании ESP-NOW каждая плата может быть одновременно отправителем и приёмником. Таким образом, вы можете установить двустороннюю связь между платами.

Например, у вас могут быть две платы, обменивающиеся данными друг с другом.

ESP8266 ESP-NOW Двусторонняя связь

Вы можете добавить больше плат в эту конфигурацию и получить нечто похожее на сеть (все платы ESP8266 обмениваются данными друг с другом).

ESP-NOW двусторонняя связь между несколькими платами ESP8266

Подводя итог, ESP-NOW идеально подходит для построения сети, в которой несколько плат ESP8266 могут обмениваться данными друг с другом.

ESP8266: получение MAC-адреса платы

Для связи через ESP-NOW вам необходимо знать MAC-адрес ESP8266-приёмника. Именно так вы определяете, на какое устройство отправлять информацию.

У каждого ESP8266 есть уникальный MAC-адрес, и именно так мы идентифицируем каждую плату для отправки данных через ESP-NOW (узнайте, как получить и изменить 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.
*/
#include <ESP8266WiFi.h>

void setup(){
  Serial.begin(115200);
  Serial.println();
  Serial.print("ESP Board MAC Address:  ");
  Serial.println(WiFi.macAddress());
}

void loop(){

}

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

После загрузки кода откройте монитор последовательного порта на скорости 115200 бод и нажмите кнопку RESET на ESP8266. MAC-адрес должен быть выведен следующим образом:

ESP-NOW ESP32 получение MAC-адреса платы

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

Односторонняя связь ESP-NOW «точка-точка» с ESP8266

Чтобы начать работу с беспроводной связью ESP-NOW, мы создадим простой проект, который показывает, как отправить сообщение от одной ESP8266 к другой. Одна ESP8266 будет выступать в роли «отправителя», а другая — в роли «приёмника».

Пример односторонней связи ESP8266 ESP-NOW с Arduino IDE

Мы отправим структуру, содержащую переменные типа char, int, float, String и boolean. Затем вы можете изменить структуру для отправки любых типов переменных, подходящих для вашего проекта (например, показания датчиков или логические переменные для включения/выключения чего-либо).

Для лучшего понимания мы будем называть ESP8266 #1 «отправителем», а ESP8266 #2 — «приёмником».

Вот что должно быть включено в скетч отправителя:

  1. Инициализировать ESP-NOW;

  2. Зарегистрировать функцию обратного вызова при отправке данных — функция OnDataSent будет выполнена при отправке сообщения. Она может сообщить нам, было ли сообщение успешно доставлено или нет;

  3. Добавить устройство-партнёр (приёмник). Для этого необходимо знать MAC-адрес приёмника;

  4. Отправить сообщение устройству-партнёру.

На стороне приёмника скетч должен включать:

  1. Инициализировать ESP-NOW;

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

  3. Внутри этой функции обратного вызова сохранить сообщение в переменную для выполнения любых действий с этой информацией.

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

Полезные функции ESP-NOW

Вот краткое описание наиболее важных функций ESP-NOW:

Имя функции и описание

esp_now_init() Инициализирует ESP-NOW. Перед инициализацией ESP-NOW необходимо инициализировать Wi-Fi. Возвращает 0 в случае успеха.

esp_now_set_self_role(role) роль может быть: ESP_NOW_ROLE_IDLE = 0, ESP_NOW_ROLE_CONTROLLER, ESP_NOW_ROLE_SLAVE, ESP_NOW_ROLE_COMBO, ESP_NOW_ROLE_MAX

esp_now_add_peer(uint8 mac_addr, uint8 role, uint8 channel, uint8 key, uint8 key_len) Вызовите эту функцию для сопряжения с устройством.

esp_now_send(uint8 mac_address, uint8 data, int len) Отправка данных через ESP-NOW.

esp_now_register_send_cb() Регистрирует функцию обратного вызова, которая срабатывает при отправке данных. При отправке сообщения вызывается функция — эта функция возвращает информацию об успешной или неудачной доставке.

esp_now_register_recv_cb() Регистрирует функцию обратного вызова, которая срабатывает при получении данных. При получении данных через ESP-NOW вызывается функция.

Для получения дополнительной информации об этих функциях:

Скетч отправителя ESP8266 NodeMCU (ESP-NOW)

Вот код для платы-отправителя ESP8266 NodeMCU. Скопируйте код в Arduino IDE, но пока не загружайте его. Вам нужно внести несколько изменений, чтобы он заработал.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp8266-nodemcu-arduino-ide/

  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};

// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
  char a[32];
  int b;
  float c;
  String d;
  bool e;
} struct_message;

// Create a struct_message called myData
struct_message myData;

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) {
  Serial.print("Last 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);

  // Init ESP-NOW
  if (esp_now_init() != 0) {
    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_set_self_role(ESP_NOW_ROLE_CONTROLLER);
  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
    strcpy(myData.a, "THIS IS A CHAR");
    myData.b = random(1,20);
    myData.c = 1.2;
    myData.d = "Hello";
    myData.e = false;

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

    lastTime = millis();
  }
}

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

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

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

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

В следующей строке необходимо указать MAC-адрес ESP8266-приёмника.

uint8_t broadcastAddress[] = {0x5C, 0xCF, 0x7F, 0x99, 0x9A, 0xEA};

В нашем случае MAC-адрес приёмника: 5C:CF:7F:99:9A:EA, но вам нужно заменить эту переменную своим собственным MAC-адресом.

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

typedef struct struct_message {
  char a[32];
  int b;
  float c;
  String d;
  bool e;
} struct_message;

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

struct_message myData;

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

void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
}

В setup() инициализируйте монитор последовательного порта для отладки:

Serial.begin(115200);

Установите устройство в режим Wi-Fi станции:

WiFi.mode(WIFI_STA);

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

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

Установите роль ESP8266:

esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);

Он принимает следующие роли: ESP_NOW_ROLE_CONTROLLER, ESP_NOW_ROLE_SLAVE, ESP_NOW_ROLE_COMBO, ESP_NOW_ROLE_MAX.

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

esp_now_register_send_cb(OnDataSent);

Затем выполните сопряжение с другим устройством ESP-NOW для отправки данных:

esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

Функция esp_now_add_peer принимает следующие аргументы в указанном порядке: MAC-адрес, роль, канал Wi-Fi, ключ и длина ключа.

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

Сначала мы устанавливаем значения переменных следующим образом:

strcpy(myData.a, "THIS IS A CHAR");
myData.b = random(1,20);
myData.c = 1.2;
myData.d = "Hello";
myData.e = false;

Помните, что myData — это структура. Здесь мы присваиваем значения, которые хотим отправить внутри структуры. Например, первая строка присваивает char, вторая строка присваивает случайное число Int, Float, String и логическую переменную Boolean.

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

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

esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));

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

if ((millis() - lastTime) > timerDelay) {
  // Set values to send
  strcpy(myData.a, "THIS IS A CHAR");
  myData.b = random(1,20);
  myData.c = 1.2;
  myData.d = "Hello";
  myData.e = false;

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

  lastTime = millis();
}

Скетч приёмника ESP8266 NodeMCU (ESP-NOW)

Загрузите следующий код на плату-приёмник ESP8266 NodeMCU.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp8266-nodemcu-arduino-ide/

  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 {
    char a[32];
    int b;
    float c;
    String d;
    bool e;
} struct_message;

// Create a struct_message called myData
struct_message 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("Char: ");
  Serial.println(myData.a);
  Serial.print("Int: ");
  Serial.println(myData.b);
  Serial.print("Float: ");
  Serial.println(myData.c);
  Serial.print("String: ");
  Serial.println(myData.d);
  Serial.print("Bool: ");
  Serial.println(myData.e);
  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() != 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 struct_message {
  char a[32];
  int b;
  float c;
  String d;
  bool e;
} struct_message;

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

struct_message 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-отправителем. Чтобы получить доступ к переменной a, например, нам просто нужно вызвать myData.a.

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

  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("Char: ");
  Serial.println(myData.a);
  Serial.print("Int: ");
  Serial.println(myData.b);
  Serial.print("Float: ");
  Serial.println(myData.c);
  Serial.print("String: ");
  Serial.println(myData.d);
  Serial.print("Bool: ");
  Serial.println(myData.e);
  Serial.println();
}

В setup() инициализируйте последовательную связь для целей отладки.

Serial.begin(115200);

Установите устройство в режим Wi-Fi станции.

WiFi.mode(WIFI_STA);

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

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

Установите роль ESP8266:

esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);

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

esp_now_register_recv_cb(OnDataRecv);

Тестирование связи ESP-NOW

Загрузите скетч отправителя на одну плату и скетч приёмника на другую плату. Не забудьте вставить MAC-адрес приёмника в скетч отправителя.

Теперь откройте два окна Arduino IDE. Одно для приёмника, а другое для отправителя. Откройте монитор последовательного порта для каждой платы. Это должен быть разный COM-порт для каждой платы.

Вот что вы должны увидеть на стороне отправителя.

Тестирование связи ESP-NOW ESP8266 с Arduino IDE Отправитель

А вот что вы должны увидеть на стороне приёмника. Обратите внимание, что переменная Int изменяется между каждым полученным чтением (потому что мы установили её как случайное число на стороне отправителя).

Тестирование связи ESP-NOW ESP32 с Arduino IDE Приёмник

Мы протестировали дальность связи между двумя платами, и нам удалось получить стабильную связь на расстоянии до 140 метров (примерно 459 футов) на открытой местности. В этом эксперименте встроенные антенны обоих ESP8266 были направлены друг на друга.

Тест дальности связи ESP-NOW ESP8266 NodeMCU

Заключение

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

Платы ESP8266 и ESP32 могут обмениваться данными друг с другом по протоколу ESP-NOW. Вам просто нужно загрузить соответствующие скетчи на каждую плату. Чтобы узнать, как использовать ESP-NOW с ESP32, вы можете прочитать наше руководство по началу работы с ESP-NOW для ESP32.

Мы надеемся, что это руководство было вам полезно. Чтобы узнать больше о плате ESP8266, обязательно ознакомьтесь с нашими ресурсами:

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