Веб-сервер ESP8266 NodeMCU с BME680 — метеостанция (Arduino IDE)
В этом руководстве показано, как создать веб-сервер метеостанции на ESP8266 NodeMCU для отображения показаний датчика окружающей среды BME680: газ (качество воздуха), температура, влажность и давление. Показания автоматически обновляются на веб-сервере с помощью Server-Sent Events (SSE). Плата ESP8266 будет запрограммирована с помощью Arduino IDE.
Для создания веб-сервера мы будем использовать библиотеку ESP Async Web Server, которая предоставляет простой способ создания асинхронного веб-сервера.
Датчик окружающей среды BME680
BME680 — это датчик окружающей среды, объединяющий датчики газа, температуры, влажности и давления. Датчик газа может обнаруживать широкий спектр газов, таких как летучие органические соединения (VOC). По этой причине BME680 может использоваться для контроля качества воздуха в помещении.
BME680 содержит MOX (Metal-oxide) сенсор, который обнаруживает VOC в воздухе. Этот датчик даёт вам качественное представление о сумме VOC/загрязняющих веществ в окружающем воздухе. В качестве необработанного сигнала BME680 выводит значения сопротивления. Эти значения изменяются из-за колебаний концентрации VOC:
Более высокая концентрация VOC >> Более низкое сопротивление
Более низкая концентрация VOC >> Более высокое сопротивление
Для получения дополнительной информации о BME680 прочитайте наше руководство по началу работы: ESP8266 NodeMCU: датчик окружающей среды BME680 в Arduino IDE (газ, давление, влажность, температура)
Необходимые компоненты
Для выполнения этого руководства вам понадобятся следующие компоненты:
Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все детали для ваших проектов по лучшей цене!
Схема подключения — ESP8266 с BME680
BME680 может обмениваться данными по протоколам связи I2C или SPI. В этом руководстве мы будем использовать протокол связи I2C.
Следуйте приведённой ниже схеме подключения, чтобы подключить BME680 к ESP8266, используя стандартные пины I2C.
Рекомендуемая литература: Распиновка ESP8266: какие GPIO пины следует использовать?
Подготовка Arduino IDE
Мы будем программировать плату ESP8266 с помощью Arduino IDE. Поэтому убедитесь, что у вас установлено дополнение ESP8266. Следуйте следующему руководству:
Вам также необходимо установить следующие библиотеки:
Вы можете установить эти библиотеки с помощью Менеджера библиотек Arduino. Перейдите в Скетч > Подключить библиотеку > Управлять библиотеками и найдите нужные библиотеки по названию.
Код веб-сервера ESP8266 BME680
Откройте Arduino IDE и скопируйте следующий код. Чтобы он заработал, вам нужно вставить учётные данные вашей сети: SSID и пароль.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp8266-nodemcu-bme680-sensor-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 <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
//Uncomment if using SPI
/*#define BME_SCK 14
#define BME_MISO 12
#define BME_MOSI 13
#define BME_CS 15*/
Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
float temperature;
float humidity;
float pressure;
float gasResistance;
AsyncWebServer server(80);
AsyncEventSource events("/events");
unsigned long lastTime = 0;
unsigned long timerDelay = 30000; // send readings timer
void getBME680Readings(){
// Tell BME680 to begin measurement.
unsigned long endTime = bme.beginReading();
if (endTime == 0) {
Serial.println(F("Failed to begin reading :("));
return;
}
if (!bme.endReading()) {
Serial.println(F("Failed to complete reading :("));
return;
}
temperature = bme.temperature;
pressure = bme.pressure / 100.0;
humidity = bme.humidity;
gasResistance = bme.gas_resistance / 1000.0;
}
String processor(const String& var){
getBME680Readings();
//Serial.println(var);
if(var == "TEMPERATURE"){
return String(temperature);
}
else if(var == "HUMIDITY"){
return String(humidity);
}
else if(var == "PRESSURE"){
return String(pressure);
}
else if(var == "GAS"){
return String(gasResistance);
}
return String();
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>BME680 Web Server</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;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #4B1D3F; 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(300px, 1fr)); }
.reading { font-size: 2.8rem; }
.card.temperature { color: #0e7c7b; }
.card.humidity { color: #17bebb; }
.card.pressure { color: #3fca6b; }
.card.gas { color: #d62246; }
</style>
</head>
<body>
<div class="topnav">
<h3>BME680 WEB SERVER</h3>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
</div>
<div class="card humidity">
<h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> %</span></p>
</div>
<div class="card pressure">
<h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
</div>
<div class="card gas">
<h4><i class="fas fa-wind"></i> GAS</h4><p><span class="reading"><span id="gas">%GAS%</span> KΩ</span></p>
</div>
</div>
</div>
<script>
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('temperature', function(e) {
console.log("temperature", e.data);
document.getElementById("temp").textContent = e.data;
}, false);
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").textContent = e.data;
}, false);
source.addEventListener('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").textContent = e.data;
}, false);
source.addEventListener('gas', function(e) {
console.log("gas", e.data);
document.getElementById("gas").textContent = e.data;
}, false);
}
</script>
</body>
</html>)rawliteral";
void setup() {
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.println();
// Init BME680 sensor
if (!bme.begin()) {
Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
// Handle Web Server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", index_html, processor);
});
// Handle Web Server Events
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() {
if ((millis() - lastTime) > timerDelay) {
getBME680Readings();
Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f % \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
Serial.println();
// Send Events to the Web Server with the Sensor Readings
events.send("ping",NULL,millis());
events.send(String(temperature).c_str(),"temperature",millis());
events.send(String(humidity).c_str(),"humidity",millis());
events.send(String(pressure).c_str(),"pressure",millis());
events.send(String(gasResistance).c_str(),"gas",millis());
lastTime = millis();
}
}
Вставьте учётные данные вашей сети в следующие переменные, и код будет работать сразу.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Как работает код
Прочитайте этот раздел, чтобы узнать, как работает код, или перейдите к следующему разделу.
Подключение библиотек
Начнём с подключения необходимых библиотек. Библиотека Wire нужна для протокола связи I2C. Мы также подключаем библиотеку SPI на случай, если вы захотите использовать вместо этого протокол связи SPI.
#include <Wire.h>
#include <SPI.h>
Библиотеки Adafruit_Sensor и Adafruit_BME680 необходимы для работы с датчиком BME680.
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
Библиотеки WiFi и ESPAsyncWebServer используются для создания веб-сервера.
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
Учётные данные сети
Вставьте учётные данные вашей сети в следующие переменные, чтобы ESP8266 мог подключиться к вашей локальной сети по Wi-Fi.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Связь I2C
Создайте объект Adafruit_BME680 с именем bme на стандартных пинах I2C ESP8266.
Adafruit_BME680 bme; // I2C
Если вы хотите использовать вместо этого протокол связи SPI, вам нужно определить пины SPI ESP8266 в следующих строках (для раскомментирования уберите /* и */):
/*#define BME_SCK 14
#define BME_MISO 12
#define BME_MOSI 13
#define BME_CS 15*/
А затем создайте объект Adafruit_BME680, используя эти пины (для раскомментирования уберите //):
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
Объявление переменных
Переменные типа float — temperature, humidity, pressure и gasResistance — будут использоваться для хранения показаний датчика BME680.
float temperature;
float humidity;
float pressure;
float gasResistance;
Переменные lastTime и timerDelay будут использоваться для обновления показаний датчика каждые X секунд. В качестве примера мы будем получать новые показания датчика каждые 30 секунд (30000 миллисекунд). Вы можете изменить это время задержки в переменной timerDelay.
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
Создайте асинхронный веб-сервер на порту 80.
AsyncWebServer server(80);
Создание источника событий
Для автоматического отображения информации на веб-сервере при поступлении нового показания мы будем использовать Server-Sent Events (SSE).
Следующая строка создаёт новый источник событий по адресу /events.
AsyncEventSource events("/events");
Server-Sent Events позволяют веб-странице (клиенту) получать обновления от сервера. Мы будем использовать это для автоматического отображения новых показаний на странице веб-сервера, когда появятся новые данные BME680.
Важно: Server-sent events не поддерживаются в Internet Explorer.
Получение показаний BME680
Функция getBME680Reading() получает показания газа, температуры, влажности и давления от датчика BME680 и сохраняет их в переменных gasResistance, temperature, humidity и pressure.
void getBME680Readings(){
// Tell BME680 to begin measurement.
unsigned long endTime = bme.beginReading();
if (endTime == 0) {
Serial.println(F("Failed to begin reading :("));
return;
}
if (!bme.endReading()) {
Serial.println(F("Failed to complete reading :("));
return;
}
temperature = bme.temperature;
pressure = bme.pressure / 100.0;
humidity = bme.humidity;
gasResistance = bme.gas_resistance / 1000.0;
}
Процессор
Функция processor() заменяет все заполнители в HTML-тексте, используемом для создания веб-страницы, текущими показаниями датчика.
String processor(const String& var){
getBME680Readings();
//Serial.println(var);
if(var == "TEMPERATURE"){
return String(temperature);
}
else if(var == "HUMIDITY"){
return String(humidity);
}
else if(var == "PRESSURE"){
return String(pressure);
}
else if(var == "GAS"){
return String(gasResistance);
}
}
Это позволяет отображать текущие показания датчика на веб-странице при первом обращении к ней. В противном случае вы увидите пустое место до тех пор, пока не появятся новые показания (что может занять некоторое время в зависимости от времени задержки, определённого в коде).
Создание веб-страницы
Переменная index_html содержит весь HTML, CSS и JavaScript для создания веб-страницы. Мы не будем подробно рассматривать, как работают HTML и CSS. Мы просто рассмотрим, как обрабатывать события, отправленные сервером.
Давайте быстро взглянем на строку, которая отображает температуру:
<h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
Вы можете видеть, что заполнитель %TEMPERATURE% окружён тегами <span id="temp"></span>. HTML-атрибут id используется для указания уникального идентификатора HTML-элемента.
Он используется для указания на определённый стиль или может быть использован JavaScript для доступа к элементу с этим конкретным id и управления им. Именно это мы и собираемся сделать.
Например, когда веб-сервер получает новое событие с последним значением температуры, мы обновим HTML-элемент с id «temp» новым показанием.
Аналогичный процесс используется для обновления остальных показаний.
Обработка событий
Создайте новый объект 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);
Затем добавьте обработчик события для «temperature».
source.addEventListener('temperature', function(e) {
Когда появляется новое значение температуры, ESP8266 отправляет событие («temperature») клиенту. Следующие строки обрабатывают то, что происходит, когда браузер получает это событие.
console.log("temperature", e.data);
document.getElementById("temp").textContent = e.data;
По сути, выводим новые показания в консоль браузера и помещаем полученные данные в элемент с соответствующим id («temp») на веб-странице.
Аналогичный процесс выполняется для влажности, давления и сопротивления газа.
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").textContent = e.data;
}, false);
source.addEventListener('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").textContent = e.data;
}, false);
source.addEventListener('gas', function(e) {
console.log("gas", e.data);
document.getElementById("gas").textContent = e.data;
}, false);
setup()
В функции setup() инициализируем монитор порта.
Serial.begin(115200);
Подключаем ESP8266 к вашей локальной сети и выводим IP-адрес ESP8266.
// 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.println();
Инициализируем датчик BME680.
// Init BME680 sensor
if (!bme.begin()) {
Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
Обработка запросов
Когда вы обращаетесь к IP-адресу ESP8266 по корневому URL /, отправляем текст, хранящийся в переменной index_html, для создания веб-страницы и передаём processor в качестве аргумента, чтобы все заполнители были заменены последними показаниями датчика.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
Источник событий сервера
Настраиваем источник событий на сервере.
// Handle Web Server Events
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() получаем новые показания датчика:
getBME680Readings();
Выводим новые показания в монитор порта.
Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f % \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
Serial.println();
Наконец, отправляем события в браузер с новейшими показаниями датчика для обновления веб-страницы.
// Send Events to the Web Server with the Sensor Readings
events.send("ping",NULL,millis());
events.send(String(temperature).c_str(),"temperature",millis());
events.send(String(humidity).c_str(),"humidity",millis());
events.send(String(pressure).c_str(),"pressure",millis());
events.send(String(gasResistance).c_str(),"gas",millis());
Следующая диаграмма обобщает, как Server-Sent Events работают для обновления веб-страницы.
Загрузка кода
Теперь загрузите код на ESP8266. Убедитесь, что выбрана правильная плата и COM-порт.
После загрузки откройте монитор порта на скорости 115200 бод. Нажмите встроенную кнопку RST/EN на ESP8266. IP-адрес должен быть напечатан в мониторе порта.
Демонстрация
Откройте браузер в вашей локальной сети и введите IP-адрес ESP8266. Вы должны получить доступ к веб-серверу ESP8266 с последними показаниями BME680.
Показания обновляются автоматически с помощью Server-Sent Events.
Заключение
В этом руководстве вы узнали, как создать асинхронный веб-сервер метеостанции на ESP8266 для отображения показаний датчика BME680 — газ (качество воздуха), температура, влажность и давление — и как автоматически обновлять показания на веб-странице с помощью Server-Sent Events.
У нас есть другие руководства по веб-серверам, которые могут вам понравиться:
ESP8266 DHT11/DHT22 веб-сервер температуры и влажности с Arduino IDE
ESP8266 с BME280 в Arduino IDE (давление, температура, влажность)
ESP8266 DS18B20 датчик температуры с Arduino IDE (одиночный, множественный, веб-сервер)
Надеемся, что этот проект был для вас интересным. Узнайте больше об ESP8266 с нашими ресурсами:
Спасибо за чтение.