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

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

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

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

Arduino IDE

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

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

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

Для видеовведения в протокол ESP-NOW посмотрите следующее видео (попробуйте проект из этого видео):

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

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

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

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

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

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

  • Полезная нагрузка до 250 байт может быть передана;

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

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

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

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

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

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

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

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

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

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

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

ESP-NOW — одна плата ESP32 отправляет данные другой плате ESP32
  • «Мастер» ESP32 отправляет данные нескольким «ведомым» ESP32 (подробнее)

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

ESP-NOW — мастер ESP32 отправляет данные нескольким ведомым ESP32
  • Один «ведомый» ESP32 получает данные от нескольких «мастеров» (подробнее)

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

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

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

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

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

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

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

Узнайте, как: обмениваться показаниями датчиков с помощью двусторонней связи ESP-NOW.

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

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

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

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

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

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

void readMacAddress(){
  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");
  }
}

void setup(){
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.STA.begin();

  Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
  readMacAddress();
}

void loop(){

}

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

После загрузки кода откройте Serial Monitor на скорости 115200 бод и нажмите кнопку RST/EN на ESP32. MAC-адрес должен отобразиться следующим образом:

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

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

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

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

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

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

Для лучшего понимания мы будем называть ESP32 #1 «отправителем», а ESP32 #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.

esp_now_add_peer() — Вызовите эту функцию для сопряжения с устройством, передав в качестве аргумента MAC-адрес пира.

esp_now_send() — Отправка данных через ESP-NOW.

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

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

Для получения дополнительной информации об этих функциях прочитайте документацию ESP-NOW на Read the Docs.

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

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

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-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 <esp_now.h>
#include <WiFi.h>

// REPLACE WITH YOUR 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;
  bool d;
} struct_message;

// Create a struct_message called myData
struct_message myData;

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
  strcpy(myData.a, "THIS IS A CHAR");
  myData.b = random(1,20);
  myData.c = 1.2;
  myData.d = false;

  // 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(2000);
}

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

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

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

#include <esp_now.h>
#include <WiFi.h>

В следующей строке вы должны вставить MAC-адрес ESP32-получателя.

uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};

В нашем случае MAC-адрес получателя: 30:AE:A4:07:0D:64, но вам нужно заменить эту переменную вашим собственным MAC-адресом.

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

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

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

struct_message myData;

Создайте переменную типа esp_now_peer_info_t для хранения информации о пире.

esp_now_peer_info_t peerInfo;

Далее определите функцию 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() инициализируйте Serial Monitor для целей отладки:

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

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

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

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

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

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

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

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

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

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

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

delay(2000);

Скетч получателя ESP32 (ESP-NOW)

Загрузите следующий код на вашу плату ESP32-получатель.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-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 <esp_now.h>
#include <WiFi.h>

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

// Create a struct_message called myData
struct_message myData;

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int 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("Bool: ");
  Serial.println(myData.d);
  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() {

}

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

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

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

#include <esp_now.h>
#include <WiFi.h>

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

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

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

struct_message myData;

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

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

Мы копируем содержимое переменной incomingData в переменную myData.

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

Теперь структура myData содержит несколько переменных с значениями, отправленными ESP32-отправителем. Чтобы получить доступ к переменной 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("Bool: ");
Serial.println(myData.d);
Serial.println();

В setup() инициализируйте Serial Monitor.

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

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

Загрузите скетч отправителя на плату ESP32-отправитель, а скетч получателя — на плату ESP32-получатель.

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

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

ESP-NOW ESP32 — вывод сообщения отправителя в Arduino IDE

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

ESP-NOW ESP32 — вывод сообщения получателя в Arduino IDE

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

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

Заключение

Мы постарались сделать наши примеры максимально простыми, чтобы вы лучше поняли, как всё работает. Существуют и другие функции, связанные с ESP-NOW, которые могут быть полезны в ваших проектах, такие как: управление пирами, удаление пиров, сканирование ведомых устройств и т.д. Для полного примера в Arduino IDE вы можете перейти в File > Examples > ESP32 > ESPNow и выбрать один из примеров скетчей.

Мы надеемся, что это введение в ESP-NOW было полезным для вас. В качестве простого примера для начала мы показали, как отправить данные в виде структуры с одной ESP32 на другую. Идея состоит в том, чтобы заменить значения структуры показаниями датчиков или состояниями GPIO, например.

Кроме того, с ESP-NOW каждая плата может одновременно быть отправителем и получателем. Одна плата может отправлять данные на несколько плат, а также получать данные от нескольких плат.

У нас также есть руководство по ESP-NOW с ESP8266: Начало работы с ESP-NOW (ESP8266 NodeMCU с Arduino IDE).

Для изучения дополнительных материалов о плате ESP32 обязательно ознакомьтесь с нашими ресурсами:

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


Источник: Getting Started with ESP-NOW (ESP32 with Arduino IDE)