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

Узнайте, как установить двустороннюю связь между двумя платами ESP8266 NodeMCU с использованием протокола связи ESP-NOW. В качестве примера две платы ESP8266 будут обмениваться показаниями датчика DHT. Мы будем использовать Arduino IDE.

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

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

Введение в ESP-NOW

ESP-NOW — это бессоединительный протокол связи, разработанный Espressif, который отличается передачей коротких пакетов. Этот протокол позволяет нескольким устройствам обмениваться данными без использования Wi-Fi.

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

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

В этом руководстве мы покажем вам, как установить двустороннюю связь между двумя платами ESP8266.

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

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

На следующей диаграмме показан обзор проекта, который мы создадим:

ESP8266 ESP-NOW двусторонняя связь обмен показаниями датчика DHT
  • В этом проекте у нас будут две платы ESP8266. К каждой плате подключен датчик DHT;

  • Каждая плата получает показания температуры и влажности от своего соответствующего датчика;

  • Каждая плата отправляет свои показания другой плате через ESP-NOW;

  • Когда плата получает показания, она выводит их в монитор порта (Serial Monitor). Вы можете подключить OLED-дисплей для визуализации показаний, например.

  • При отправке сообщения выводится сообщение, указывающее, был ли пакет успешно доставлен или нет.

  • Каждая плата должна знать MAC-адрес другой платы для отправки сообщения.

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

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

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

Дополнение ESP8266 для Arduino IDE

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

Установка библиотек DHT

Для чтения данных с датчика DHT мы будем использовать библиотеку DHT от Adafruit. Для использования этой библиотеки вам также необходимо установить библиотеку Adafruit Unified Sensor. Выполните следующие шаги для установки этих библиотек.

  1. Откройте Arduino IDE и перейдите в Sketch > Include Library > Manage Libraries. Должен открыться менеджер библиотек.

  2. Введите «DHT» в поле поиска и установите библиотеку DHT от Adafruit.

Установка библиотеки Adafruit DHT
  1. После установки библиотеки DHT от Adafruit введите «Adafruit Unified Sensor» в поле поиска. Прокрутите вниз до конца, чтобы найти библиотеку, и установите её.

Установка библиотеки Adafruit Unified Sensor

После установки библиотек перезапустите Arduino IDE.

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

Для этого руководства вам понадобятся следующие компоненты:

2x

Плата разработки ESP8266 (читайте сравнение плат разработки ESP8266)

2x

DHT22 или DHT11 датчик температуры и влажности

2x

Резистор 4,7 кОм

2x

Макетная плата

Набор соединительных проводов

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

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

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

}

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

После загрузки кода нажмите кнопку RST/EN, и MAC-адрес должен отобразиться в мониторе порта.

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

Запишите MAC-адрес каждой платы, чтобы чётко их идентифицировать.

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

Схема подключения ESP8266 и DHT11/DHT22

Прежде чем продолжить руководство, подключите датчик температуры и влажности DHT11 или DHT22 к ESP8266, как показано на следующей схеме.

Схема подключения ESP8266 DHT11 DHT22

В этом примере мы подключаем вывод данных DHT к GPIO5 (D1), но вы можете использовать любой другой подходящий GPIO. Прочитайте наше руководство по распиновке ESP8266 GPIO, чтобы узнать больше о GPIO ESP8266.

Вам также может понравиться: ESP8266 DHT11/DHT22 веб-сервер температуры и влажности с Arduino IDE

Код ESP8266 для двусторонней связи ESP-NOW

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

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp-now-two-way-communication-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>

#include <Adafruit_Sensor.h>
#include <DHT.h>

// REPLACE WITH THE MAC Address of your receiver
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// Digital pin connected to the DHT sensor
#define DHTPIN 5

// Uncomment the type of sensor in use:
//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

// Define variables to store DHT readings to be sent
float temperature;
float humidity;

// Define variables to store incoming readings
float incomingTemp;
float incomingHum;

// Updates DHT readings every 10 seconds
const long interval = 10000;
unsigned long previousMillis = 0;    // will store last time DHT was updated

// Variable to store if sending data was successful
String success;

//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
    float temp;
    float hum;
} struct_message;

// Create a struct_message called DHTReadings to hold sensor readings
struct_message DHTReadings;

// Create a struct_message to hold incoming sensor readings
struct_message incomingReadings;

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

// Callback when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  Serial.print("Bytes received: ");
  Serial.println(len);
  incomingTemp = incomingReadings.temp;
  incomingHum = incomingReadings.hum;
}

void getReadings(){
  // Read Temperature
  temperature = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  if (isnan(temperature)){
    Serial.println("Failed to read from DHT");
    temperature = 0.0;
  }
  humidity = dht.readHumidity();
  if (isnan(humidity)){
    Serial.println("Failed to read from DHT");
    humidity = 0.0;
  }
}

void printIncomingReadings(){
  // Display Readings in Serial Monitor
  Serial.println("INCOMING READINGS");
  Serial.print("Temperature: ");
  Serial.print(incomingTemp);
  Serial.println(" ºC");
  Serial.print("Humidity: ");
  Serial.print(incomingHum);
  Serial.println(" %");
}

void setup() {
  // Init Serial Monitor
  Serial.begin(115200);

  // Init DHT sensor
  dht.begin();

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

  // 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_COMBO, 1, NULL, 0);

  // Register for a callback function that will be called when data is received
  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you updated the DHT values
    previousMillis = currentMillis;

    //Get DHT readings
    getReadings();

    //Set values to send
    DHTReadings.temp = temperature;
    DHTReadings.hum = humidity;

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

    // Print incoming readings
    printIncomingReadings();
  }
}

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

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

Продолжайте чтение, чтобы узнать, как работает код, или перейдите к разделу «Демонстрация».

Импорт библиотек

Начните с импорта необходимых библиотек.

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

Библиотека espnow.h устанавливается по умолчанию при установке платы ESP8266. При компиляции кода убедитесь, что в меню Boards выбрана плата ESP8266.

Подключите библиотеки DHT для чтения данных с датчика DHT.

#include <Adafruit_Sensor.h>
#include <DHT.h>

В следующей строке введите MAC-адрес платы-получателя:

uint8_t broadcastAddress[] = {0x2C, 0x3A, 0xE8, 0x0E, 0xBB, 0xED};

Определите GPIO, к которому подключен вывод DHT. В данном случае он подключен к GPIO 5 (D1).

#define DHTPIN 5

Затем выберите тип используемого датчика DHT. В нашем примере мы используем DHT22. Если вы используете другой тип, просто раскомментируйте свой датчик и закомментируйте все остальные.

//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

Создайте объект DHT с определёнными ранее типом и выводом.

DHT dht(DHTPIN, DHTTYPE);

Определите переменные для хранения показаний DHT, которые будут отправлены:

float temperature;
float humidity;

Определите ещё две переменные для хранения входящих показаний:

float incomingTemp;
float incomingHum;

Мы будем отправлять показания DHT через ESP-NOW каждые 10 секунд. Этот период времени определён в переменной interval. Вы можете изменить этот интервал.

const long interval = 10000;
unsigned long previousMillis = 0;    // will store last time DHT was updated

Следующая переменная будет хранить сообщение об успешной доставке показаний на другую плату.

// Variable to store if sending data was successful
String success;

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

typedef struct struct_message {
    float temp;
    float hum;
} struct_message;

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

DHTReadings будет хранить показания для отправки.

struct_message DHTReadings;

incomingReadings будет хранить данные, поступающие от другой платы.

struct_message incomingReadings;

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

Функция OnDataSent() будет вызываться при отправке новых данных. Эта функция просто выводит информацию о том, было ли сообщение успешно доставлено или нет.

Если сообщение доставлено успешно, переменная status возвращает 0, поэтому мы можем установить наше сообщение об успехе как «Delivery Success»:

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

Если сообщение об успехе возвращает 1, это означает, что доставка не удалась:

else {
  success = "Delivery Fail :(";
}

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

Функция OnDataRecv() выполняется при получении нового пакета.

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

Мы сохраняем новый пакет в структуре incomingReadings, которую мы создали ранее:

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

Мы выводим длину сообщения в монитор порта. В каждом пакете можно отправить только 250 байт.

Serial.print("Bytes received: ");
Serial.println(len);

Затем сохраните входящие показания в соответствующие переменные. Чтобы получить доступ к переменной температуры внутри структуры incomingReadings, просто вызовите incomingReadings.temp следующим образом:

incomingTemp = incomingReadings.temp;

Тот же процесс выполняется для влажности:

incomingHum = incomingReadings.hum;

getReadings()

Функция getReadings() получает температуру и влажность от датчика DHT. Следующая строка получает текущую температуру и сохраняет её в переменной temperature:

temperature = dht.readTemperature();

Предыдущая строка возвращает температуру в градусах Цельсия. Чтобы получить температуру в градусах Фаренгейта, закомментируйте предыдущую строку и раскомментируйте следующую:

//float t = dht.readTemperature(true);

Иногда вы не можете получить показания от датчика. Когда это происходит, датчик возвращает nan. Если это так, мы устанавливаем температуру в 0.0.

Аналогичный процесс выполняется для получения показаний влажности. Текущее значение влажности сохраняется в переменной humidity:

humidity = dht.readHumidity();
if (isnan(humidity)){
  Serial.println("Failed to read from DHT");
  humidity = 0.0;
}

printIncomingReadings()

Функция printIncomingReadings() просто выводит полученные показания в монитор порта.

Полученная температура сохранена в переменной incomingTemp, а полученная влажность сохранена в переменной incomingHum, как мы увидим далее.

void printIncomingReadings(){
  // Display Readings in Serial Monitor
  Serial.println("INCOMING READINGS");
  Serial.print("Temperature: ");
  Serial.print(incomingTemp);
  Serial.println(" ºC");
  Serial.print("Humidity: ");
  Serial.print(incomingHum);
  Serial.println(" %");
}

setup()

В setup() инициализируйте монитор порта:

Serial.begin(115200);

Инициализируйте датчик DHT:

dht.begin();

Установите ESP8266 в режим станции:

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

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

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

Установите роль ESP8266 ESP-NOW. В этом примере ESP8266 принимает и отправляет показания через ESP-NOW, поэтому установите роль ESP_NOW_ROLE_COMBO.

esp_now_set_self_role(ESP_NOW_ROLE_COMBO);

Затем зарегистрируйте функцию обратного вызова OnDataSent.

esp_now_register_send_cb(OnDataSent);

Чтобы отправить данные на другую плату, необходимо связать её как пира. Следующая строка добавляет нового пира.

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

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

Наконец, зарегистрируйте функцию обратного вызова OnDataRecv.

esp_now_register_recv_cb(OnDataRecv);

loop()

В loop() проверьте, пришло ли время получить новые показания:

unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
  // save the last time you updated the DHT values
  previousMillis = currentMillis;

Если пришло время, вызовите функцию getReadings для чтения текущей температуры и влажности.

getReadings();

Затем обновите структуру DHTReadings новыми значениями температуры и влажности:

DHTReadings.temp = temperature;
DHTReadings.hum = humidity;

Отправьте структуру через ESP-NOW:

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

Наконец, вызовите функцию printIncomingReadings() для вывода входящих показаний в монитор порта.

printIncomingReadings();

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

После загрузки кода на каждую плату откройте окно монитора порта Arduino IDE. Также вы можете открыть два различных последовательных соединения (используя PuTTY), чтобы одновременно видеть взаимодействие двух плат.

ESP-NOW ESP8266 NodeMCU обмен показаниями датчиков Arduino IDE

Как видите, всё работает как ожидалось. Каждая плата выводит показания другой платы.

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

Заключение

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

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

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

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