Веб-сервер ESP32 с BME280 — продвинутая метеостанция

В этом уроке вы узнаете, как создать веб-сервер на ESP32 для отображения показаний модуля датчика BME280. Датчик BME280 измеряет температуру, влажность и давление. Таким образом, вы можете легко собрать мини-метеостанцию и отслеживать измерения с помощью веб-сервера ESP32. Именно это мы и будем делать в данном проекте.

ESP32 Web Server with BME280 Weather Station

Прежде чем приступить к этому уроку, у вас должно быть установлено дополнение ESP32 в Arduino IDE. Следуйте одному из следующих руководств для установки ESP32 в Arduino IDE, если вы ещё этого не сделали.

Вам также могут быть полезны другие руководства по BME280:

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

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

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

MakerAdvisor

Знакомство с модулем датчика BME280

Модуль датчика BME280 измеряет температуру, влажность и давление. Поскольку давление изменяется с высотой, вы также можете оценить высоту над уровнем моря. Существует несколько версий этого модуля датчика, но мы используем ту, что показана на рисунке ниже.

BME280 Sensor Module Read temperature, humidity, and pressure

Датчик может обмениваться данными по протоколу SPI или I2C (существуют модули этого датчика, которые работают только по I2C — они имеют всего четыре вывода).

Для использования протокола связи SPI используются следующие выводы:

  • SCK — это вывод тактового сигнала SPI

  • SDO — MISO

  • SDI — MOSI

  • CS — выбор микросхемы (Chip Select)

Для использования протокола связи I2C датчик использует следующие выводы:

  • SCK — это также вывод SCL

  • SDI — это также вывод SDA

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

Мы будем использовать I2C-связь с модулем датчика BME280. Для этого подключите датчик к выводам SDA и SCL на ESP32, как показано на следующей схеме.

BME280 Schematic Wiring temperature, humidity, and pressure

(Эта схема использует модуль ESP32 DEVKIT V1 с 36 GPIO — если вы используете другую модель, проверьте распиновку вашей платы.)

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

Для получения показаний с модуля датчика BME280 мы будем использовать библиотеку Adafruit_BME280. Выполните следующие шаги для установки библиотеки в Arduino IDE:

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

Введите «adafruit bme280» в поле поиска и установите библиотеку.

Installing the BME280 library Arduino IDE

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

Для использования библиотеки BME280 вам также необходимо установить библиотеку Adafruit_Sensor. Выполните следующие шаги для установки библиотеки в Arduino IDE:

Перейдите в Sketch > Include Library > Manage Libraries и введите «Adafruit Unified Sensor» в поле поиска. Прокрутите вниз, чтобы найти библиотеку, и установите её.

Installing Adafruit Unified Sensor library Arduino IDE

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

Чтение температуры, влажности и давления

Чтобы познакомиться с датчиком BME280, мы воспользуемся примером скетча из библиотеки, чтобы увидеть, как считывать температуру, влажность и давление.

Reading Temperature, Humidity, and Pressure BME280 with Arduino IDE

После установки библиотеки 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 бод. Вы должны увидеть показания, отображаемые в мониторе порта.

Demonstration Temperature, Humidity, and Pressure BME280 with Arduino IDE

Создание таблицы в 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-текст создаёт следующую таблицу.

Demonstration HTML Temperature, Humidity, and Pressure BME280 with Arduino IDE

К таблице не применены стили. Вы можете использовать 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.

ESP32 IP Address Web Server Arduino IDE Serial Monitor

Откройте браузер, вставьте IP-адрес, и вы должны увидеть последние показания датчика. Для обновления показаний достаточно обновить веб-страницу.

ESP32 with BME280 Sensor Web Server with Arduino IDE

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

Этот скетч очень похож на скетч из урока по веб-серверу 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. Вы также узнали, как создать веб-сервер, отображающий таблицу с показаниями датчиков. Вы можете легко модифицировать этот проект для отображения данных с любого другого датчика.

ESP32 Web Server with BME280 Weather Station

Если вам понравился этот проект, вас также могут заинтересовать следующие уроки:

Это отрывок из курса: Learn ESP32 with Arduino IDE. Если вам нравится ESP32 и вы хотите узнать больше, мы рекомендуем записаться на курс Learn ESP32 with Arduino IDE.

Примечание

Источник: ESP32 Web Server with BME280 – Advanced Weather Station (Random Nerd Tutorials)