ESP32:Примеры/Веб-сервер на базе ESP32 – отображение данных, считанных датчиком BME280

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Веб-сервер на базе ESP32 – отображение данных, считанных датчиком BME280

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

Что такое датчик BME280

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

BME280 может коммуницировать при помощи протоколов передачи данных SPI и I2C (есть версии этого модуля, которые коммуницируют только через I2C, и у них лишь 4 контакта).

При коммуникации с помощью SPI нужно использовать следующие контакты:

  • SCK – это тактовый контакт интерфейса SPI;
  • SDOMISO;
  • SDIMOSI;
  • CS – контакт для выбора чипа («chip select»);

При коммуникации с помощью I2C нужно использовать следующие контакты:

  • SCK – это также контакт SCL
  • SDI – это также контакт SDA

Что нужно сделать перед загрузкой кода

Устанавливаем библиотеку «Adafruit BME280»

Для чтения данных с BME280 нам понадобится библиотека «Adafruit BME280». Чтобы установить ее в IDE Arduino, проделайте следующее:

  1. Кликните тут, чтобы попасть на GitHub-страницу библиотеки, там нажмите на зеленую кнопку «Clone or download», а затем на «Download ZIP». Это должно загрузить на ваш компьютер ZIP-архив с библиотекой;
  2. Распакуйте его. У вас должна получиться папка «Adafruit-BME280-Library-master»;
  3. Переименуйте ее на «Adafruit_BME280_Library»;
  4. Переместите папку «Adafruit_BMPE280_Library» в папку «libraries», которая находится в папке, где установлена IDE Arduino;
  5. Наконец, снова откройте IDE Arduino;

Загрузить библиотеку «Adafruit BME280» в IDE Arduino можно и другим способом. Кликните в IDE Arduino на «Скетч» > «Подключить библиотеку» > «Управлять библиотеками...», введите в поле поиска «adafruit bme280», а затем установите библиотеку, которая появится в поисковой выдаче.

Устанавливаем библиотеку «Adafruit Sensor»

Чтобы воспользоваться библиотекой «Adafruit BME280», нам также понадобится установить в IDE Arduino библиотеку «Adafruit Sensor». Для этого проделайте следующее:

  1. Кликните тут, чтобы открыть GitHub-страницу библиотеки «Adafruit Sensor», там нажмите на зеленую кнопку «Clone or download», а затем на «Download ZIP». Это должно загрузить на ваш компьютер ZIP-архив с библиотекой.
  2. Распакуйте его. У вас должна получиться папка «Adafruit_Sensor-master».
  3. Переименуйте ее на «Adafruit_Sensor».
  4. Переместите папку «Adafruit_Sensor» в папку «libraries», которая находится в папке, где установлена IDE Arduino.
  5. Наконец, снова откройте IDE Arduino.

Задаем учетные данные сети

Строчки кода ниже нужно отредактировать, вписав в них данные своей сети: SSID и пароль. Найти эти строчки можно по соответствующему комментарию:

// здесь пишем учетные данные своей сети:
const char* ssid     = "";
const char* password = "";

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

Вначале мы подключаем библиотеку «WiFi» и другие библиотеки, необходимые для считывания данных с датчика BME280:

#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.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. Вы можете использовать либо шину HSPI, либо шину VSPI (подробнее смотрите в таблице ниже).

SPI MOSI MISO CLK CS
HSPI GPIO13 GPIO12 GPIO14 GPIO15
VSPI GPIO23 GPIO19 GPIO18 GPIO5

Далее в коде стоит строчка с переменной «SEALEVELPRESSURE_HPA».

#define SEALEVELPRESSURE_HPA (1013.25)

В ней будет храниться давление на уровне моря в гектопаскалях (эту единицу измерения вместе с барами используется для измерения давления). Это значение необходимо для расчета высоты при помощи давления – мы будем сравнить текущее давление с давлением на уровне моря и таким образом высчитывать приблизительную высоту. В строчке выше задано значение, которое подходит для определенного региона, поэтому для получения более точных результатов вам нужно заменить его на давление на уровне моря, корректное для вашего региона.

В следующей строчке создаем объект класса «Adafruit_BME280» под названием «bme». С ее помощью скетч будет по умолчанию коммуницировать с датчиком при помощи протокола I2C:

Adafruit_BME280 bme; // I2C

Если вы предпочитаете использовать SPI, вам нужно закомментировать строчку выше, а также раскомментировать одну из строчек ниже (в зависимости от того, какой SPI вы используете – аппаратным или программным).

// аппаратный SPI:
//Adafruit_BME280 bme(BME_CS);
// программный SPI:
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

Опять же, в следующих строчках внутри двойных кавычек вставляем SSID и пароль для своей сети:

const char* ssid = "";
const char* password = "";

Затем создаем веб-сервер на порте «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!");
            //  "Невозможно найти корректный датчик BME280, проверьте подключение!"
 while (1);
}

Фрагмент ниже запускает WiFi-коммуникацию при помощи функции WiFi.begin(ssid, password), потом ждет, пока соединение наладится, и печатает в монитор порта IP-адрес ESP32.

  // подключаемся к WiFi-сети при помощи SSID и пароля:
  Serial.print("Connecting to ");  //  "Подключение к"
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // печатаем локальный IP-адрес и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "WiFi подключен."
  Serial.println("IP address: ");  //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();

loop()

В блоке loop() программируем, что произойдет, когда новый клиент установит соединение с веб-сервером. ESP32 всегда прослушивает входящих клиентов при помощи этой строчки:

// начинаем прослушивать входящих клиентов:
WiFiClient client = server.available();

Получив запрос от клиента, сохраняем входящие данные. Далее идет цикл while(), который будет работать, пока клиент будет оставаться подключенным к серверу. Не рекомендуем менять фрагмент кода ниже (но если вы хорошо разбираетесь в том, как это все работает, то вперед):

if (client) {                       // если подключился новый клиент,     
    Serial.println("New Client.");  // печатаем сообщение
                                    // «Новый клиент.»
                                    // в мониторе порта;
    String currentLine = "";        // создаем строку для хранения
                                    // входящих данных от клиента;
    while (client.connected()) {    // цикл while() будет работать
                                    // все то время, пока клиент
                                    // будет подключен к серверу;
      if (client.available()) {     // если у клиента есть данные,
                                    // которые можно прочесть, 
        char c = client.read();     // считываем байт, а затем                 
        Serial.write(c);            // печатаем его в мониторе порта 
        header += c;
        if (c == '\n') {            // если этим байтом является
                                    // символ новой строки
          // если мы получим два символа новой строки подряд,
          // то это значит, что текущая строчка пуста;
          // это конец HTTP-запроса клиента,
          // а значит – пора отправлять ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются
            // с кода ответа (например, «HTTP/1.1 200 OK»)
            // и информации о типе контента
            // (чтобы клиент понимал, что получает);
            // в конце пишем пустую строчку:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                         //  "Соединение: отключено"
            client.println();

Показываем веб-страницу

Далее отправляем ответ клиенту, содержащий HTML-код для создания веб-страницы.

Веб-страница отправляется клиенту при помощи метода client.println(). Параметром у этой функции задается то, что нужно отправить клиенту.

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

// показываем веб-страницу с помощью этого HTML-кода:
            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-стили для таблицы: 
            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; }");
            
            // заголовок веб-страницы:
            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>");
Примечание

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

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

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

Отключение соединения

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

// очищаем переменную «header»:
header = "";
// отключаем соединение:
client.stop();

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

Необходимое оборудование

Схема

Примечание

На данной схеме используется плата ESP32 DEVKIT V1, если вы используете другую, сверьтесь с вашей распиновкой.

Мы воспользуемся протоколом I2C. Для этого BME280 нужно подключить к контактам SDA и SCL на плате ESP32 (см. схему ниже).

Код

/*********
  Руи Сантос
  Более подробно об этом проекте на: http://randomnerdtutorials.com  
*********/

// загружаем библиотеку для WiFi и другие библиотеки:
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

// при использовании 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
// аппаратный SPI:
//Adafruit_BME280 bme(BME_CS);
// программный SPI:
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

// ставим здесь учетные данные своей сети:
const char* ssid     = "";
const char* password = "";

// выставляем номер порта на «80»:
WiFiServer server(80);

// создаем переменную для хранения HTTP-запроса:
String header;

void setup() {
  Serial.begin(115200);
  bool status;

  // настройки по умолчанию
  // (вы также можете использовать объект библиотеки Wire):
  //status = bme.begin();  
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
               //  "Невозможно найти корректный датчик BME280,проверьте подключение!"
    while (1);
  }

  // подключаемся к WiFi-сети при помощи SSID и пароля:
  Serial.print("Connecting to ");  //  "Подключение к"
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // печатаем локальный IP-адрес и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "WiFi подключен."
  Serial.println("IP address: ");  //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  // начинаем прослушивать входящих клиентов:
  WiFiClient client = server.available();

  if (client) {                     // если подключился новый клиент,     
    Serial.println("New Client.");  // печатаем сообщение
                                    // «Новый клиент.»
                                    // в мониторе порта;
    String currentLine = "";        // создаем строку для хранения
                                    // входящих данных от клиента;
    while (client.connected()) {    // цикл while() будет работать
                                    // все то время, пока клиент
                                    // будет подключен к серверу;
      if (client.available()) {     // если у клиента есть данные,
                                    // которые можно прочесть, 
        char c = client.read();     // считываем байт, а затем                 
        Serial.write(c);            // печатаем его в мониторе порта 
        header += c;
        if (c == '\n') {            // если этим байтом является
                                    // символ новой строки
          // если мы получим два символа новой строки подряд,
          // то это значит, что текущая строчка пуста;
          // это конец HTTP-запроса клиента,
          // а значит – пора отправлять ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются
            // с кода ответа (например, «HTTP/1.1 200 OK»)
            // и информации о типе контента
            // (чтобы клиент понимал, что получает);
            // в конце пишем пустую строчку:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                         //  "Соединение: отключено"
            client.println();
            
            // показываем веб-страницу с помощью этого HTML-кода:
            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-стили для таблицы: 
            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; }");
            
            // заголовок веб-страницы:
            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>");
            
            // конец HTTP-ответа задается 
            // с помощью дополнительной пустой строки:
            client.println();
            // выходим из цикла while():
            break;
          } else {  // если получили символ новой строки,
                    // очищаем переменную «currentLine»
            currentLine = "";
          }
        } else if (c != '\r') {  // если получили любые данные,
                                 // кроме символа возврата каретки,
          currentLine += c;      // добавляем эти данные 
                                 // в конец строки «currentLine»
        }
      }
    }
    // очищаем переменную «header»:
    header = "";
    // отключаем соединение:
    client.stop();
    Serial.println("Client disconnected.");
                // "Клиент отключился.")
    Serial.println("");
  }
}

См.также

Внешние ссылки