Веб-сервер ESP32 с BME280 — продвинутая метеостанция
В этом уроке вы узнаете, как создать веб-сервер на ESP32 для отображения показаний модуля датчика BME280. Датчик BME280 измеряет температуру, влажность и давление. Таким образом, вы можете легко собрать мини-метеостанцию и отслеживать измерения с помощью веб-сервера ESP32. Именно это мы и будем делать в данном проекте.
Прежде чем приступить к этому уроку, у вас должно быть установлено дополнение ESP32 в Arduino IDE. Следуйте одному из следующих руководств для установки ESP32 в Arduino IDE, если вы ещё этого не сделали.
Вам также могут быть полезны другие руководства по BME280:
Необходимые компоненты
Для выполнения этого урока вам понадобятся следующие компоненты:
Вы можете использовать приведённые выше ссылки или перейти напрямую на MakerAdvisor.com/tools чтобы найти все компоненты для ваших проектов по лучшей цене!
Знакомство с модулем датчика BME280
Модуль датчика BME280 измеряет температуру, влажность и давление. Поскольку давление изменяется с высотой, вы также можете оценить высоту над уровнем моря. Существует несколько версий этого модуля датчика, но мы используем ту, что показана на рисунке ниже.
Датчик может обмениваться данными по протоколу SPI или I2C (существуют модули этого датчика, которые работают только по I2C — они имеют всего четыре вывода).
Для использования протокола связи SPI используются следующие выводы:
SCK — это вывод тактового сигнала SPI
SDO — MISO
SDI — MOSI
CS — выбор микросхемы (Chip Select)
Для использования протокола связи I2C датчик использует следующие выводы:
SCK — это также вывод SCL
SDI — это также вывод SDA
Схема подключения
Мы будем использовать I2C-связь с модулем датчика BME280. Для этого подключите датчик к выводам SDA и SCL на ESP32, как показано на следующей схеме.
(Эта схема использует модуль ESP32 DEVKIT V1 с 36 GPIO — если вы используете другую модель, проверьте распиновку вашей платы.)
Установка библиотеки BME280
Для получения показаний с модуля датчика BME280 мы будем использовать библиотеку Adafruit_BME280. Выполните следующие шаги для установки библиотеки в Arduino IDE:
Откройте Arduino IDE и перейдите в Sketch > Include Library > Manage Libraries. Откроется менеджер библиотек.
Введите «adafruit bme280» в поле поиска и установите библиотеку.
Установка библиотеки Adafruit_Sensor
Для использования библиотеки BME280 вам также необходимо установить библиотеку Adafruit_Sensor. Выполните следующие шаги для установки библиотеки в Arduino IDE:
Перейдите в Sketch > Include Library > Manage Libraries и введите «Adafruit Unified Sensor» в поле поиска. Прокрутите вниз, чтобы найти библиотеку, и установите её.
После установки библиотек перезапустите Arduino IDE.
Чтение температуры, влажности и давления
Чтобы познакомиться с датчиком BME280, мы воспользуемся примером скетча из библиотеки, чтобы увидеть, как считывать температуру, влажность и давление.
После установки библиотеки BME280 и библиотеки Adafruit_Sensor откройте Arduino IDE и перейдите в File > Examples > Adafruit BME280 library > bme280 test.
/*********
Complete project details at https://randomnerdtutorials.com
*********/
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI
unsigned long delayTime;
void setup() {
Serial.begin(9600);
Serial.println(F("BME280 test"));
bool status;
// default settings
// (you can also pass in a Wire library object like &Wire2)
status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
Serial.println("-- Default Test --");
delayTime = 1000;
Serial.println();
}
void loop() {
printValues();
delay(delayTime);
}
void printValues() {
Serial.print("Temperature = ");
Serial.print(bme.readTemperature());
Serial.println(" *C");
// Convert temperature to Fahrenheit
/*Serial.print("Temperature = ");
Serial.print(1.8 * bme.readTemperature() + 32);
Serial.println(" *F");*/
Serial.print("Pressure = ");
Serial.print(bme.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(" m");
Serial.print("Humidity = ");
Serial.print(bme.readHumidity());
Serial.println(" %");
Serial.println();
}
Библиотеки
Код начинается с подключения необходимых библиотек:
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
SPI-связь
Поскольку мы будем использовать I2C-связь, вы можете закомментировать следующие строки:
/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
Примечание: если вы используете SPI-связь, вам нужно изменить определение выводов для использования GPIO ESP32. Для SPI-связи на ESP32 вы можете использовать выводы HSPI или VSPI, как показано в следующей таблице.
SPI |
MOSI |
MISO |
CLK |
CS |
|---|---|---|---|---|
HSPI |
GPIO 13 |
GPIO 12 |
GPIO 14 |
GPIO 15 |
VSPI |
GPIO 23 |
GPIO 19 |
GPIO 18 |
GPIO 5 |
Давление на уровне моря
Создаётся переменная SEALEVELPRESSURE_HPA.
#define SEALEVELPRESSURE_HPA (1013.25)
Она сохраняет давление на уровне моря в гектопаскалях (эквивалентно миллибарам). Эта переменная используется для оценки высоты при заданном давлении путём сравнения его с давлением на уровне моря. В этом примере используется значение по умолчанию, но для более точных результатов замените значение на текущее давление на уровне моря в вашем местоположении.
I2C
В этом примере по умолчанию используется I2C-связь. Как видите, вам нужно просто создать объект Adafruit_BME280 с именем bme.
Adafruit_BME280 bme; // I2C
Если вы хотите использовать SPI, вам нужно закомментировать предыдущую строку и раскомментировать одну из следующих строк в зависимости от того, используете ли вы аппаратный или программный SPI.
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI
setup()
В setup() вы запускаете последовательную связь:
Serial.begin(9600);
И инициализируете датчик:
status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
Вывод значений
В loop() функция printValues() считывает значения с BME280 и выводит результаты в монитор порта.
void loop() {
printValues();
delay(delayTime);
}
Чтение температуры, влажности, давления и оценка высоты выполняется просто:
bme.readTemperature() — считывает температуру в градусах Цельсия;
bme.readHumidity() — считывает абсолютную влажность;
bme.readPressure() — считывает давление в гПа (гектопаскалях = миллибарах);
bme.readAltitude(SEALEVELPRESSURE_HPA) — оценивает высоту в метрах на основе давления на уровне моря.
Загрузите код в ESP32 и откройте монитор порта на скорости 9600 бод. Вы должны увидеть показания, отображаемые в мониторе порта.
Создание таблицы в HTML
Как вы видели в начале статьи, мы отображаем показания на веб-странице с таблицей, которую обслуживает ESP32. Поэтому нам нужно написать HTML-текст для создания таблицы.
Для создания таблицы в HTML используются теги <table> и </table>.
Для создания строки используются теги <tr> и </tr>. Заголовок таблицы определяется с помощью тегов <th> и </th>, а каждая ячейка таблицы определяется с помощью тегов <td> и </td>.
Для создания таблицы наших показаний используется следующий HTML-текст:
<table>
<tr>
<th>MEASUREMENT</th>
<th>VALUE</th>
</tr>
<tr>
<td>Temp. Celsius</td>
<td>--- *C</td>
</tr>
<tr>
<td>Temp. Fahrenheit</td>
<td>--- *F</td>
</tr>
<tr>
<td>Pressure</td>
<td>--- hPa</td>
</tr>
<tr>
<td>Approx. Altitude</td>
<td>--- meters</td></tr>
<tr>
<td>Humidity</td>
<td>--- %</td>
</tr>
</table>
Мы создаём заголовок таблицы с ячейкой MEASUREMENT и другой ячейкой VALUE. Затем мы создаём шесть строк для отображения каждого из показаний с помощью тегов <tr> и </tr>. Внутри каждой строки мы создаём две ячейки с помощью тегов <td> и </td>, одну с названием измерения и другую для хранения значения измерения. Три тире «—» затем должны быть заменены фактическими измерениями с датчика BME.
Вы можете сохранить этот текст как table.html, перетащить файл в браузер и посмотреть, что получилось. Приведённый выше HTML-текст создаёт следующую таблицу.
К таблице не применены стили. Вы можете использовать CSS для оформления таблицы по своему вкусу. Вам может быть полезна эта ссылка: CSS Styling Tables.
Создание веб-сервера
Теперь, когда вы знаете, как получать показания с датчика и как создать таблицу для отображения результатов, пришло время создать веб-сервер. Если вы следовали другим урокам по ESP32, вам должна быть знакома большая часть кода. Если нет, ознакомьтесь с уроком по веб-серверу ESP32.
Скопируйте следующий код в Arduino IDE. Пока не загружайте его. Сначала вам нужно указать ваш SSID и пароль.
/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/
// Load Wi-Fi library
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
//uncomment the following lines if you're using SPI
/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Set web server port number to 80
WiFiServer server(80);
// Variable to store the HTTP request
String header;
// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0;
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;
void setup() {
Serial.begin(115200);
bool status;
// default settings
// (you can also pass in a Wire library object like &Wire2)
//status = bme.begin();
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop(){
WiFiClient client = server.available(); // Listen for incoming clients
if (client) { // If a new client connects,
currentTime = millis();
previousTime = currentTime;
Serial.println("New Client."); // print a message out in the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
currentTime = millis();
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
header += c;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
// Display the HTML web page
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the table
client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
client.println("th { padding: 12px; background-color: #0043af; color: white; }");
client.println("tr { border: 1px solid #ddd; padding: 12px; }");
client.println("tr:hover { background-color: #bcbcbc; }");
client.println("td { border: none; padding: 12px; }");
client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
// Web Page Heading
client.println("</style></head><body><h1>ESP32 with BME280</h1>");
client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");
client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">");
client.println(1.8 * bme.readTemperature() + 32);
client.println(" *F</span></td></tr>");
client.println("<tr><td>Pressure</td><td><span class=\"sensor\">");
client.println(bme.readPressure() / 100.0F);
client.println(" hPa</span></td></tr>");
client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">");
client.println(bme.readAltitude(SEALEVELPRESSURE_HPA));
client.println(" m</span></td></tr>");
client.println("<tr><td>Humidity</td><td><span class=\"sensor\">");
client.println(bme.readHumidity());
client.println(" %</span></td></tr>");
client.println("</body></html>");
// The HTTP response ends with another blank line
client.println();
// Break out of the while loop
break;
} else { // if you got a newline, then clear currentLine
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
}
}
Измените следующие строки, чтобы указать ваш SSID и пароль в двойных кавычках.
const char* ssid = "";
const char* password = "";
Затем убедитесь, что выбрана правильная плата и COM-порт, и загрузите код в ESP32. После загрузки откройте монитор порта на скорости 115200 бод и скопируйте IP-адрес ESP32.
Откройте браузер, вставьте IP-адрес, и вы должны увидеть последние показания датчика. Для обновления показаний достаточно обновить веб-страницу.
Как работает код
Этот скетч очень похож на скетч из урока по веб-серверу ESP32. Сначала вы подключаете библиотеку WiFi и необходимые библиотеки для чтения данных с датчика BME280.
// Load Wi-Fi library
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
Следующая строка определяет переменную для сохранения давления на уровне моря. Для более точной оценки высоты замените значение на текущее давление на уровне моря в вашем местоположении.
#define SEALEVELPRESSURE_HPA (1013.25)
В следующей строке вы создаёте объект Adafruit_BME280 с именем bme, который по умолчанию устанавливает связь с датчиком по I2C.
Adafruit_BME280 bme; // I2C
Как упоминалось ранее, вам нужно ввести свой ssid и пароль в следующих строках в двойных кавычках.
const char* ssid = "";
const char* password = "";
Затем вы устанавливаете порт веб-сервера на 80.
// Set web server port number to 80
WiFiServer server(80);
Следующая строка создаёт переменную для хранения заголовка HTTP-запроса:
String header;
setup()
В setup() мы запускаем последовательную связь на скорости 115200 бод для отладки.
Serial.begin(115200);
Вы проверяете, что датчик BME280 успешно инициализирован.
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
Следующие строки начинают Wi-Fi соединение с помощью WiFi.begin(ssid, password), ожидают успешного подключения и выводят IP-адрес ESP в монитор порта.
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
loop()
В loop() мы программируем то, что происходит, когда новый клиент устанавливает соединение с веб-сервером. ESP постоянно прослушивает входящих клиентов с помощью этой строки:
WiFiClient client = server.available(); // Listen for incoming clients
Когда от клиента поступает запрос, мы сохраняем входящие данные. Цикл while, который следует далее, будет выполняться, пока клиент остаётся подключённым. Мы не рекомендуем изменять следующую часть кода, если вы точно не знаете, что делаете.
if (client) { // If a new client connects,
Serial.println("New Client."); // print a message out in the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
header += c;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
Отображение HTML-страницы
Следующее, что вам нужно сделать — это отправить ответ клиенту с HTML-текстом для построения веб-страницы.
Веб-страница отправляется клиенту с помощью выражения client.println(). В качестве аргумента следует указать то, что вы хотите отправить клиенту.
Следующий фрагмент кода отправляет веб-страницу для отображения показаний датчика в таблице.
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
// Feel free to change the background-color and font-size attributes to fit your preferences
client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
client.println("th { padding: 12px; background-color: #0043af; color: white; }");
client.println("tr { border: 1px solid #ddd; padding: 12px; }");
client.println("tr:hover { background-color: #bcbcbc; }");
client.println("td { border: none; padding: 12px; }");
client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
// Web Page Heading
client.println("</style></head><body><h1>ESP32 with BME280</h1>");
client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");
client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">");
client.println(1.8 * bme.readTemperature() + 32);
client.println(" *F</span></td></tr>");
client.println("<tr><td>Pressure</td><td><span class=\"sensor\">");
client.println(bme.readPressure() / 100.0F);
client.println(" hPa</span></td></tr>");
client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">");
client.println(bme.readAltitude(SEALEVELPRESSURE_HPA));
client.println(" m</span></td></tr>");
client.println("<tr><td>Humidity</td><td><span class=\"sensor\">");
client.println(bme.readHumidity());
client.println(" %</span></td></tr>");
client.println("</body></html>");
Примечание: вы можете нажать здесь для просмотра полной HTML-страницы.
Отображение показаний датчика
Для отображения показаний датчика в таблице нам просто нужно отправить их между соответствующими тегами <td> и </td>. Например, для отображения температуры:
client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");
Примечание: тег <span> полезен для стилизации определённой части текста. В данном случае мы используем тег <span> для включения показания датчика в класс «sensor». Это полезно для стилизации этой конкретной части текста с помощью CSS.
По умолчанию таблица отображает показания температуры как в градусах Цельсия, так и в градусах Фаренгейта. Вы можете закомментировать следующие три строки, если хотите отображать температуру только в градусах Фаренгейта.
/*client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");*/
Закрытие соединения
Наконец, когда ответ заканчивается, мы очищаем переменную header и прекращаем соединение с клиентом с помощью client.stop().
// Clear the header variable
header = "";
// Close the connection
client.stop();
Подведение итогов
Подводя итог, в этом проекте вы узнали, как считывать температуру, влажность, давление и оценивать высоту с помощью модуля датчика BME280. Вы также узнали, как создать веб-сервер, отображающий таблицу с показаниями датчиков. Вы можете легко модифицировать этот проект для отображения данных с любого другого датчика.
Если вам понравился этот проект, вас также могут заинтересовать следующие уроки:
—
Это отрывок из курса: Learn ESP32 with Arduino IDE. Если вам нравится ESP32 и вы хотите узнать больше, мы рекомендуем записаться на курс Learn ESP32 with Arduino IDE.
Примечание
Источник: ESP32 Web Server with BME280 – Advanced Weather Station (Random Nerd Tutorials)