Aсинхронный WEB сервер на NodeMCU (ESP8266).

Наткнулся в сети на одну из интересных реализаций on-line термометра на чипе ESP8266 с отображением значений температуры и влажности в WEB браузере клиента. Вот оригинал статьи, которую я взял за основу, чтобы модернизировать свой on-line термометр, который, кстати, отлично работает и отправляет данные в базу данных MySQL. Кому интересно какова была первая версия термометра — смотрите запись: Термометр на датчиках DS18B20 и контроллере ESP8622 NodeMCU V3 с отображением данных в браузере и записью в базу данных MySQL. Вот так отображались значения в браузере при запросе клиента:

On-Line DS18b20 термометр на Node MCU
On-Line DS18b20 термометр на Node MCU

Конечно это не критично, но нужно было обновлять страничку, чтобы получать актуальные данные. При одновременном запросе от двух и более клиентов сервак оказывался недоступным. И вообще, возможности гораздо уже у этого Web сервера в сравнении с асинхронным.

Асинхронный Web сервер.

Для создания асинхронного web сервера на ESP8266 NodeMCU нам понадобится библиотека ESPAsyncWebServer library Можно ее скачать по ссылке и установить в Arduino IDE вручную, либо воспользоваться менеджером библиотек. К плюсам такого сервера можно отнести:

  • Возможность одновременной обработки запросов от множества клиентов;
  • Возможна работа с запросами;
  • И многое другое.

Полное описание библиотеки со всеми замечательными примерами доступно на страничке GitHub.

Итак, модернизация моего термометра, как Вы поняли, коснется в основном программной части. Зальем соответствующий скетч с асинхронным web сервером, «запиленным» под мой термометр. Также добавлю в термометр к существующим датчикам температуры DS18b20 два цифровых датчика температуры и влажности DHT22 для слежения за климатом на улице и в доме.

Железо, используемое в этом проекте:

  1. Плата NodeMCU v3 (ESP8266);
  2. Цифровые датчики температуры DS18B20 — 3 шт;
  3. Цифровые датчики (модули) температуры и влажности DHT22 — 2 шт;
  4. Резистор номиналом 4.7 КОм.

Соединим все датчики с Node MCU как показано на картинке:

Подключение датчиков ds18b20 и DHT22 к ESP8266 Node MCU
Подключение датчиков ds18b20 и DHT22 к ESP8266 Node MCU

В этой схеме используются модули DHT22 c тремя контактами со встроенным pull-up резистором. Поэтому резисторы для их соединения с платой NodeMCU не нужны. А вот для соединения датчиков ds18b20 резистор номиналом 4.7 КОм необходим!

Скетч on-line термометра с асинхронным web сервером.

Прежде всего необходимо подготовить Arduino IDE для работы с платой NodeMCU, а также установить необходимые библиотеки для работы с датчиками и создания асинхронного веб сервера. Об этом уже есть соответствующие записи в моем блокноте, прочтите их если не знаете как это сделать:

Остается установить еще пару библиотек для создания асинхронного веб сервера —  ESPAsyncTCP и ESPAsyncWebServer. Скачайте их с GitHub и распакуйте в папку библиотек с установленной Arduino IDE. Обычно это C:\Program Files (x86)\Arduino\libraries. После установки платы и библиотек перезапустим Arduino IDE и зальем в плату скетч:

#include <DHT.h>
#include <Adafruit_Sensor.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ESP8266HTTPClient.h>

#define ONE_WIRE_BUS D4 //Пин шины датчиков ds18b20
#define DHT_EXT D2 //пин уличного датчика 
#define DHT_INT D3 //пин домашнего датчика 
#define DHTTYPE DHT22 //тип датчика DHT

DHT dht_ext(DHT_EXT, DHTTYPE);
DHT dht_int(DHT_INT, DHTTYPE);

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

float tempSensor1, tempSensor2, tempSensor3;
uint8_t sensor1[8] = { 0x28, 0xAB, 0xA6, 0xDA, 0x5D, 0x14, 0x1, 0xED };//Веранда
uint8_t sensor2[8] = { 0x28, 0x19, 0x33, 0x37, 0x5D, 0x14, 0x1, 0x35 };//подача
uint8_t sensor3[8] = { 0x28, 0x2A, 0xE5, 0xBB, 0x5D, 0x14, 0x1, 0x88 };//обратка


//SSID и Password для доступа к wifi сети
const char* ssid = "Имя_сети_вифи";  // введите имя сети
const char* password = "Пароль_от_вифи";  //введите пароль от сети

float h_e;
float t_e;
float h_i;
float t_i;
float hic;

// создаем объект AsyncWebServer на 80ом порту
AsyncWebServer server(80);

unsigned long previousMillis = 0;    // храним крайнее обновление сенсоров

// считываем значение с датчиков каждые  10 секунд
const long interval = 10000;  

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta charset="UTF-8" meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <style>
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 1.8rem; 
       vertical-align:middle;
       padding-bottom: 1px;
        margin: 0px auto;
       }
    p { font-size: 1.8rem; }
    .units { font-size: 1.1rem; }
    .dht-labels{
      font-size: 1.4rem;
      vertical-align:middle;
      padding-bottom: 5px;
      margin: 0px auto;
    }
  </style>
</head>
<body>
  
  <h2>On-line термометр
  <h2><font color="0044FF">На улице:</font></h2>
   <p>
    <span class="dht-labels">Температура</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">°C</sup>
    <br>
    <span class="dht-labels">Влажность</span>
    <span id="humidity">%HUMIDITY%</span>
    <i class="fas fa-tint" style="color:#00add6;"></i>
    <br>
    <span class="dht-labels">Ощущается как</span>
    <span id="feelas">%FEELAS%</span>
    <sup class="units">°C</sup>
    <br>
    <span class="dht-labels">На веранде</span> 
    <span id="veranda">%VERANDA%</span>
    <sup class="units">°C</sup>
    <br>
    </p>
    <h2><font color="FF7700">В доме:</font></h2>
    <p>
    <span class="dht-labels">Температура</span>
    <span id="tempint">%TEMPINT%</span>
    <sup class="units">°C</sup>
    <br>
    <span class="dht-labels">Влажность</span>
    <span id="humint">%HUMINT%</span>
    <i class="fas fa-tint" style="color:#00add6;"></i>
    
    <br>
    <span class="dht-labels"><font color="FF0009">Подача</font></span>
    <span id="pod">%POD%</span>
    <sup class="units">°C</sup>
    <br>
    <span class="dht-labels"><font color="0089FF">Обратка</font></span>
    <span id="obr">%OBR%</span>
    <sup class="units">°C</sup>
    </p>
    
   <h2><a href=https://ivansmith.ru/temp.php><font color="#239500">История показаний</font></a></h2>
   
</body>
<script>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("feelas").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/feelas", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("tempint").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temp_int", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humint").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/hum_int", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("veranda").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/veranda", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("pod").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/pod", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("obr").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/obr", true);
  xhttp.send();
}, 10000 ) ;
</script>
</html>)rawliteral";

// заменяем placeholder'ы значениями с датчиков
String processor(const String& var){
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(t_e);
  }
  else if(var == "HUMIDITY"){
    return String(h_e);
  }
  else if(var == "FEELAS"){
    return String(hic);
  }
  else if(var == "TEMPINT"){
    return String(t_i);
  }
  else if(var == "HUMINT"){
    return String(h_i);
  }
  else if(var == "VERANDA"){
    return String(tempSensor1);
  }
  else if(var == "POD"){
    return String(tempSensor2);
  }
  else if(var == "OBR"){
    return String(tempSensor3);
  }
  return String();
}
 

void setup() {
  Serial.begin(115200);
  dht_ext.begin();
  dht_int.begin();
  sensors.begin();
  sensors.setResolution(sensor1, 10);
  sensors.setResolution(sensor2, 10);
  sensors.setResolution(sensor3, 10);
 
   
  //подключаемся к локальной wi-fi сети             
  Serial.println("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  //проверяем подключение к wi-fi сети
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());
    
  
 
 // перенаправления в соответствии с запросами
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(t_e).c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(h_e).c_str());
  });
  server.on("/feelas", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(hic).c_str());
  });
  server.on("/temp_int", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(t_i).c_str());
  });
  server.on("/hum_int", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(h_i).c_str());
  });
  server.on("/veranda", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(tempSensor1).c_str());
  });
  server.on("/pod", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(tempSensor2).c_str());
  });
  server.on("/obr", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(tempSensor3).c_str());
  });
  // Стартуем сервер
  server.begin();


}// setup


void loop()
{
  
      
        
unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // запоминаем время последнего опроса датчиков
    previousMillis = currentMillis;
  sensors.requestTemperatures();
  tempSensor1 = sensors.getTempC(sensor1); // Gets the values of the temperature
  tempSensor2 = sensors.getTempC(sensor2); // Gets the values of the temperature
  tempSensor3 = sensors.getTempC(sensor3); // Gets the values of the temperature
 
    // Считываем температуру в Цельсиях (по умолчанию)
    float newT = dht_ext.readTemperature();
    float newTI = dht_int.readTemperature();
    
    // Считываем температуру в фаренгейтах (если Fahrenheit = true)
    //float newT = dht.readTemperature(true);
    // если тампература не считалась, значения t_e t_i не меняем
    if (isnan(newT)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      t_e = newT;
      t_i = newTI;
      Serial.println(t_e);
    }
    if (isnan(newTI)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      t_i = newTI;
      Serial.println(t_i);
    }
    // Считываем значение относительной влажности
    float newH = dht_ext.readHumidity();
    float newHI = dht_int.readHumidity();
    // Если значения не удалось считать, то переменные h_e h_i не меняем 
    if (isnan(newH)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      h_e = newH;
      Serial.println(h_e);
    }
    if (isnan(newHI)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      h_i = newHI;
      Serial.println(h_i);
    }
    // считаем heat index (ощущается как) в цельсиях (isFahreheit = false)
    hic = dht_ext.computeHeatIndex(t_e, h_e, false);
    Serial.println(hic);
  }

}//loop

Прежде чем залить скетч в плату не забудьте заменить адреса датчиков ds18b20 из этого примера на адреса ваших датчиков:

uint8_t sensor1[8] = { 0x28, 0xAB, 0xA6, 0xDA, 0x5D, 0x14, 0x1, 0xED };

Также введите данные для подключения к вашей wi-fi сети:

const char* ssid = "Имя_сети_вифи";  // введите имя сети
const char* password = "Пароль_от_вифи";  //введите пароль от сети

Далее основная часть кода посвящена работе асинхронного веб сервера. При запросе «корневого» адреса происходит построение странички с подстановкой переменных, полученных от сенсоров. После подключения к wi-fi сети NodeMCU получила IP адрес 192.168.1.103. Его получение можно наблюдать в мониторе COM порта Arduino IDE. Введем этот адрес в WEB браузере и увидим как выглядит основная страница:

Асинхронный web сервер на Node MCU
Асинхронный web сервер на Node MCU

Небольшое отступление: на страничке можно наблюдать иконку в виде капельки. Она у нас подгружается с ресурса https://fontawesome.com/. На этом ресурсе вы можете создавать свои стили и иконки и использовать потом их в своих интернет проектах!

Классно, что конкретными HTTP запросами можно получать определенные данные:

Асинхронный веб сервер на esp8266, запросы
Асинхронный веб сервер на esp8266, запросы

Большой интерес к асинхронному серверу на ESP8266 у меня возник именно от возможности получения значения переменных по HTTP запросу. Это открывает возможности обмениваться данными между ардуино и им подобными и другими железками по TCP протоколу (связанных в одну локальную сеть или через интернет) . Вот пример работы php-curl скрипта, который запрашивает данные с этой платы в режиме on-line:

Температура воздуха: 17.20 °С
Влажность воздуха: 65.00%
Ощущается как: 16.67 °С

На этом запись о асинхронном Web сервере на ESP8266 NodeMCU заканчиваю. Есть вопросы — задавайте! Следите за блокнотом! До новых встреч!

Поделиться ссылкой:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *