Веб-сервер на ESP32 создание мини-метеостанции на BME280

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

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

  • Плата ESP32 DOIT DEVKIT V1
  • Датчик bme280
  • Макетная плата
  • Соединительные провода

 

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

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

Чтобы использовать протокол связи SPI, необходимо использовать следующие контакты:

  • SCK - это SPI Clock pin
  • SDO - MISO
  • SDI - MOSI
  • CS - Выбор микросхемы

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

  • SCK - это также SCL pin
  • SDI - это также SDA pin

схема

В данном проекте используется связь I2C. Датчик подключается к контактам ESP32 SDA и SCL, как показано на следующей принципиальной схеме.

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

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

Откройте Arduino IDE и выберите «Скетч»> «Включить библиотеку»> «Управление библиотеками». 

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

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

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

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

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

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

После установки библиотеки BME280 и библиотеки Adafruit_Sensor откройте интегрированную среду разработки Arduino и выберите «Файл> Примеры> Библиотека Adafruit BME280> тест bme280».

/*********
  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");

    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();
}

/***************************************************************************
  This is a library for the BME280 humidity, temperature & pressure sensor

  Designed specifically to work with the Adafruit BME280 Breakout
  ----> http://www.adafruit.com/products/2650

  These sensors use I2C or SPI to communicate, 2 or 4 pins are required
  to interface. The device's I2C address is either 0x76 or 0x77.

  Adafruit invests time and resources providing this open source code,
  please support Adafruit andopen-source hardware by purchasing products
  from Adafruit!

  Written by Limor Fried & Kevin Townsend for Adafruit Industries.
  BSD license, all text above must be included in any redistribution
 ***************************************************************************/

Библиотеки

Код начинается с включения необходимых библиотек

#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, нужно изменить определение контактов, чтобы использовать ESP32 GPIO. Для связи 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.readTempera () - считывает температуру в градусах Цельсия;
  • bme.readHumidity () - считывает абсолютную влажность;
  • bme.readPressure () - считывает давление в hPa (hectoPascal = миллибар);
  • bme.readAltitude (SEALEVELPRESSURE_HPA) - считывает высоту в метрах на основе давления на уровне моря.

Загрузите код в ESP32 и откройте Serial Monitor со скоростью 9600 бод. Вы должны увидеть показания, отображаемые на Serial Monitor.

Создание таблицы в HTML

Как вы видели в начале поста, отображаются показания на веб-странице в виде таблицы. Нужно написать 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. Затем мы создаем шесть строк для отображения каждого из показаний. Внутри каждой строки мы создаем две ячейки, одну с именем измерения, а другую для хранения значения измерения. Три черты «-» должны быть заменены фактическими измерениями от датчика BME.

Можно сохранить этот текст как table.html, открыть файл в браузере и посмотреть. Предыдущий текст HTML создает следующую таблицу.

К таблице не применяются стили. Можно использовать CSS для стилизации таблицы с вашими предпочтениями.

Создание веб-сервера

Теперь, когда вы знаете, как считывать показания датчика и как построить таблицу для отображения результатов, пришло время создать веб-сервер. 

Скопируйте следующий код в Arduino IDE. Пока не загружайте. Во-первых, вам нужно указать свой SSID и пароль.

/*********
*********/

// 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;

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,
    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();
            
            // 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. После загрузки откройте Serial Monitor со скоростью 115200 бод и скопируйте IP-адрес ESP32.

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

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

 Сначала подключается библиотека 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, пароль), ждут успешного соединения и печатают IP-адрес ESP в Serial Monitor.

// 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>");

Отображение показаний датчика

Чтобы отобразить показания датчиков в таблице, нужно отправить их между соответствующими тегами. Например, для отображения температуры:

client.println("<tr><td>Temp.Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");

По умолчанию в таблице отображаются показания температуры в градусах Цельсия и Фаренгейта.

Закрытие соединения

Наконец, текст ответа готов, мы очищаем переменную заголовка и прекращаем соединение с клиентом с помощью client.stop().

// Clear the header variable
header = "";
// Close the connection
client.stop();

Комментарии

Евгений (не проверено) вт, 04/28/2020 - 21:44

Работает! Благодаря вашим пояснениям, легко разобрался. Благодарю!

Добавить комментарий

Restricted HTML

  • Допустимые HTML-теги: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.