ESP8266 и Arduino IDE. Часть 10a: IoT-сайт. Мониторинг температуры и влажности
Эта статья демонстрирует создание станции мониторинга окружающей среды, которая отображает данные датчиков как на LCD-дисплее, так и на веб-странице через WebSocket.
План проекта
Проект будет построен поэтапно:
Тестирование датчиков с базовым скетчем
Создание базового сайта с использованием WebSocket для данных датчиков
Улучшение сайта с циферблатами и графиками
Добавление LCD-дисплея
Интеграция WiFi Manager
Добавление синхронизации времени через NTP
Размещение проекта в корпусе
Эта статья покрывает пункты 1-2, последующие части (10b-10d) охватывают дополнительные функции.
Компоненты схемы
NodeMCU V0.9 модуль ESP8266
DHT11 датчик температуры/влажности (пин D6)
Фоторезистор LDR (пин A0)
Индикаторный светодиод (пин D5)
Пины I2C D1 и D2 зарезервированы для будущего LCD
Скетч 1: Тестирование схемы
Этот скетч тестирует схему и датчики, выводя показания в монитор порта. Для работы требуется библиотека Adafruit DHT (доступна через Library Manager или на GitHub).
//
// ESP8266 and the Arduino IDE Part 10: Environment monitor station
// ESP8266-10_sketch01_Circuit_Test
//
#include "DHT.h"
#define DHTPIN D6
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
byte const pin_LDR = A0;
byte const pin_LED = D5;
float humidity = 0;
float tempC = 0;
float tempF = 0;
int brightness = 0;
boolean DHTvaluesOK = false;
void setup()
{
pinMode(pin_LED, OUTPUT);
digitalWrite(pin_LED, LOW);
dht.begin();
Serial.begin(9600);
while (!Serial) {;}
Serial.println("");
Serial.println("STARTED");
}
void loop()
{
Serial.println(" ");
brightness = analogRead(pin_LDR);
Serial.print("Brightness = ");
Serial.print(brightness);
Serial.print("\t");
humidity = dht.readHumidity();
tempC = dht.readTemperature();
tempF = dht.readTemperature(true);
if (isnan(humidity) || isnan(tempC) || isnan(tempF))
{
DHTvaluesOK = false;
}
else
{
DHTvaluesOK = true;
}
if (DHTvaluesOK)
{
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(tempC);
Serial.print ("*C ");
Serial.print(tempF);
Serial.println("*F");
}
else
{
Serial.println("DHT11 ERROR");
}
digitalWrite(pin_LED, HIGH);
delay(100);
digitalWrite(pin_LED, LOW);
delay(3000);
}
Скетч 2: Шаблон WebSocket
//
// ESP8266 and the Arduino IDE Part 10: Environment monitor station
// ESP8266-10_sketch_02_WebSocket_Template
//
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = R"=====(
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
<meta charset='utf-8'>
<style>
body { font-size:100%;}
#main { display: table; width: 300px; margin: auto; padding: 10px; border: 3px solid blue; border-radius: 10px; text-align:center;}
p { font-size: 100%; }
</style>
<title>Websockets</title>
</head>
<body>
<div id='main'>
<h3>Websockets</h3>
<div id='content'>
</div>
</div>
</body>
<script>
var Socket;
function init()
{
Socket = new WebSocket('ws://' + window.location.hostname + ':81/');
Socket.onmessage = function(event) { processReceivedCommand(event); };
}
function processReceivedCommand(evt)
{
document.getElementById('rd').textContent = evt.data;
console.log(evt.data);
}
function sendText(data) { Socket.send(data); }
window.onload = function(e) { init(); }
</script>
</html>
)=====";
#include <ESP8266WiFi.h>
#include <WebSocketsServer.h>
WiFiServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
char ssid[] = "ssid";
char pass[]= "password";
void setup()
{
Serial.begin(9600);
Serial.println();
Serial.println("Serial started at 9600");
Serial.println();
Serial.print("Connecting to ");
Serial.println("ssid");
WiFi.begin(ssid,pass);
int count = 0;
while ( (WiFi.status() != WL_CONNECTED) && count < 17)
{
Serial.print(".");
delay(500);
count++;
}
Serial.println("");
if (WiFi.status() != WL_CONNECTED)
{
Serial.print("Failed to connect to ");
Serial.println(ssid);
while(1);
}
Serial.print("Connected to ");
Serial.println(WiFi.localIP());
server.begin();
Serial.println("Server started");
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
void loop()
{
webSocket.loop();
WiFiClient client = server.available();
if (client)
{
client.flush();
client.print( header );
client.print( html_1 );
Serial.println("New page served");
}
}
void webSocketEvent(byte num, WStype_t type, uint8_t * payload, size_t length)
{
Serial.println();
Serial.print("WStype = ");
Serial.println(type);
Serial.print("WS payload = ");
for(int i = 0; i < length; i++) {
Serial.print((char) payload[i]);
}
Serial.println();
}
HTML-шаблон веб-страницы
Шаблон использует элементы <span> с уникальными ID для адресного обновления значений. Символ ° представляет знак градуса.
Скетч 3: Базовый сайт с интеграцией датчиков
//
// ESP8266 and the Arduino IDE Part 10: Environment monitor station
// ESP8266-10_sketch03_Basic_Website
//
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html_1 = R"=====(
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
<meta charset='utf-8'>
<style>
body { font-size:100%;}
#main { display: table; width: 300px; margin: 20px auto; padding: 10px; border: 1px solid blue; border-radius: 10px; text-align:center;}
p { font-size: 120%; }
</style>
<title>ESP8266 Part 10</title>
</head>
<body>
<div id='main'>
<h1>ESP8266 monitor</h1>
<div id='content'>
<p>Humidity = <span id='humidity'>00</span>%</p>
<p>Temperature = <span id='tempC'>00</span>°C</p>
<p>Temperature = <span id='tempF'>00</span>°F</p>
<p>Brightness = <span id='brightness'>00</span></p>
<p>Data = <span id='recData'>00</span></p>
</div>
<br />
</div>
</body>
<script>
var Socket;
function init()
{
Socket = new WebSocket('ws://' + window.location.hostname + ':81/');
Socket.onmessage = function(event) { processReceivedCommand(event); };
}
function processReceivedCommand(evt)
{
var data = evt.data;
document.getElementById('recData').textContent = data;
var tmp = data.split('|');
document.getElementById('humidity').textContent = tmp[0];
document.getElementById('tempC').textContent = tmp[1];
document.getElementById('tempF').textContent = tmp[2];
document.getElementById('brightness').textContent = tmp[3];
}
function sendText(data) { Socket.send(data); }
window.onload = function(e) { init(); }
</script>
</html>
)=====";
#include <ESP8266WiFi.h>
#include <WebSocketsServer.h>
WiFiServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
char ssid[] = "mySSID";
char pass[]= "myPassword";
#include "DHT.h"
#define DHTPIN D6
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
byte const pinLED = D5;
byte const pinLDR = A0;
int brightness = 0;
float humidity = 0;
float tempC = 0;
float tempF = 0;
boolean DHTreadingsOK = false;
boolean updateWebpage = false;
long sensorUpdateFrequency = 5000;
long timeNow = 0;
long timePrev = 0;
void setup()
{
pinMode(pinLED, OUTPUT);
digitalWrite(pinLED, LOW);
dht.begin();
Serial.begin(9600);
while (!Serial) {;}
Serial.println();
Serial.println("Serial started at 9600");
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid,pass);
int count = 0;
digitalWrite(pinLED, HIGH);
while ( (WiFi.status() != WL_CONNECTED) && count < 17)
{
Serial.print(".");
count++;
delay(500);
}
digitalWrite(pinLED, LOW);
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("");
Serial.print("Failed to connect to ");
Serial.println(ssid);
while(1);
}
Serial.println("");
Serial.print("Connected. IP address = ");
Serial.println(WiFi.localIP());
server.begin();
Serial.println("Server started");
webSocket.begin();
Serial.println("websocket started");
Serial.println("");
Serial.println("");
webSocket.onEvent(webSocketEvent);
}
void loop()
{
webSocket.loop();
WiFiClient client = server.available();
if (client)
{
client.flush();
client.print( header );
client.print( html_1 );
Serial.println("New page served");
Serial.println("");
}
timeNow = millis();
if (timeNow - timePrev >= sensorUpdateFrequency)
{
timePrev = timeNow;
updateSensors();
}
}
void updateSensors()
{
brightness = analogRead(pinLDR);
humidity = dht.readHumidity();
tempC = dht.readTemperature();
tempF = dht.readTemperature(true);
if (isnan(humidity) || isnan(tempC) || isnan(tempF))
{
Serial.println("Error reading from the DHT11.");
}
else
{
String data = "";
data = String(data + byte(humidity) );
data = String(data + "|");
data = String(data + tempC);
data = String(data + "|");
data = String(data + tempF);
data = String(data + "|");
data = String(data + brightness);
webSocket.broadcastTXT(data);
Serial.println(data);
digitalWrite(pinLED, HIGH);
delay(50);
digitalWrite(pinLED, LOW);
}
}
void webSocketEvent(byte num, WStype_t type, uint8_t * payload, size_t length)
{
Serial.println("");
Serial.print("WStype = ");
Serial.println(type);
Serial.print("WS payload = ");
for(int i = 0; i < length; i++) {
Serial.print((char) payload[i]);
}
Serial.println();
}
Ключевые особенности:
Используется таймер на основе
millis()вместоdelay()для неблокирующего обновления датчиковДанные отправляются в формате, разделённом вертикальной чертой:
humidity|tempC|tempF|brightnessJavaScript разделяет полученные данные в массив для обновления DOM-элементов
Светодиод мигает при передаче данных
Важные технические концепции
WebSocket vs традиционный опрос: При использовании WebSocket после первоначальной отдачи веб-страницы все последующие обновления могут проходить через WebSocket, и может не потребоваться отдача другой страницы.
Формат данных: Формат значений, разделённых вертикальной чертой, позволяет методу JavaScript split() разбирать строку в массив без необходимости именованных идентификаторов.
Неблокирующие обновления: Использование millis() вместо delay() позволяет скетчу обрабатывать несколько задач одновременно (клиентские подключения и считывание датчиков).
Использование String: Строки (String) обычно нежелательны. Они потребляют память и могут вызвать различные проблемы, однако на ESP8266 памяти относительно много.