ESP8266 NodeMCU: Панель мониторинга датчиков на веб-сервере ESP-NOW (ESP-NOW + Wi-Fi)
В этом проекте вы узнаете, как разместить веб-сервер на плате ESP8266 NodeMCU и одновременно использовать протокол связи ESP-NOW. У вас может быть несколько плат ESP8266, отправляющих показания датчиков через ESP-NOW на одну плату-приёмник ESP8266, которая отображает все показания на веб-сервере. Платы будут программироваться с помощью Arduino IDE.
У нас есть другие руководства, связанные с ESP-NOW, которые могут вас заинтересовать:
ESP-NOW с ESP8266: Отправка данных на несколько плат (один-ко-многим)
ESP-NOW с ESP8266: Приём данных от нескольких плат (многие-к-одному)
Одновременное использование ESP-NOW и Wi-Fi
Есть несколько моментов, которые необходимо учитывать, если вы хотите использовать Wi-Fi для размещения веб-сервера и одновременно использовать ESP-NOW для получения показаний датчиков от других плат:
Платы-отправители ESP8266 должны использовать тот же Wi-Fi канал, что и плата-приёмник.
Wi-Fi канал платы-приёмника автоматически назначается вашим Wi-Fi роутером.
Режим Wi-Fi платы-приёмника должен быть точка доступа и станция (
WIFI_AP_STA).Вы можете настроить тот же Wi-Fi канал вручную, или добавить простой фрагмент кода на отправитель, чтобы установить его Wi-Fi канал таким же, как у платы-приёмника.
Обзор проекта
На следующей диаграмме показан общий обзор проекта, который мы создадим.
Есть две платы-отправителя ESP8266, которые отправляют показания температуры и влажности с датчика BME280 через ESP-NOW на одну плату-приёмник ESP8266 (конфигурация ESP-NOW «многие-к-одному»);
Плата-приёмник ESP8266 получает пакеты и отображает показания на веб-сервере;
Веб-страница автоматически обновляется каждый раз, когда поступает новое показание, с помощью Server-Sent Events (SSE).
Веб-страница также показывает время последнего обновления показаний с помощью JavaScript.
Предварительные требования
Прежде чем приступить к этому проекту, убедитесь, что вы проверили следующие предварительные требования.
Arduino IDE
Мы будем программировать платы ESP8266 с помощью Arduino IDE, поэтому перед началом работы с этим руководством убедитесь, что у вас установлена плата ESP8266 в Arduino IDE.
Библиотеки BME280
Плата-отправитель ESP8266 будет отправлять показания температуры и влажности с датчика BME280.
Для чтения данных с датчика BME280 мы будем использовать библиотеку Adafruit_BME280. Для использования этой библиотеки вам также необходимо установить библиотеку Adafruit Unified Sensor. Выполните следующие шаги для установки этих библиотек.
Найдите «Adafruit BME280 Library» в поле поиска и установите библиотеку.
Для использования библиотеки BME280 вам также необходимо установить библиотеку Adafruit_Sensor. Всплывающее окно предложит вам установить её и все другие зависимости при установке библиотеки BME280.
Чтобы узнать больше о датчике температуры, влажности и давления BME280, прочитайте наше руководство: ESP8266 с BME280 с использованием Arduino IDE (давление, температура, влажность).
Библиотеки асинхронного веб-сервера
Мы создадим веб-сервер с использованием следующих библиотек:
Вы можете установить эти библиотеки с помощью менеджера библиотек Arduino. Перейдите в Sketch > Include Library > Manage Libraries и найдите названия библиотек.
Библиотека Arduino_JSON
Вам необходимо установить библиотеку Arduino_JSON. Вы можете установить эту библиотеку через менеджер библиотек Arduino IDE. Просто перейдите в Sketch > Include Library > Manage Libraries и найдите название библиотеки «Arduino_JSON», затем выберите вариант by Arduino.
Необходимые компоненты
Для этого руководства вам потребуется несколько плат ESP8266. Мы будем использовать три платы ESP8266. Вам также понадобятся:
3x ESP8266 (читайте Лучшие платы разработки ESP8266)
Вы можете использовать вышеуказанные ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Получение MAC-адреса платы-приёмника
Для отправки сообщений через ESP-NOW вам необходимо знать MAC-адрес платы-приёмника. Каждая плата имеет уникальный MAC-адрес (узнайте, как получить и изменить MAC-адрес ESP8266).
Загрузите следующий код на плату-приёмник 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-адрес должен отобразиться в мониторе порта.
Приёмник ESP8266 (ESP-NOW + веб-сервер)
Плата-приёмник ESP8266 NodeMCU получает пакеты от плат-отправителей и размещает веб-сервер для отображения последних полученных показаний.
Загрузите следующий код на плату-приёмник – код подготовлен для приёма показаний от двух разных плат.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp8266-esp-now-wi-fi-web-server/
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 <espnow.h>
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
#include "ESPAsyncTCP.h"
#include <Arduino_JSON.h>
// Replace with your network credentials (STATION)
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
struct_message incomingReadings;
JSONVar board;
AsyncWebServer server(80);
AsyncEventSource events("/events");
// callback function that will be executed when data is received
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
// Copies the sender mac address to a string
char macStr[18];
Serial.print("Packet received from: ");
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.println(macStr);
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);
events.send(jsonString.c_str(), "new_readings", millis());
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP-NOW DASHBOARD</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h1 { font-size: 2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.reading { font-size: 2.8rem; }
.timestamp { color: #bebebe; font-size: 1rem; }
.card-title{ font-size: 1.2rem; font-weight : bold; }
.card.temperature { color: #B10F2E; }
.card.humidity { color: #50B8B4; }
</style>
</head>
<body>
<div class="topnav">
<h1>ESP-NOW DASHBOARD</h1>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<p class="card-title"><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</p><p><span class="reading"><span id="t1"></span> °C</span></p><p class="timestamp">Last Reading: <span id="rt1"></span></p>
</div>
<div class="card humidity">
<p class="card-title"><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</p><p><span class="reading"><span id="h1"></span> %</span></p><p class="timestamp">Last Reading: <span id="rh1"></span></p>
</div>
<div class="card temperature">
<p class="card-title"><i class="fas fa-thermometer-half"></i> BOARD #2 - TEMPERATURE</p><p><span class="reading"><span id="t2"></span> °C</span></p><p class="timestamp">Last Reading: <span id="rt2"></span></p>
</div>
<div class="card humidity">
<p class="card-title"><i class="fas fa-tint"></i> BOARD #2 - HUMIDITY</p><p><span class="reading"><span id="h2"></span> %</span></p><p class="timestamp">Last Reading: <span id="rh2"></span></p>
</div>
</div>
</div>
<script>
function getDateTime() {
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " at "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
return datetime;
}
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = getDateTime();
document.getElementById("rh"+obj.id).innerHTML = getDateTime();
}, false);
}
</script>
</body>
</html>)rawliteral";
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
// Set the device as a Station and Soft Access Point simultaneously
WiFi.mode(WIFI_AP_STA);
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
// 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_register_recv_cb(OnDataRecv);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", index_html);
});
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
}
}
Как работает код
Сначала подключите необходимые библиотеки.
#include <espnow.h>
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
#include "ESPAsyncTCP.h"
#include <Arduino_JSON.h>
Библиотека Arduino_JSON необходима, потому что мы создадим JSON-переменную с данными, полученными от каждой платы. Эта JSON-переменная будет использоваться для отправки всей необходимой информации на веб-страницу, как вы увидите далее в этом проекте.
Вставьте ваши сетевые учётные данные в следующие строки, чтобы ESP8266 мог подключиться к вашей локальной сети.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Структура данных
Затем создайте структуру, содержащую данные, которые мы будем получать. Мы назвали эту структуру struct_message, и она содержит идентификатор платы, показания температуры и влажности, а также идентификатор показания.
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
Создайте новую переменную типа struct_message с именем incomingReadings, которая будет хранить значения переменных.
struct_message incomingReadings;
Создайте JSON-переменную с именем board.
JSONVar board;
Создайте асинхронный веб-сервер на порту 80.
AsyncWebServer server(80);
Создание источника событий
Для автоматического отображения информации на веб-сервере при поступлении нового показания мы будем использовать Server-Sent Events (SSE).
Следующая строка создаёт новый источник событий на /events.
AsyncEventSource events("/events");
Server-Sent Events позволяют веб-странице (клиенту) получать обновления от сервера. Мы будем использовать это для автоматического отображения новых показаний на странице веб-сервера при поступлении нового пакета ESP-NOW.
Важно: Server-sent events не поддерживаются в Internet Explorer.
Функция OnDataRecv()
Функция OnDataRecv() будет выполняться при получении нового пакета ESP-NOW.
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
Внутри этой функции выведите MAC-адрес отправителя:
char macStr[18];
Serial.print("Packet received from: ");
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.println(macStr);
Скопируйте информацию из переменной incomingData в структурную переменную incomingReadings.
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Затем создайте строковую JSON-переменную с полученной информацией (переменная jsonString):
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);
Вот пример того, как может выглядеть переменная jsonString после получения показаний:
board = {
"id": "1",
"temperature": "24.32",
"humidity" = "65.85",
"readingId" = "2"
}
После сбора всех полученных данных в переменной jsonString отправьте эту информацию в браузер как событие («new_readings»).
events.send(jsonString.c_str(), "new_readings", millis());
Позже мы рассмотрим, как обрабатывать эти события на стороне клиента.
Наконец, выведите полученную информацию в мониторе порта Arduino IDE для целей отладки:
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();
Создание веб-страницы
Переменная index_html содержит весь HTML, CSS и JavaScript для создания веб-страницы. Мы не будем подробно рассматривать, как работает HTML и CSS. Мы только рассмотрим, как обрабатывать события, отправленные сервером.
Обработка событий
Создайте новый объект EventSource и укажите URL страницы, отправляющей обновления. В нашем случае это /events.
if (!!window.EventSource) {
var source = new EventSource('/events');
После создания экземпляра источника событий вы можете начать прослушивать сообщения от сервера с помощью addEventListener().
Это слушатели событий по умолчанию, как показано в документации AsyncWebServer.
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
Затем добавьте слушатель событий для «new_readings».
source.addEventListener('new_readings', function(e) {
Когда ESP8266 получает новый пакет, он отправляет JSON-строку с показаниями как событие («new_readings») клиенту. Следующие строки определяют, что происходит, когда браузер получает это событие.
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = getDateTime();
document.getElementById("rh"+obj.id).innerHTML = getDateTime();
По сути, выводим новые показания в консоль браузера и помещаем полученные данные в элементы с соответствующим id на веб-странице. Мы также обновляем дату и время получения показаний, вызывая JavaScript-функцию getDateTime().
function getDateTime() {
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " at "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
return datetime;
}
setup()
В setup() установите приёмник ESP8266 как точку доступа и Wi-Fi станцию:
WiFi.mode(WIFI_AP_STA);
Следующие строки подключают ESP8266 к вашей локальной сети и выводят IP-адрес и Wi-Fi канал:
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
Инициализируйте ESP-NOW.
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
Зарегистрируйте функцию обратного вызова OnDataRecv, чтобы она выполнялась при поступлении нового пакета ESP-NOW.
esp_now_register_recv_cb(OnDataRecv);
Обработка запросов
Когда вы обращаетесь к IP-адресу ESP8266 по корневому URL /, отправляется текст, хранящийся в переменной index_html, для построения веб-страницы.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
Серверный источник событий
Настройте источник событий на сервере.
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
);
server.addHandler(&events);
Наконец, запустите сервер.
server.begin();
loop()
В loop() отправляем ping каждые 5 секунд. Это используется для проверки на стороне клиента, работает ли сервер (эти строки не являются обязательными).
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
}
На следующей диаграмме показано, как работают Server-Sent Events в этом проекте и как обновляются значения без перезагрузки веб-страницы.
После загрузки кода на плату-приёмник нажмите встроенную кнопку EN/RST. IP-адрес ESP8266 должен быть выведен в мониторе порта, а также Wi-Fi канал.
Схема подключения отправителя ESP8266
Платы-отправители ESP8266 подключены к датчику BME280. Подключите датчик к стандартным I2C пинам ESP8266:
GPIO 5 (D1) -> SCL
GPIO 4 (D2) -> SDA
Код отправителя ESP8266 (ESP-NOW)
Каждая плата-отправитель будет отправлять структуру через ESP-NOW, содержащую идентификатор платы (чтобы вы могли определить, какая плата отправила показания), температуру, влажность и идентификатор показания. Идентификатор показания – это число типа int, позволяющее узнать, сколько сообщений было отправлено.
Загрузите следующий код на каждую из ваших плат-отправителей. Не забудьте увеличить номер id для каждой платы-отправителя и вставить ваш SSID в переменную WIFI_SSID.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp8266-esp-now-wi-fi-web-server/
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 <espnow.h>
#include <ESP8266WiFi.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 2
Adafruit_BME280 bme;
//MAC Address of the receiver
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
//Create a struct_message called myData
struct_message myData;
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
unsigned int readingId = 0;
// Insert your SSID
constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";
int32_t getWiFiChannel(const char *ssid) {
if (int32_t n = WiFi.scanNetworks()) {
for (uint8_t i=0; i<n; i++) {
if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
return WiFi.channel(i);
}
}
}
return 0;
}
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
float readTemperature() {
float t = bme.readTemperature();
return t;
}
float readHumidity() {
float h = bme.readHumidity();
return h;
}
// 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);
initBME();
// Set device as a Wi-Fi Station and set channel
WiFi.mode(WIFI_STA);
int32_t channel = getWiFiChannel(WIFI_SSID);
WiFi.printDiag(Serial); // Uncomment to verify channel number before
wifi_promiscuous_enable(1);
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
WiFi.printDiag(Serial); // Uncomment to verify channel change after
// 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);
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
//Set values to send
myData.id = BOARD_ID;
myData.temp = readTemperature();
myData.hum = readHumidity();
myData.readingId = readingId++;
esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
Serial.print("loop");
}
}
Как работает код
Начните с импорта необходимых библиотек:
#include <espnow.h>
#include <ESP8266WiFi.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
Установка идентификатора платы
Определите идентификатор платы-отправителя ESP8266, например, установите BOARD_ID 1 для ESP8266 Sender #1 и т.д.
#define BOARD_ID 1
Датчик BME280
Создайте объект Adafruit_BME280 с именем bme.
Adafruit_BME280 bme;
MAC-адрес приёмника
Вставьте MAC-адрес приёмника в следующую строку (например):
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x15, 0xC7, 0xFC};
Структура данных
Затем создайте структуру, содержащую данные, которые мы хотим отправить. Структура struct_message содержит идентификатор платы, показание температуры, показание влажности и идентификатор показания.
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
Создайте новую переменную типа struct_message с именем myData, которая хранит значения переменных.
struct_message myData;
Интервал таймера
Создайте вспомогательные переменные таймера для публикации показаний каждые 10 секунд. Вы можете изменить время задержки в переменной interval.
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
Инициализируйте переменную readingId – она отслеживает количество отправленных показаний.
unsigned int readingId = 0;
Изменение Wi-Fi канала
Теперь мы получим Wi-Fi канал приёмника. Это полезно, потому что позволяет нам автоматически назначить тот же Wi-Fi канал плате-отправителю.
Для этого вы должны вставить свой SSID в следующую строку:
constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";
Затем функция getWiFiChannel() сканирует вашу сеть и получает её канал.
int32_t getWiFiChannel(const char *ssid) {
if (int32_t n = WiFi.scanNetworks()) {
for (uint8_t i=0; i<n; i++) {
if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
return WiFi.channel(i);
}
}
}
return 0;
}
Этот фрагмент кода был предложен Stephane (одним из наших читателей). Вы можете увидеть его полный пример здесь.
Инициализация датчика BME280
Функция initBME() инициализирует датчик BME280.
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
Чтение температуры
Функция readTemperature() считывает и возвращает температуру с датчика BME280.
float readTemperature() {
float t = bme.readTemperature();
return t;
}
Чтение влажности
Функция readHumidity() считывает и возвращает влажность с датчика BME280.
float readHumidity() {
float h = bme.readHumidity();
return h;
}
Примечание: чтобы узнать больше о получении температуры и влажности с датчика BME280, прочитайте: ESP8266 с BME280 с использованием Arduino IDE (давление, температура, влажность).
Функция обратного вызова OnDataSent
Функция обратного вызова 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);
Инициализируйте датчик BME280:
initBME();
Установите ESP8266 как Wi-Fi станцию.
WiFi.mode(WIFI_STA);
Установите его канал в соответствии с Wi-Fi каналом приёмника:
int32_t channel = getWiFiChannel(WIFI_SSID);
WiFi.printDiag(Serial); // Uncomment to verify channel number before
wifi_promiscuous_enable(1);
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
WiFi.printDiag(Serial); // Uncomment to verify channel change after
Инициализируйте ESP-NOW.
// Init 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 зарегистрируйте функцию обратного вызова, которая будет вызываться при отправке сообщения. В данном случае зарегистрируйте ранее созданную функцию OnDataSent().
esp_now_register_send_cb(OnDataSent);
Добавление пира
Для отправки данных на другую плату (приёмник) необходимо добавить её как пир. Следующие строки регистрируют и добавляют приёмник как пир.
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
loop()
В loop() проверяем, пришло ли время получить и отправить новые показания.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
Отправка сообщения ESP-NOW
Наконец, отправьте структуру сообщения через ESP-NOW.
esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
Рекомендуемое чтение: Начало работы с ESP-NOW (ESP8266 с Arduino IDE)
Загрузите код на ваши платы-отправители. Вы должны заметить, что платы изменяют свой Wi-Fi канал на канал платы-приёмника.
Демонстрация
После загрузки кода на все платы и если всё работает как ожидается, плата-приёмник ESP8266 должна начать получать показания датчиков от других плат.
Откройте браузер в вашей локальной сети и введите IP-адрес ESP8266.
Должны загрузиться температура, влажность и время последнего обновления показаний на веб-странице для каждой платы. При получении нового пакета ваша веб-страница автоматически обновляется без перезагрузки страницы.
Заключение
В этом руководстве вы узнали, как использовать ESP-NOW и Wi-Fi для настройки веб-сервера для приёма пакетов ESP-NOW от нескольких плат (конфигурация «многие-к-одному») с использованием платы ESP8266 NodeMCU. У нас есть аналогичный проект с использованием ESP32:
Кроме того, вы также использовали Server-Sent Events для автоматического обновления веб-страницы каждый раз при получении нового пакета без перезагрузки страницы. У нас есть специальное руководство по Server-Sent Events:
Если вам нравится ESP8266, вы можете рассмотреть возможность приобретения нашей электронной книги «Домашняя автоматизация с использованием ESP8266». Вы также можете получить доступ к нашим бесплатным ресурсам по ESP8266 здесь.
Спасибо за чтение.