ESP8266 и Arduino IDE. Часть 10a: IoT-монитор

Примечание

Оригинал статьи: martyncurrey.com

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

Веб-интерфейс IoT-монитора

Компоненты проекта

Оборудование

  • NodeMCU V0.9 (плата на базе ESP8266)

  • Датчик температуры/влажности DHT11 (пин D6)

  • Фоторезистор LDR (пин A0)

  • Статусный светодиод (пин D5)

  • Макетная плата и провода

Программное обеспечение

  • Arduino IDE с поддержкой ESP8266

  • Библиотека Adafruit DHT sensor

  • Библиотека WebSocketsServer

  • JavaScript/HTML5 для веб-интерфейса

Плата NodeMCU

Конфигурация схемы

Проект использует пять основных подключений на NodeMCU:

  • Аналоговый пин A0 подключён к фоторезистору

  • Цифровой пин D5 управляет статусным светодиодом

  • Цифровой пин D6 подключён к датчику DHT11

  • Пины D2 и D1 (SDA/SCL) зарезервированы для будущей интеграции LCD

План макетной платы План макетной платы - детали Схема подключения Макетная плата Макетная плата - вид сверху

Стратегия реализации

Проект разбит на этапы:

  1. Тестирование датчиков — проверка работы DHT11 и LDR через Serial Monitor

  2. Базовый веб-сайт — создание простой веб-страницы с отображением данных в реальном времени

  3. Улучшение веб-сайта — добавление визуальных элементов: шкалы и графики

  4. Интеграция LCD — добавление локального дисплея

  5. Конфигурация WiFi — реализация WiFi Manager для гибкого подключения

Технический подход

Система использует неблокирующую архитектуру таймеров на основе millis() вместо delay(), что позволяет скетчу обрабатывать несколько параллельных задач: обслуживание веб-страниц, управление WebSocket-соединениями и чтение датчиков.

Формат данных: значения датчиков передаются через WebSocket в виде значений, разделённых символом |: humidity|tempC|tempF|brightness

Веб-страница получает эти данные и использует метод JavaScript split() для разбора значений в массив, а затем обновляет соответствующие HTML-элементы.

Скетч 1: Тестирование схемы

//
//  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();      // Read temperature as Celsius (the default)
  tempF = dht.readTemperature(true);  // Read temperature as Fahrenheit (isFahrenheit = true)

  // Check if any reads failed
  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");
  }

  // blink the LED to show the readings have been received.
  digitalWrite(pin_LED, HIGH);   delay(100);  digitalWrite(pin_LED, LOW);

  delay(3000);
}
Serial Monitor - тест датчиков

Скетч 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 10px 10px 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;
}

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();
}
Шаблон веб-страницы

Скетч 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 10px 10px 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>&#176;C</p>
      <p>Temperature = <span id='tempF'>00</span>&#176;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();
}
Serial Monitor - базовый сайт Базовый веб-сайт с данными Serial Monitor - данные

Часть 10a охватывает начальное тестирование схемы и базовую реализацию WebSocket. Последующие части расширяют интерфейс продвинутыми визуализациями и дополнительными функциями.