ESP-NOW: двусторонняя связь между платами ESP8266 NodeMCU
Узнайте, как установить двустороннюю связь между двумя платами ESP8266 NodeMCU с использованием протокола связи ESP-NOW. В качестве примера две платы ESP8266 будут обмениваться показаниями датчика DHT. Мы будем использовать Arduino IDE.
У нас есть другие руководства по ESP-NOW с ESP8266:
ESP-NOW с ESP8266: отправка данных на несколько плат (один-ко-многим)
ESP-NOW с ESP8266: получение данных от нескольких плат (многие-к-одному)
Введение в ESP-NOW
ESP-NOW — это бессоединительный протокол связи, разработанный Espressif, который отличается передачей коротких пакетов. Этот протокол позволяет нескольким устройствам обмениваться данными без использования Wi-Fi.
Это быстрый протокол связи, который можно использовать для обмена небольшими сообщениями (до 250 байт) между платами ESP32. ESP-NOW очень универсален, и вы можете использовать одностороннюю или двустороннюю связь в различных конфигурациях.
В этом руководстве мы покажем вам, как установить двустороннюю связь между двумя платами ESP8266.
Обзор проекта
На следующей диаграмме показан обзор проекта, который мы создадим:
В этом проекте у нас будут две платы ESP8266. К каждой плате подключен датчик DHT;
Каждая плата получает показания температуры и влажности от своего соответствующего датчика;
Каждая плата отправляет свои показания другой плате через ESP-NOW;
Когда плата получает показания, она выводит их в монитор порта (Serial Monitor). Вы можете подключить OLED-дисплей для визуализации показаний, например.
При отправке сообщения выводится сообщение, указывающее, был ли пакет успешно доставлен или нет.
Каждая плата должна знать MAC-адрес другой платы для отправки сообщения.
В этом примере мы используем двустороннюю связь между двумя платами, но вы можете добавить больше плат в эту конфигурацию, чтобы все платы обменивались данными друг с другом.
Необходимые условия
Прежде чем приступить к этому проекту, убедитесь, что вы выполнили следующие предварительные условия.
Дополнение ESP8266 для Arduino IDE
Мы будем программировать ESP8266 с помощью Arduino IDE, поэтому перед тем, как приступить к этому руководству, у вас должно быть установлено дополнение ESP8266 в вашей Arduino IDE. Следуйте следующему руководству:
Установка библиотек DHT
Для чтения данных с датчика DHT мы будем использовать библиотеку DHT от Adafruit. Для использования этой библиотеки вам также необходимо установить библиотеку Adafruit Unified Sensor. Выполните следующие шаги для установки этих библиотек.
Откройте Arduino IDE и перейдите в Sketch > Include Library > Manage Libraries. Должен открыться менеджер библиотек.
Введите «DHT» в поле поиска и установите библиотеку DHT от Adafruit.
После установки библиотеки DHT от Adafruit введите «Adafruit Unified Sensor» в поле поиска. Прокрутите вниз до конца, чтобы найти библиотеку, и установите её.
После установки библиотек перезапустите Arduino IDE.
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
2x |
Плата разработки ESP8266 (читайте сравнение плат разработки ESP8266) |
2x |
|
2x |
|
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 и DHT11/DHT22
Прежде чем продолжить руководство, подключите датчик температуры и влажности DHT11 или DHT22 к ESP8266, как показано на следующей схеме.
В этом примере мы подключаем вывод данных 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), чтобы одновременно видеть взаимодействие двух плат.
Как видите, всё работает как ожидалось. Каждая плата выводит показания другой платы.
Заключение
В этом руководстве мы показали вам, как установить двустороннюю связь между двумя платами ESP8266 с помощью ESP-NOW. Это очень универсальный протокол связи, который можно использовать для отправки пакетов размером до 250 байт. Протокол связи ESP-NOW также можно использовать с платами ESP32: Начало работы с ESP-NOW (ESP32 с Arduino IDE).
В качестве примера мы показали вам взаимодействие между двумя платами, но вы можете добавить множество плат в вашу конфигурацию. Вам просто нужно знать MAC-адрес платы, на которую вы отправляете данные. Мы будем публиковать больше руководств по ESP-NOW, так что следите за обновлениями.
Мы надеемся, что это руководство было для вас полезным. Чтобы узнать больше о плате ESP8266, обязательно ознакомьтесь с нашими ресурсами:
Спасибо за чтение.