ESP32/ESP8266: построение графиков показаний датчиков в реальном времени – веб-сервер
Узнайте, как строить графики показаний датчиков (температура, влажность и давление) на веб-сервере с использованием ESP32 или ESP8266 в Arduino IDE. ESP будет размещать веб-страницу с тремя графиками в реальном времени, на которые каждые 30 секунд добавляются новые данные.
Обзор проекта
В этом руководстве мы создадим асинхронный веб-сервер с использованием библиотеки ESPAsyncWebServer.
HTML-код для построения веб-страницы будет сохранён в файловой системе ESP32 или ESP8266 (LittleFS).
Мы будем отображать показания температуры, влажности и давления с датчика BME280 на графике, но вы можете модифицировать этот проект для отображения показаний любого другого датчика. Чтобы узнать больше о BME280, прочитайте наши руководства:
ESP32 с BME280 в Arduino IDE (давление, температура, влажность)
ESP8266 с BME280 в Arduino IDE (давление, температура, влажность)
Для построения графиков мы будем использовать библиотеку Highcharts. Мы создадим три графика: температура, влажность и давление во времени. Графики отображают максимум 40 точек данных, и новое значение добавляется каждые 30 секунд, но вы можете изменить эти значения в своём коде.
Видеодемонстрация
Необходимые условия
Перед тем как продолжить проект, убедитесь, что вы выполнили все условия в этом разделе для успешной компиляции кода.
1. Установка платы ESP в Arduino IDE
Мы будем программировать ESP32 и ESP8266 с помощью Arduino IDE. Поэтому у вас должно быть установлено дополнение ESP32 или ESP8266. Следуйте одному из следующих руководств для установки дополнения ESP:
2. Плагин для загрузки файловой системы
Для загрузки HTML-файла во флеш-память ESP32 и ESP8266 мы будем использовать плагин для Arduino IDE: Filesystem uploader. Следуйте одному из следующих руководств для установки плагина загрузчика файловой системы. Загрузчик совместим с обеими платами.
3. Установка библиотек
Для создания веб-сервера вам необходимо установить следующие библиотеки:
ESP32: установите библиотеки ESPAsyncWebServer и AsyncTCP (от ESP32Async).
ESP8266: установите библиотеки ESPAsyncWebServer и ESPAsyncTCP (от ESP32Async).
Вы можете установить эти библиотеки в менеджере библиотек Arduino IDE. Перейдите в Sketch > Include Library > Manage Libraries и найдите названия библиотек.
Для получения показаний с модуля датчика BME280 вам необходимо установить следующие библиотеки:
Вы можете установить эти библиотеки через менеджер библиотек Arduino.
Необходимые компоненты
Для выполнения этого руководства вам понадобятся следующие компоненты:
Схема подключения
Модуль датчика BME280, который мы используем, взаимодействует по протоколу I2C, поэтому вам нужно подключить его к выводам I2C на ESP32 или ESP8266.
Подключение BME280 к ESP32
BME280 |
ESP32 |
|---|---|
SCK (вывод SCL) |
GPIO 22 |
SDI (вывод SDA) |
GPIO 21 |
Соберите вашу схему, как показано на следующей диаграмме.
Рекомендуемое чтение: Справочник по выводам ESP32
Подключение BME280 к ESP8266
BME280 |
ESP8266 |
|---|---|
SCK (вывод SCL) |
GPIO 5 |
SDI (вывод SDA) |
GPIO 4 |
Соберите вашу схему, как показано на следующей диаграмме, если вы используете плату ESP8266.
Рекомендуемое чтение: Справочник по выводам ESP8266
Организация файлов
Для создания веб-сервера вам понадобятся два разных файла. Скетч Arduino и HTML-файл. HTML-файл должен быть сохранён внутри папки с именем data в папке скетча Arduino, как показано ниже:
Создание HTML-файла
Создайте файл index.html со следующим содержимым или скачайте все файлы проекта здесь:
<!DOCTYPE HTML><html>
<!-- Rui Santos - Complete project details at https://RandomNerdTutorials.com
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. -->
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.highcharts.com/highcharts.js"></script>
<style>
body {
min-width: 310px;
max-width: 800px;
height: 400px;
margin: 0 auto;
}
h2 {
font-family: Arial;
font-size: 2.5rem;
text-align: center;
}
</style>
</head>
<body>
<h2>ESP Weather Station</h2>
<div id="chart-temperature" class="container"></div>
<div id="chart-humidity" class="container"></div>
<div id="chart-pressure" class="container"></div>
</body>
<script>
var chartT = new Highcharts.Chart({
chart:{ renderTo : 'chart-temperature' },
title: { text: 'BME280 Temperature' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
},
series: { color: '#059e8a' }
},
xAxis: { type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Temperature (Celsius)' }
//title: { text: 'Temperature (Fahrenheit)' }
},
credits: { enabled: false }
});
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime(),
y = parseFloat(this.responseText);
//console.log(this.responseText);
if(chartT.series[0].data.length > 40) {
chartT.series[0].addPoint([x, y], true, true, true);
} else {
chartT.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/temperature", true);
xhttp.send();
}, 30000 ) ;
var chartH = new Highcharts.Chart({
chart:{ renderTo:'chart-humidity' },
title: { text: 'BME280 Humidity' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
}
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Humidity (%)' }
},
credits: { enabled: false }
});
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime(),
y = parseFloat(this.responseText);
//console.log(this.responseText);
if(chartH.series[0].data.length > 40) {
chartH.series[0].addPoint([x, y], true, true, true);
} else {
chartH.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/humidity", true);
xhttp.send();
}, 30000 ) ;
var chartP = new Highcharts.Chart({
chart:{ renderTo:'chart-pressure' },
title: { text: 'BME280 Pressure' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
},
series: { color: '#18009c' }
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Pressure (hPa)' }
},
credits: { enabled: false }
});
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime(),
y = parseFloat(this.responseText);
//console.log(this.responseText);
if(chartP.series[0].data.length > 40) {
chartP.series[0].addPoint([x, y], true, true, true);
} else {
chartP.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/pressure", true);
xhttp.send();
}, 30000 ) ;
</script>
</html>
Давайте рассмотрим основные части для построения графика.
Сначала необходимо подключить библиотеку highcharts:
<script src="https://code.highcharts.com/highcharts.js"></script>
Для каждого графика нужно создать элемент <div> с уникальным идентификатором. В данном случае: chart-temperature, chart-humidity и chart-pressure.
<div id="chart-temperature" class="container"></div>
<div id="chart-humidity" class="container"></div>
<div id="chart-pressure" class="container"></div>
Для создания графиков и добавления данных мы используем JavaScript-код. Он должен быть помещён внутри тегов <script> и </script>.
Следующий фрагмент создаёт график температуры. Вы определяете идентификатор графика, можете задать заголовок, подписи осей и т.д.
var chartT = new Highcharts.Chart({
chart:{ renderTo : 'chart-temperature' },
title: { text: 'BME280 Temperature' },
series: [{
showInLegend: false,
data: []
}],
plotOptions: {
line: { animation: false,
dataLabels: { enabled: true }
},
series: { color: '#059e8a' }
},
xAxis: { type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: { text: 'Temperature (Celsius)' }
//title: { text: 'Temperature (Fahrenheit)' }
},
credits: { enabled: false }
});
Затем функция setInterval() добавляет точки на графики. Каждые 30 секунд она делает запрос на URL /temperature для получения показаний температуры от вашего ESP32 или ESP8266.
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var x = (new Date()).getTime(),
y = parseFloat(this.responseText);
//console.log(this.responseText);
if(chartT.series[0].data.length > 40) {
chartT.series[0].addPoint([x, y], true, true, true);
} else {
chartT.series[0].addPoint([x, y], true, false, true);
}
}
};
xhttp.open("GET", "/temperature", true);
xhttp.send();
}, 30000 ) ;
Остальные графики создаются аналогичным образом. Мы делаем запросы на URL /humidity и /pressure для получения показаний влажности и давления соответственно.
В скетче Arduino мы должны обработать, что происходит при получении этих запросов: мы должны отправить соответствующие показания датчиков.
Скетч Arduino
Скопируйте следующий код в Arduino IDE или скачайте все файлы проекта здесь. Затем вам нужно ввести данные вашей сети (SSID и пароль), чтобы код работал.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-plot-chart-web-server/
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.
*********/
#ifdef ESP32
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#else
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <FS.h>
#endif
#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*/
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_IWTH_YOUR_SSID";
const char* password = "REPLACE_IWTH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
String readBME280Temperature() {
// Read temperature as Celsius (the default)
float t = bme.readTemperature();
// Convert temperature to Fahrenheit
//t = 1.8 * t + 32;
if (isnan(t)) {
Serial.println("Failed to read from BME280 sensor!");
return "";
}
else {
Serial.println(t);
return String(t);
}
}
String readBME280Humidity() {
float h = bme.readHumidity();
if (isnan(h)) {
Serial.println("Failed to read from BME280 sensor!");
return "";
}
else {
Serial.println(h);
return String(h);
}
}
String readBME280Pressure() {
float p = bme.readPressure() / 100.0F;
if (isnan(p)) {
Serial.println("Failed to read from BME280 sensor!");
return "";
}
else {
Serial.println(p);
return String(p);
}
}
void setup(){
// Serial port for debugging purposes
Serial.begin(115200);
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);
}
// Initialize LittleFS
if(!LittleFS.begin()){
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// Print ESP32 Local IP Address
Serial.println(WiFi.localIP());
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html");
});
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", readBME280Temperature().c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", readBME280Humidity().c_str());
});
server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", readBME280Pressure().c_str());
});
// Start server
server.begin();
}
void loop(){
}
Как работает код
Давайте рассмотрим код и разберёмся, как он работает.
Подключение библиотек
Сначала подключаем необходимые библиотеки. Вы подключаете разные библиотеки в зависимости от используемой платы. Если вы используете ESP32, код загружает следующие библиотеки:
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
Если вы используете ESP8266, код загружает эти библиотеки:
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Создаём экземпляр для связи с датчиком BME280 по I2C:
Adafruit_BME280 bme; // I2C
Введите данные вашей сети в следующие переменные:
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Создаём объект AsyncWebServer на порту 80:
AsyncWebServer server(80);
Чтение температуры, влажности и давления
Затем мы создаём три функции: readBME280Temperature(), readBME280Humidity() и readBME280Pressure(). Эти функции запрашивают температуру, влажность и давление от датчика BME280 и возвращают показания в виде типа String.
String readBME280Temperature() {
// Read temperature as Celsius (the default)
float t = bme.readTemperature();
// Convert temperature to Fahrenheit
//t = 1.8 * t + 32;
if (isnan(t)) {
Serial.println("Failed to read from BME280 sensor!");
return "";
}
else {
Serial.println(t);
return String(t);
}
}
Инициализация BME280
В функции setup() инициализируем датчик:
status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
Инициализация LittleFS
Инициализируем файловую систему (LittleFS):
if(!LittleFS.begin()){
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
Подключение к Wi-Fi
Подключаемся к Wi-Fi и выводим IP-адрес в Serial Monitor:
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// Print ESP32 Local IP Address
Serial.println(WiFi.localIP());
Обработка запросов
Затем нам нужно обработать, что происходит, когда ESP получает запрос.
Когда поступает запрос на корневой URL, мы отправляем HTML-текст, сохранённый в LittleFS под именем index.html:
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html");
});
Когда мы получаем запрос на URL /temperature, /humidity или /pressure, вызываем функции, которые возвращают показания датчиков.
Например, если мы получаем запрос на URL /temperature, мы вызываем функцию readBME280Temperature(), которая возвращает температуру.
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", readBME280Temperature().c_str());
});
То же самое происходит для остальных показаний.
Наконец, запускаем сервер:
server.begin();
Поскольку это асинхронный веб-сервер, нам не нужно писать ничего в функции loop().
void loop(){
}
Загрузка кода и файлов
Сохраните код как ESP_Chart_Web_Server или скачайте все файлы проекта здесь. Перейдите в Sketch > Show Sketch Folder и создайте папку с именем data. Внутри этой папки сохраните ранее созданный HTML-файл.
Теперь вам нужно загрузить HTML-файл в файловую систему ESP32 или ESP8266.
Нажмите [Ctrl] + [Shift] + [P] в Windows или [Cmd] + [Shift] + [P] в MacOS, чтобы открыть палитру команд. Найдите команду Upload LittleFS to Pico/ESP8266/ESP32 и нажмите на неё.
Если у вас нет этой опции, значит вы не установили плагин загрузчика файловой системы. Ознакомьтесь с этим руководством.
Важно: убедитесь, что Serial Monitor закрыт перед загрузкой в файловую систему. В противном случае загрузка не удастся.
Затем загрузите код на вашу плату. Убедитесь, что выбрана правильная плата и COM-порт. Также убедитесь, что вы ввели данные вашей сети в код.
Когда всё успешно загружено, откройте Serial Monitor на скорости 115200 бод. Нажмите кнопку «EN/RST» на плате, и она должна вывести свой IP-адрес.
Демонстрация
Откройте браузер в вашей локальной сети и введите IP-адрес ESP32 или ESP8266. Вы должны увидеть три графика. Новая точка данных добавляется каждые 30 секунд, максимум до 40 точек. Новые данные продолжают отображаться на графиках, пока вкладка браузера открыта.
Вот пример графика влажности:
Вы можете выбрать каждую точку, чтобы увидеть точную метку времени.
Заключение
В этом руководстве вы научились создавать графики для отображения данных на веб-сервере. Вы можете модифицировать этот проект, чтобы создать столько графиков, сколько захотите, и использовать любые другие датчики.
Далее мы рекомендуем создать проект, который отображает графики из данных, хранящихся в вашей базе данных. Вот другие руководства, которые могут вам понравиться:
Визуализация показаний датчиков ESP32/ESP8266 из любой точки мира
ESP32/ESP8266: вставка данных в базу данных MySQL с помощью PHP и Arduino IDE
Узнайте больше об ESP32 и ESP8266 с нашими ресурсами:
—
Данная статья является переводом материала с сайта Random Nerd Tutorials. Все права на оригинальный контент принадлежат автору — Rui Santos.