ESP8266 NodeMCU: построение графиков показаний датчиков (несколько серий)
Этот проект показывает, как создать веб-сервер на плате ESP8266 NodeMCU для отображения показаний датчиков на графиках с несколькими сериями. В качестве примера мы построим график показаний температуры от четырёх различных датчиков DS18B20 на одном графике. Вы можете модифицировать проект для отображения любых других данных. Для построения графиков мы будем использовать JavaScript-библиотеку Highcharts.
У нас есть аналогичное руководство для платы ESP32:
Обзор проекта
Этот проект создаст веб-сервер на плате ESP8266 NodeMCU, который отображает показания температуры от четырёх датчиков DS18B20 на одном графике — графике с несколькими сериями. График отображает максимум 40 точек данных для каждой серии, а новые показания добавляются каждые 30 секунд. Вы можете изменить эти значения в своём коде.
Датчик температуры DS18B20
Датчик температуры DS18B20 — это цифровой датчик температуры с однопроводным интерфейсом. Это означает, что для связи с микроконтроллером ему требуется только одна линия данных.
Каждый датчик имеет уникальный 64-битный серийный номер, что означает, что вы можете подключить несколько датчиков к одному и тому же GPIO — как мы сделаем в этом руководстве. Узнайте больше о датчике температуры DS18B20:
Server-Sent Events
Показания автоматически обновляются на веб-странице с помощью Server-Sent Events (SSE).
Чтобы узнать больше о SSE с ESP8266, вы можете прочитать:
Файлы, сохранённые в файловой системе (LittleFS)
Чтобы наш проект был лучше организован и легче для понимания, мы сохраним файлы HTML, CSS и JavaScript для построения веб-страницы в файловой системе платы (LittleFS).
Узнайте больше об использовании LittleFS с ESP8266:
Необходимые условия
Убедитесь, что вы выполнили все необходимые условия из этого раздела, прежде чем переходить к проекту.
1. Установка платы ESP8266 в Arduino IDE
Мы будем программировать ESP8266 с помощью Arduino IDE. Поэтому у вас должно быть установлено дополнение ESP8266. Следуйте следующему руководству, если вы ещё этого не сделали:
Если вы хотите использовать VS Code с расширением PlatformIO, следуйте следующему руководству, чтобы узнать, как программировать ESP8266:
2. Плагин для загрузки файловой системы
Для загрузки файлов HTML, CSS и JavaScript во флеш-память ESP8266 (LittleFS) мы используем плагин для Arduino IDE: LittleFS Filesystem uploader. Следуйте следующему руководству для установки плагина загрузчика файловой системы:
Если вы используете VS Code с расширением PlatformIO, прочитайте следующее руководство, чтобы узнать, как загружать файлы в файловую систему:
3. Установка библиотек
Для создания этого проекта вам необходимо установить следующие библиотеки:
Вы можете установить эти библиотеки через менеджер библиотек Arduino. Перейдите в Sketch > Include Library > Manage Libraries и найдите библиотеки по названию.
Необходимые компоненты
Для выполнения этого руководства вам понадобятся следующие компоненты:
Компонент |
Количество |
|---|---|
ESP8266 (рекомендуем лучшие платы ESP8266) |
1 |
4 |
|
1 |
|
1 набор |
|
1 |
Если у вас нет четырёх датчиков DS18B20, вы можете использовать три или два. Также вы можете использовать другие датчики (вам нужно будет модифицировать код) или данные из любого другого источника (например, показания датчиков, полученные через MQTT, ESP-NOW или случайные значения — для экспериментов с этим проектом…).
Вы можете перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Схема подключения
Подключите четыре датчика DS18B20 к вашей плате.
Рекомендуемое чтение: Распиновка ESP8266: какие GPIO-выводы использовать?
Получение адресов датчиков DS18B20
Каждый датчик температуры DS18B20 имеет присвоенный серийный номер. Сначала вам нужно найти этот номер, чтобы соответствующим образом пометить каждый датчик. Это нужно для того, чтобы позже вы знали, с какого датчика считываете температуру.
Загрузите следующий код в ESP8266. Убедитесь, что выбраны правильная плата и COM-порт.
/*
* Rui Santos
* Complete Project Details https://randomnerdtutorials.com
*/
#include <OneWire.h>
// Based on the OneWire library example
OneWire ds(4); //data wire connected to GPIO 4
void setup(void) {
Serial.begin(115200);
}
void loop(void) {
byte i;
byte addr[8];
if (!ds.search(addr)) {
Serial.println(" No more addresses.");
Serial.println();
ds.reset_search();
delay(250);
return;
}
Serial.print(" ROM =");
for (i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(addr[i], HEX);
}
}
Подключайте только один датчик за раз, чтобы узнать его адрес (или последовательно добавляйте новый датчик), чтобы вы могли идентифицировать каждый из них по его адресу. Затем вы можете добавить физическую метку к каждому датчику.
Откройте Serial Monitor на скорости 115200 бод, нажмите встроенную кнопку RST/EN, и вы должны получить примерно следующее (но с другими адресами):
Снимите отметку с опции «Autoscroll», чтобы вы могли скопировать адреса. В нашем случае мы получили следующие адреса:
Sensor 1: 28 FF A0 11 33 17 3 96
Sensor 2: 28 FF B4 6 33 17 3 4B
Sensor 3: 28 FF 11 28 33 18 1 6B
Sensor 4: 28 FF 43 F5 32 18 2 A8
Организация файлов
Чтобы проект был организован и легче для понимания, мы создадим четыре файла для построения веб-сервера:
Скетч Arduino, который обрабатывает веб-сервер;
index.html: для определения содержимого веб-страницы;
style.css: для стилизации веб-страницы;
script.js: для программирования поведения веб-страницы — обработка ответов веб-сервера, событий, создание графика и т.д.
Вы должны сохранить файлы HTML, CSS и JavaScript внутри папки с именем data внутри папки скетча Arduino, как показано на предыдущей диаграмме. Мы загрузим эти файлы в файловую систему ESP8266 (LittleFS).
Вы можете скачать все файлы проекта:
HTML-файл
Скопируйте следующий код в файл index.html.
<!-- Complete project details: https://randomnerdtutorials.com/esp8266-nodemcu-plot-readings-charts-multiple/ -->
<!DOCTYPE html>
<html>
<head>
<title>ESP IOT DASHBOARD</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://code.highcharts.com/highcharts.js"></script>
</head>
<body>
<div class="topnav">
<h1>ESP WEB SERVER CHARTS</h1>
</div>
<div class="content">
<div class="card-grid">
<div class="card">
<p class="card-title">Temperature Chart</p>
<div id="chart-temperature" class="chart-container"></div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
HTML-файл для этого проекта очень прост. Он включает JavaScript-библиотеку Highcharts в заголовке HTML-файла:
<script src="https://code.highcharts.com/highcharts.js"></script>
Есть раздел <div> с идентификатором chart-temperature, где мы позже отобразим наш график.
<div id="chart-temperature" class="chart-container"></div>
CSS-файл
Скопируйте следующие стили в файл style.css.
/* Complete project details: https://randomnerdtutorials.com/esp8266-nodemcu-plot-readings-charts-multiple/ */
html {
font-family: Arial, Helvetica, sans-serif;
display: inline-block;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
p {
font-size: 1.4rem;
}
.topnav {
overflow: hidden;
background-color: #0A1128;
}
body {
margin: 0;
}
.content {
padding: 5%;
}
.card-grid {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.card {
background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}
.card-title {
font-size: 1.2rem;
font-weight: bold;
color: #034078
}
.chart-container {
padding-right: 5%;
padding-left: 5%;
}
JavaScript-файл (создание графиков)
Скопируйте следующий код в файл script.js. Вот список того, что делает этот код:
инициализация протокола event source;
добавление слушателя событий для события new_readings;
создание графика;
получение последних показаний датчиков из события new_readings и построение их на графике;
выполнение HTTP GET-запроса для получения текущих показаний датчиков при первом открытии веб-страницы.
// Complete project details: https://randomnerdtutorials.com/esp8266-nodemcu-plot-readings-charts-multiple/
// Get current sensor readings when the page loads
window.addEventListener('load', getReadings);
// Create Temperature Chart
var chartT = new Highcharts.Chart({
chart:{
renderTo:'chart-temperature'
},
series: [
{
name: 'Temperature #1',
type: 'line',
color: '#101D42',
marker: {
symbol: 'circle',
radius: 3,
fillColor: '#101D42',
}
},
{
name: 'Temperature #2',
type: 'line',
color: '#00A6A6',
marker: {
symbol: 'square',
radius: 3,
fillColor: '#00A6A6',
}
},
{
name: 'Temperature #3',
type: 'line',
color: '#8B2635',
marker: {
symbol: 'triangle',
radius: 3,
fillColor: '#8B2635',
}
},
{
name: 'Temperature #4',
type: 'line',
color: '#71B48D',
marker: {
symbol: 'triangle-down',
radius: 3,
fillColor: '#71B48D',
}
},
],
title: {
text: undefined
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: {
text: 'Temperature Celsius Degrees'
}
},
credits: {
enabled: false
}
});
//Plot temperature in the temperature chart
function plotTemperature(jsonValue) {
var keys = Object.keys(jsonValue);
console.log(keys);
console.log(keys.length);
for (var i = 0; i < keys.length; i++){
var x = (new Date()).getTime();
console.log(x);
const key = keys[i];
var y = Number(jsonValue[key]);
console.log(y);
if(chartT.series[i].data.length > 40) {
chartT.series[i].addPoint([x, y], true, true, true);
} else {
chartT.series[i].addPoint([x, y], true, false, true);
}
}
}
// Function to get current readings on the webpage when it loads for the first time
function getReadings(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
};
xhr.open("GET", "/readings", true);
xhr.send();
}
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var myObj = JSON.parse(e.data);
console.log(myObj);
plotTemperature(myObj);
}, false);
}
Получение показаний
Когда вы открываете веб-страницу в первый раз, мы запрашиваем у сервера текущие показания датчиков. В противном случае нам пришлось бы ждать поступления новых показаний датчиков (через Server-Sent Events), что может занять некоторое время в зависимости от интервала, установленного на сервере.
Добавьте слушатель событий, который вызывает функцию getReadings при загрузке веб-страницы.
// Get current sensor readings when the page loads
window.addEventListener('load', getReadings);
Объект window представляет открытое окно в браузере. Метод addEventListener() настраивает функцию, которая будет вызываться при возникновении определённого события. В данном случае мы вызовем функцию getReadings при загрузке страницы („load“), чтобы получить текущие показания датчиков.
Теперь давайте рассмотрим функцию getReadings. Мы отправим HTTP-запрос, чтобы ESP знал, что ему нужно отправить новые показания. Когда ESP получит этот запрос, он сбросит таймер для отправки нового события с показаниями.
Создайте новый объект XMLHttpRequest. Затем отправьте GET-запрос на сервер по URL /readings с помощью методов open() и send().
function getReadings() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/readings", true);
xhr.send();
}
Когда мы отправляем этот запрос, ESP отправит ответ. Поэтому нам нужно обработать то, что происходит при получении ответа. Мы будем использовать свойство onreadystatechange, которое определяет функцию, выполняемую при изменении свойства readyState. Свойство readyState содержит статус XMLHttpRequest. Ответ на запрос готов, когда readyState равно 4, а status равен 200.
readyState = 4 означает, что запрос завершён и ответ готов;
status = 200 означает «OK»
В нашем случае мы просто выведем ответ ESP в журнал (сообщение «OK»). Таким образом, запрос должен выглядеть примерно так:
function getReadings(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
};
xhr.open("GET", "/readings", true);
xhr.send();
}
Создание графика
Следующие строки создают графики с несколькими сериями.
// Create Temperature Chart
var chartT = new Highcharts.Chart({
chart:{
renderTo:'chart-temperature'
},
series: [
{
name: 'Temperature #1',
type: 'line',
color: '#101D42',
marker: {
symbol: 'circle',
radius: 3,
fillColor: '#101D42',
}
},
{
name: 'Temperature #2',
type: 'line',
color: '#00A6A6',
marker: {
symbol: 'square',
radius: 3,
fillColor: '#00A6A6',
}
},
{
name: 'Temperature #3',
type: 'line',
color: '#8B2635',
marker: {
symbol: 'triangle',
radius: 3,
fillColor: '#8B2635',
}
},
{
name: 'Temperature #4',
type: 'line',
color: '#71B48D',
marker: {
symbol: 'triangle-down',
radius: 3,
fillColor: '#71B48D',
}
},
],
title: {
text: undefined
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
yAxis: {
title: {
text: 'Temperature Celsius Degrees'
}
},
credits: {
enabled: false
}
});
Для создания нового графика используйте метод new Highcharts.Chart() и передайте в качестве аргумента свойства графика.
var chartT = new Highcharts.Chart({
В следующей строке определите, куда вы хотите поместить график. В нашем примере мы хотим разместить его в HTML-элементе с идентификатором chart-temperature — см. раздел HTML-файла.
chart:{
renderTo:'chart-temperature'
},
Затем определите параметры для серий. Следующие строки создают первую серию:
series: [
{
name: 'Temperature #1',
type: 'line',
color: '#101D42',
marker: {
symbol: 'circle',
radius: 3,
fillColor: '#101D42',
}
Свойство name определяет название серии. Свойство type определяет тип графика — в данном случае мы хотим построить линейный график. Свойство color относится к цвету линии — вы можете изменить его на любой желаемый цвет.
Далее определите свойства маркера. Вы можете выбрать из нескольких стандартных символов — square, circle, diamond, triangle, triangle-down. Вы также можете создать свои собственные символы. Свойство radius относится к размеру маркера, а fillColor — к цвету маркера. Существуют и другие свойства, которые вы можете использовать для настройки маркера — узнайте больше.
marker: {
symbol: 'circle',
radius: 3,
fillColor: '#101D42',
}
Создание остальных серий аналогично, но мы выбрали другие названия, маркеры и цвета.
Существует множество других параметров, которые вы можете использовать для настройки серий — ознакомьтесь с документацией по plotOptions.
Вы также можете определить заголовок графика — в данном случае, поскольку мы уже определили заголовок для графика в заголовке HTML-файла, мы не будем устанавливать заголовок здесь. Заголовок отображается по умолчанию, поэтому мы должны установить его в undefined.
title: {
text: undefined
},
Определите свойства для оси X — это ось, на которой мы будем отображать дату и время. Ознакомьтесь с дополнительными параметрами для настройки оси X.
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { second: '%H:%M:%S' }
},
Мы установили заголовок для оси Y. Смотрите все доступные свойства для оси Y.
yAxis: {
title: {
text: 'Temperature Celsius Degrees'
}
}
Часовой пояс
Если по какой-то причине после сборки проекта графики не отображают правильный часовой пояс, добавьте следующие строки в JavaScript-файл после второй строки:
Highcharts.setOptions({
time: {
timezoneOffset: -60 //Add your time zone offset here in seconds
}
});
Графики будут показывать время в UTC. Если вы хотите, чтобы они отображали время в вашем часовом поясе, вы должны установить параметр useUTC (который является параметром time) в false:
time:{
useUTC: false
},
Итак, добавьте это при создании графика следующим образом:
var chart = new Highcharts.Chart({
time:{
useUTC: false
},
(...)
Чтобы узнать больше об этом свойстве, перейдите по ссылке на документацию: https://api.highcharts.com/highcharts/time.useUTC
Наконец, установите параметр credits в false, чтобы скрыть информацию об авторских правах библиотеки Highcharts.
credits: {
enabled: false
}
Построение температур
Мы создали функцию plotTemperature(), которая принимает в качестве аргумента JSON-объект с показаниями температуры, которые мы хотим отобразить.
//Plot temperature in the temperature chart
function plotTemperature(jsonValue) {
var keys = Object.keys(jsonValue);
console.log(keys);
console.log(keys.length);
for (var i = 0; i < keys.length; i++){
var x = (new Date()).getTime();
console.log(x);
const key = keys[i];
var y = Number(jsonValue[key]);
console.log(y);
if(chartT.series[i].data.length > 40) {
chartT.series[i].addPoint([x, y], true, true, true);
} else {
chartT.series[i].addPoint([x, y], true, false, true);
}
}
}
Сначала мы получаем ключи нашего JSON-объекта и сохраняем их в переменной keys. Это позволяет нам пройти по всем ключам в объекте.
var keys = Object.keys(jsonValue);
Переменная keys будет массивом со всеми ключами в JSON-объекте. В нашем случае:
["sensor1", "sensor2", "sensor3", "sensor4"]
Это работает, если у вас JSON-объект с другим количеством ключей или с другими ключами. Затем мы пройдём по всем ключам (keys.length()), чтобы построить каждое из их значений на графике.
Значение x для графика — это метка времени.
var x = (new Date()).getTime()
Переменная key содержит текущий ключ в цикле. В первый раз при проходе через цикл переменная key равна «sensor1».
const key = keys[i];
Затем мы получаем значение ключа (jsonValue[key]) и сохраняем его как число в переменной y.
Наш график имеет несколько серий (индексация начинается с 0). Мы можем получить доступ к первой серии в графике температуры с помощью: chartT.series[0], что соответствует chartT.series[i] при первом проходе через цикл.
Сначала мы проверяем длину данных серии:
Если серия содержит более 40 точек: добавить и сдвинуть новую точку;
Или если серия содержит менее 40 точек: добавить новую точку.
Для добавления новой точки используйте метод addPoint(), который принимает следующие аргументы:
Значение для построения. Если это одно число, точка с этим значением y добавляется к серии. Если это массив, он будет интерпретирован как значения x и y. В нашем случае мы передаём массив со значениями x и y;
Опция перерисовки (boolean): установите в true для перерисовки графика после добавления точки.
Опция сдвига (boolean): если true, точка удаляется с начала серии при добавлении новой в конец. Когда длина графика превышает 40, мы устанавливаем опцию сдвига в true.
Опция withEvent (boolean): используется внутренне для запуска события addPoint серии — узнайте больше.
Итак, для добавления точки на график мы используем следующие строки:
if(chartT.series[i].data.length > 40) {
chartT.series[i].addPoint([x, y], true, true, true);
} else {
chartT.series[i].addPoint([x, y], true, false, true);
}
Обработка событий
Построение показаний на графиках при получении клиентом показаний через событие new_readings.
Создайте новый объект EventSource и укажите URL страницы, отправляющей обновления. В нашем случае это /events.
if (!!window.EventSource) {
var source = new EventSource('/events');
После создания экземпляра event source вы можете начать прослушивать сообщения от сервера с помощью addEventListener().
Это стандартные слушатели событий, как показано в документации AsyncWebServer.
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
Затем добавьте слушатель событий для new_readings.
source.addEventListener('new_readings', function(e) {
Когда новые показания доступны, ESP8266 отправляет событие (new_readings) клиенту. Следующие строки обрабатывают то, что происходит, когда браузер получает это событие.
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var myObj = JSON.parse(e.data);
console.log(myObj);
plotTemperature(myObj);
}, false);
По сути, выводим новые показания в консоль браузера, преобразуем данные в JSON-объект и строим показания на графике, вызывая функцию plotTemperature().
Скетч Arduino
Скопируйте следующий код в ваш Arduino IDE или в файл main.cpp, если вы используете PlatformIO.
/*********
Rui Santos
Complete instructions at https://RandomNerdTutorials.com/esp8266-nodemcu-plot-readings-charts-multiple/
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.
*********/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
#include <Arduino_JSON.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Create an Event Source on /events
AsyncEventSource events("/events");
// Json Variable to hold sensor readings
JSONVar readings;
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
// GPIO where the DS18B20 sensors are connected to
const int oneWireBus = 4;
// Setup a oneWire instance to communicate with OneWire devices (DS18B20)
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
// Address of each sensor
DeviceAddress sensor3 = { 0x28, 0xFF, 0xA0, 0x11, 0x33, 0x17, 0x3, 0x96 };
DeviceAddress sensor1 = { 0x28, 0xFF, 0xB4, 0x6, 0x33, 0x17, 0x3, 0x4B };
DeviceAddress sensor2 = { 0x28, 0xFF, 0x43, 0xF5, 0x32, 0x18, 0x2, 0xA8 };
DeviceAddress sensor4 = { 0x28, 0xFF, 0x11, 0x28, 0x33, 0x18, 0x1, 0x6B };
// Get Sensor Readings and return JSON object
String getSensorReadings(){
sensors.requestTemperatures();
readings["sensor1"] = String(sensors.getTempC(sensor1));
readings["sensor2"] = String(sensors.getTempC(sensor2));
readings["sensor3"] = String(sensors.getTempC(sensor3));
readings["sensor4"] = String(sensors.getTempC(sensor4));
String jsonString = JSON.stringify(readings);
return jsonString;
}
// Initialize LittleFS
void initLittleFS() {
if (!LittleFS.begin()) {
Serial.println("An error has occurred while mounting LittleFS");
}
else{
Serial.println("LittleFS mounted successfully");
}
}
// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}
void setup() {
// Serial port for debugging purposes
Serial.begin(115200);
initWiFi();
initLittleFS();
// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html", "text/html");
});
server.serveStatic("/", LittleFS, "/");
// Request for the latest sensor readings
server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
lastTime = lastTime + timerDelay;
request->send(200, "text/plain", "OK!");
});
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
// Start server
server.begin();
}
void loop() {
if ((millis() - lastTime) > timerDelay) {
// Send Events to the client with the Sensor Readings Every 10 seconds
events.send("ping",NULL,millis());
events.send(getSensorReadings().c_str(),"new_readings" ,millis());
lastTime = millis();
}
}
Как работает код
Давайте рассмотрим код и посмотрим, как он работает для отправки показаний клиенту с помощью server-sent events.
Подключение библиотек
Библиотеки OneWire и DallasTemperature необходимы для взаимодействия с датчиками температуры DS18B20.
#include <OneWire.h>
#include <DallasTemperature.h>
Библиотеки ESP8266WiFi, ESPAsyncWebServer и ESPAsyncTCP используются для создания веб-сервера.
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
Файлы HTML, CSS и JavaScript для построения веб-страницы сохранены в файловой системе ESP8266 (LittleFS). Поэтому нам также нужно подключить библиотеку LittleFS.
#include "LittleFS.h"
Вам также нужно подключить библиотеку Arduino_JSON, чтобы упростить работу с JSON-строками.
#include <Arduino_JSON.h>
Сетевые учётные данные
Вставьте ваши сетевые учётные данные в следующие переменные, чтобы ESP8266 мог подключиться к вашей локальной сети через Wi-Fi.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
AsyncWebServer и AsyncEventSource
Создайте объект AsyncWebServer на порту 80.
AsyncWebServer server(80);
Следующая строка создаёт новый источник событий на /events.
AsyncEventSource events("/events");
Объявление переменных
Переменная readings — это JSON-переменная для хранения показаний датчиков в формате JSON.
JSONVar readings;
Переменные lastTime и timerDelay будут использоваться для обновления показаний датчиков каждые X секунд. В качестве примера мы будем получать новые показания каждые 30 секунд (30000 миллисекунд). Вы можете изменить это время задержки в переменной timerDelay.
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
Датчики DS18B20
Датчики температуры DS18B20 подключены к GPIO 4 (D2).
// GPIO where the DS18B20 sensors are connected to
const int oneWireBus = 4;
Настройте экземпляр oneWire для связи с устройствами OneWire (DS18B20):
OneWire oneWire(oneWireBus);
Передайте нашу ссылку oneWire датчику Dallas Temperature:
DallasTemperature sensors(&oneWire);
Вставьте адреса ваших датчиков DS18B20 в следующие строки (проверьте этот раздел, если у вас нет адресов ваших датчиков):
// Address of each sensor
DeviceAddress sensor3 = { 0x28, 0xFF, 0xA0, 0x11, 0x33, 0x17, 0x3, 0x96 };
DeviceAddress sensor1 = { 0x28, 0xFF, 0xB4, 0x6, 0x33, 0x17, 0x3, 0x4B };
DeviceAddress sensor2 = { 0x28, 0xFF, 0x43, 0xF5, 0x32, 0x18, 0x2, 0xA8 };
DeviceAddress sensor4 = { 0x28, 0xFF, 0x11, 0x28, 0x33, 0x18, 0x1, 0x6B };
Получение показаний DS18B20
Для получения показаний от датчиков температуры DS18B20 сначала нужно вызвать метод requestTemperatures() для объекта sensors. Затем используйте функцию getTempC() и передайте в качестве аргумента адрес датчика, с которого вы хотите получить температуру — это получает температуру в градусах Цельсия.
Примечание: если вы хотите получить температуру в градусах Фаренгейта, используйте вместо этого функцию getTempF().
Наконец, сохраните показания в JSON-строку (переменная jsonString) и верните эту переменную.
// Get Sensor Readings and return JSON object
String getSensorReadings(){
sensors.requestTemperatures();
readings["sensor1"] = String(sensors.getTempC(sensor1));
readings["sensor2"] = String(sensors.getTempC(sensor2));
readings["sensor3"] = String(sensors.getTempC(sensor3));
readings["sensor4"] = String(sensors.getTempC(sensor4));
String jsonString = JSON.stringify(readings);
return jsonString;
}
Инициализация LittleFS
Функция initLittleFS() инициализирует файловую систему LittleFS:
void initLittleFS() {
if (!LittleFS.begin()) {
Serial.println("An error has occurred while mounting LittleFS");
}
else{
Serial.println("LittleFS mounted successfully");
}
}
Инициализация WiFi
Функция initWiFi() инициализирует Wi-Fi и выводит IP-адрес в Serial Monitor.
// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}
setup()
В setup() инициализируйте Serial Monitor, Wi-Fi и файловую систему.
Serial.begin(115200);
initWiFi();
initLittleFS();
Обработка запросов
Когда вы обращаетесь к IP-адресу ESP8266 по корневому URL /, отправляется текст, хранящийся в файле index.html, для построения веб-страницы.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.html", "text/html");
});
Обслуживание других статических файлов, запрашиваемых клиентом (style.css и script.js).
server.serveStatic("/", LittleFS, "/");
Когда вы получаете запрос по URL /readings, сбросьте таймер — таким образом, условие if в loop() станет истинным и ESP8266 отправит новые показания через SSE.
// Request for the latest sensor readings
server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){
lastTime = lastTime + timerDelay;
request->send(200, "text/plain", "OK!");
});
Мы отправляем ответ с OK, чтобы на стороне клиента мы знали, что ESP8266 получил запрос.
Серверный источник событий
Настройте источник событий на сервере.
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
Наконец, запустите сервер.
server.begin();
loop()
В loop() отправляйте события в браузер с новейшими показаниями датчиков для обновления веб-страницы каждые 30 секунд.
if ((millis() - lastTime) > timerDelay) {
// Send Events to the client with the Sensor Readings Every 10 seconds
events.send("ping",NULL,millis());
events.send(getSensorReadings().c_str(),"new_readings" ,millis());
lastTime = millis();
}
Используйте метод send() для объекта events и передайте в качестве аргумента контент, который вы хотите отправить, и имя события. В данном случае мы хотим отправить JSON-строку, возвращаемую функцией getSensorReadings(). Имя события — new_readings.
Загрузка кода и файлов
После ввода ваших сетевых учётных данных сохраните код. Перейдите в Sketch > Show Sketch Folder и создайте папку с именем data.
Внутри этой папки вы должны сохранить файлы HTML, CSS и JavaScript.
Затем загрузите код на вашу плату ESP8266. Убедитесь, что выбраны правильная плата и COM-порт. Также убедитесь, что вы добавили свои сетевые учётные данные и адреса датчиков в код.
После загрузки кода вам нужно загрузить файлы в файловую систему.
Нажмите [Ctrl] + [Shift] + [P] на Windows или [Cmd] + [Shift] + [P] на MacOS, чтобы открыть палитру команд. Найдите команду Upload LittleFS to Pico/ESP8266/ESP32 и нажмите на неё.
Если у вас нет этой опции, это потому что вы не установили плагин загрузчика файловой системы. Ознакомьтесь с этим руководством.
Когда всё успешно загружено, откройте Serial Monitor на скорости 115200 бод. Нажмите кнопку ESP8266 EN/RST, и он должен вывести IP-адрес ESP8266.
Демонстрация
Откройте ваш браузер и введите IP-адрес ESP8266. Вы должны получить доступ к веб-странице, которая показывает показания датчиков. Подождите некоторое время, пока не будет собрано несколько точек данных.
Вы можете выбрать точку, чтобы увидеть её значение и метку времени.
Заключение
В этом руководстве вы узнали, как создавать графики с несколькими сериями для отображения температуры от нескольких датчиков DS18B20. Вы можете модифицировать этот проект, чтобы создать столько графиков и серий, сколько захотите, и отображать данные от любых других датчиков или источников.
Вам также может быть интересно: ESP32/ESP8266: построение графиков показаний датчиков в реальном времени — веб-сервер
Узнайте больше о ESP8266 с помощью наших ресурсов: