Đo nhiệt độ & độ ẩm DHT22/DHT11 ESP32 trên WebServer cùng Arduino IDE

Trong dự án này, IoTZone sẽ hướng dẫn bạn cách xây dựng một WebServer để đo và hiển thị nhiệt độ, độ ẩm DHT22/DHT11 ESP32, sử dụng phần mềm Arduino IDE.

Đo nhiệt độ & độ ẩm DHT22/DHT11 ESP32 trên WebServer cùng Arduino IDE
Đo nhiệt độ & độ ẩm DHT22/DHT11 ESP32 trên Web Server cùng Arduino IDE

Web Server mà mình xây dựng sẽ tự động cập nhật thông tin nhiệt độ, độ ẩm theo thời gian thực mà không cần phải F5 hoặc làm mới trang Web.

Qua dự án này, bạn sẽ học được cách:

  • Đọc thông tin nhiệt độ, độ ẩm từ cảm biến DHT11 hoặc DHT22
  • Xây dựng Web Server hiển thị thông tin nhiệt độ bằng thư viện ESPAsyncWebServer 
  • Tự động cập nhật thông tin nhiệt độ, độ ẩm theo thời gian thực (real time) mà không cần phải F5, làm mới trang

Nào, cùng thực hành dự án hiển thị nhiệt độ, độ ẩm DHT22/DHT11 ESP32 bên dưới nhé!

Giới thiệu về thư viện Asynchronous Web Server

Để xây dựng một máy chủ Web, chúng ta cần phải dùng đến thư viện Asynchronous Web Server. Theo mình thấy thì việc xây dựng máy chủ web không đồng bộ như vậy sẽ có nhiều ưu điểm vượt trội, được trình bày rõ trong trang GitHub của thư viện, ví dụ:

  • Cho phép xử lý đồng thời nhiều kết nối cho tới máy chủ
  • Sau khi gửi phản hồi, hệ thống sẽ lập tức sẵn sàng để xử lý cho các yêu cầu khác trong khi máy chủ đang xử lý việc phản hồi
  • Cung cấp các công cụ đơn giản để xử lý mẫu 

Chuẩn bị linh kiện

Để thực hiện dự án này, bạn cần chuẩn bị các thiết bị điện tử sau:

  • Mạch ESP32
  • Cảm biến nhiệt độ & độ ẩm DHT22 hoặc DHT11
  • Điện trở 4,7k Ohm
  • Breadboard
  • Dây Jumper
Chuẩn bị linh kiện DHT22, DHT11 ESP32

Kết nối

Hãy kết nối cảm biến DHT22 hoặc DHT11 với mạch ESP32 theo như hình dưới (sử dụng chân GPIO 27). 

Kết nối thiết bị DHT22 DHT11 ESP32

Ngoài ra, bạn có thể kết nối với bất kỳ chân tín hiệu Digital nào khác trên mạch. Sơ đồ trên có thể dùng cho cả cảm biến DHT22 lẫn DHT11.

Lưu ý: 

  • Trong sơ đồ trên, mình sử dụng mạch ESP32  DEVKIT V1 – sở hữu 36 chân GPIO. Nếu bạn sử dụng mạch khác, vui lòng xem sơ đồ chân của bo mạch trước để kết nối đúng chân Digital nhé!
  • Cảm biến DHT thường chỉ có ba chân. Trong đó, mỗi chân đều phải dán nhãn để bạn biết cách nối dây. Đa số các môđun này đều đã có sẵn điện trở bên trong, nên chúng ta không cần phải nối thêm điện trở này vào mạch.

Tải thư viện

Để thực hành dự án này, bạn cần 4 thư viện sau:

Dưới đây, mình sẽ hướng dẫn bạn cách cài đặt các thư viện này:

Thư viện cảm biến DHT

  1. Tải thư viện cảm biến DHT qua link này 
  2. Giải nén tập tin .zip vừa tải về
  3. Đổi tên thư mục từ DHT-sensor-library-master thành DHT_sensor
  4. Di chuyển thư mục DHT_sensor vào thư mục thư viện cài đặt Arduino IDE
  5. Mở Arduino IDE và sử dụng thư viện

Thư viện Adafruit Unified Sensor Driver

  1. Tải thư viện qua link này 
  2. Giải nén tập tin .zip vừa tải về
  3. Đổi tên thư mục từ Adafruit_sensor-master thành Adafbean_sensor
  4. Di chuyển thư mục Adafbean_sensor vào thư mục thư viện cài đặt Arduino IDE
  5. Mở Arduino IDE và sử dụng thư viện

Thư viện ESPAsyncWebServer

  1. Tải thư viện qua link này 
  2. Giải nén tập tin .zip vừa tải về
  3. Đổi tên thư mục từ ESPAsyncWebServer-master thành ESPAsyncWebServer
  4. Di chuyển thư mục ESPAsyncWebServer vào thư mục thư viện cài đặt Arduino IDE
  5. Mở Arduino IDE và sử dụng thư viện

Thư viện Async TCP cho mạch ESP32

  1. Tải thư viện qua link này 
  2. Giải nén tập tin .zip vừa tải về
  3. Đổi tên thư mục từ AsyncTCP-master thành AsyncTCP
  4. Di chuyển thư mục AsyncTCP-master vào thư mục thư viện cài đặt Arduino IDE
  5. Mở Arduino IDE và sử dụng thư viện

Lập trình

Chúng ta sử dụng Arduino IDE để lập trình mạch ESP32, do đó, bạn cần cài đặt ESP32 trong phần mềm của mình theo hướng dẫn sau (nếu bạn đã cài thì có thể bỏ qua bước này): Cách lập trình ESP32 bằng Arduino IDE (Windows, Linux, Mac OS X)

Upload đoạn chương trình sau vào trong Arduino IDE của bạn và thay đổi mạng WiFi, sau đó khởi chạy nhé! 

// Import required libraries
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

#define DHTPIN 27     // Digital pin connected to the DHT sensor

// Uncomment the type of sensor in use:
//#define DHTTYPE    DHT11     // DHT 11
#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

String readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {    
    Serial.println("Failed to read from DHT sensor!");
    return "--";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}

String readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return "--";
  }
  else {
    Serial.println(h);
    return String(h);
  }
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <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: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>
<body>
  <h2>ESP32 DHT Server</h2>
  <p>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  <p>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">&percnt;</sup>
  </p>
</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 ) ;
</script>
</html>)rawliteral";

// Replaces placeholder with DHT values
String processor(const String& var){
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return readDHTTemperature();
  }
  else if(var == "HUMIDITY"){
    return readDHTHumidity();
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  dht.begin();
  
  // 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_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTTemperature().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTHumidity().c_str());
  });

  // Start server
  server.begin();
}
 
void loop(){
  
}

Chèn thông tin mạng của bạn tại đoạn code sau (gồm tên WiFi tại SSID và mật khẩu WiFi tại password):

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Giải thích chương trình

Mình sẽ giải thích chi tiết cho bạn cách hoạt động của đoạn chương trình trên, cụ thể:

Khởi tạo thư viện

Đầu tiên chúng ta cần khởi tạo các thư viện cần dùng (bao gồm 4 thư viện):

  • WiFi, ESPAsyncWebServer và ESPAsyncTCP: Dùng để xây dựng Web Server
  • Adafruit_Sensor và DHT: Đọc thông tin của cảm biến DHT22 hoặc DHT11
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include <ESPAsyncTCP.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

Khai báo mạng của bạn

Chèn thông tin mạng của bạn vào các biến sau, cho phép mạch ESP32 có thể kết nối mạng:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Tạo các biến

Khai báo chân GPIO mà bạn đã kết nối cảm biến DHT, trong trường hợp này là GPIO 27:

#define DHTPIN 27  // Digital pin connected to the DHT sensor

Chọn loại cảm biến DHT mà bạn đang dùng. Trong dự án này mình đã sử dụng DHT22:

#define DHTTYPE DHT22   // DHT 22 (AM2302)

Khởi tạo một đối tượng DHT với chân GPIO và loại cảm biến mà bạn đã khai báo trước đó:

DHT dht(DHTPIN, DHTTYPE);

Tạo một đối tượng  AsyncWebServer ở cổng 80:

AsyncWebServer server(80);

Hàm đọc nhiệt độ và độ ẩm trên cảm biến DHT22/DHT11 ESP32

Chúng ta cần tạo hai hàm: một hàm để đọc nhiệt độ và một hàm để đọc độ ẩm:

String readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) { 
    Serial.println("Failed to read from DHT sensor!");
    return "--";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}

Việc đọc thông tin từ cảm biến sẽ rất đơn giản khi chúng ta sử dụng readTemperature() và readHumidity() trên đối tượng DHT.

float t = dht.readTemperature();
float h = dht.readHumidity();

Trong trường hợp không nhận được thông tin dữ liệu từ cảm biến, chúng ta sẽ sử dụng câu lệnh điều kiện để thể hiện dấu gạch ngang trên màn hình:

if (isnan(t)) {
  Serial.println("Failed to read from DHT sensor!");
  return "--";
}

Các thông tin đọc được sẽ trả về dưới dạng chuỗi:

return String(t);

Ở chế độ mặc định, chúng ta sẽ đọc nhiệt độ trên thang đo độ C. Nếu bạn muốn đổi thành thang đo độ F, hãy ghi chú nhiệt độ cho độ C và bỏ ghi chú nhiệt độ theo độ F,chúng ta sẽ có kết quả như sau:

//float t = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
float t = dht.readTemperature(true);

Xây dựng Web Server

Web Server sau khi tạo xong sẽ có giao diện như sau:

Webserver với DHT22 và DHT11 ESP32
Webserver với DHT22 và DHT11 ESP32

Như hình trang trang web bao gồm một tiêu đề và hai dòng chữ thể hiện nhiệt độ và độ ẩm. Ngoài ra, mình còn sử dụng thêm hai icon để trang web trở nên đẹp mắt hơn.

Bây giờ mình sẽ giải thích chi tiết cho bạn cách tạo một web server như trên nhé! Mình sử dụng các đoạn test HTML và lưu trữ chúng trong biến  index_html. 

Dưới đây là giải thích chi tiết về đoạn textHTML của mình:

Thẻ <meta> cho phép trang web của bạn có thể sử dụng trên bất kỳ trình duyệt nào 

<meta name="viewport" content="width=device-width, initial-scale=1">

Thẻ <link> cho phép tải các biểu tượng từ trang web Fontawesome:

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

Styles

Giữa 2 thẻ  <style></style>, mình đặt các đoạn code CSS để tạo style cho web:

<style>
  html {
    font-family: Arial;
    display: inline-block;
    margin: 0px auto;
    text-align: center;
  }
  h2 { font-size: 3.0rem; }
  p { font-size: 3.0rem; }
  .units { font-size: 1.2rem; }
  .dht-labels{
    font-size: 1.5rem;
    vertical-align:middle;
    padding-bottom: 15px;
  }
</style>

Về cơ bản, chúng ta sẽ cấu hình 1 page HTML để hiển thị văn bản, với font chữ Arial và được căn giữa:

html {
  font-family: Arial;
  display: inline-block;
  margin: 0px auto;
  text-align: center;
}

Mình thiết lập kích cỡ font chữ cho từng đoạn văn bản, bao gồm H2 đoạn văn và các đơn vị unit:

h2 { font-size: 3.0rem; }
p { font-size: 3.0rem; }
.units { font-size: 1.2rem; }

Chương trình tạo các label:

dht-labels{
  font-size: 1.5rem;
  vertical-align:middle;
  padding-bottom: 15px;
}

Tất cả các thẻ này đều phải nằm giữa các thẻ <head> và </head>. Các thẻ này sẽ không hiển thị trực tiếp nội dungcho người dùng, ví dụ như thẻ <meta>, <link> và style.

Nội dung trong HTML

Chúng ta sẽ thêm nội dung của trang web bên trong thẻ <body></body>.

Bên trong <h2></h2> là tiêu đề của trang web. Hiện tại mình đang để là ESP32 DHT Server và bạn có thể thay đổi thành bất kỳ tiêu đề nào bạn thích:

<h2>ESP32 DHT Server</h2>

Sau đó, chúng ta có hai đoạn văn để lần lượt hiển thị thông tin nhiệt độ và độ ẩm. Mỗi đoạn văn sẽ được phân cách với nhau bằng thẻ <p> và </p>:

  • Thông tin nhiệt độ:
<p>
  <i class="fas fa-thermometer-half" style="color:#059e8a;"</i> 
  <span class="dht-labels">Temperature</span> 
  <span id="temperature">%TEMPERATURE%</span>
  <sup class="units">°C</sup>
</p>
  • Thông tin độ ẩm:
<p>
  <i class="fas fa-tint" style="color:#00add6;"></i> 
  <span class="dht-labels">Humidity</span>
  <span id="humidity">%HUMIDITY%</span>
  <sup class="units">%</sup>
</p>

Thẻ <i> là nơi hiển thị các icon đẹp mắt từ fontawesome.

Hiển thị icon

Để hiển thị icon trang trí trên trang web, bạn hãy truy cập vào Font Awesome Icons website.

Chọn icon cho Webserver DHT22 DHT11 ESP32
Chọn icon cho Webserver DHT22 DHT11 ESP32

Tìm bằng tiếng Anh và nhấn vào bất kỳ icon nào mà bạn thích. Sau đó copy đoạn mã HTML của chúng, ví dụ:

Chọn icon cho Webserver DHT22 DHT11 ESP32

Để thay đổi màu sắc của icon, bạn chỉ cần thêm đoạn mã màu như bên dưới:

<i class="fas fa-tint" style="color:#00add6;"></i> 

Tiếp tục làm việc với code HTML như bên dưới để hiển thị nhiệt độ:

  • Thêm văn bản “Temperature”:
<span class="dht-labels">Temperature</span>
  • Đoạn văn bản giữa các dấu % là nơi để ghi giá trị nhiệt độ. Đây là giá trị có thể thay đổi được, tùy thuộc vào nhiệt độ hiện tại là bao nhiêu.
<span id="temperature">%TEMPERATURE%</span>
  • Thêm ký hiệu độ C – đơn vị nhiệt độ:
<sup class="units">°C</sup>

Thẻ <sup></sup> tạo thành các chỉ số của văn bản.

Chúng ta làm tương tự với độ ẩm:

<p>
  <i class="fas fa-tint" style="color:#00add6;"></i> 
  <span class="dht-labels">Humidity</span>
  <span id="humidity">%HUMIDITY%</span>
  <sup class="units">%</sup>
</p>

Cập nhật nhiệt độ, độ ẩm tự động

Cuối cùng, chúng ta sử dụng Javascript trên Website để tự động cập nhật thông tin nhiệt độ và độ ẩm sau mỗi 10 giây:

<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 ) ;
</script>

Lưu ý quan trọng: Vì cảm biến DHT nhận thông tin khá chậm nên nếu bạn muốn kết nối nhiều Client với mạch ESP32 cùng lúc, bạn nên tăng thời gian tự động cập nhật thời gian hoặc xóa luôn phần tự động cập nhật theo thời gian thực này.

processor() 

Bây giờ, chúng ta cần tạo một hàm processor() để thay thế các placeholder trong văn bản HTML thành các giá trị nhiệt độ, độ ẩm thực tế, thông  qua đoạn code sau:

String processor(const String& var){
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return readDHTTemperature();
  }
  else if(var == "HUMIDITY"){
    return readDHTHumidity();
  }
  return String();
}

Khi được yêu cầu, hệ thống sẽ kiểm tra trong đoạn HTML có bất kỳ placeholder nào không. Nếu tìm thấy placeholder %TEMPERATURE%, chúng sẽ trả về kết quả là thông tin nhiệt độ:

if(var == "TEMPERATURE"){
  return readDHTTemperature();
}

Tương tự với độ ẩm:

else if(var == "HUMIDITY"){
  return readDHTHumidity();
}

setup()

Khởi tạo Serial Monitor để quan sát và gỡ lỗi:

Serial.begin(115200);

Khởi tạo cảm biến DHT:

dht.begin();

Kết nối mạng và in ra IP của mạch ESP32:

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}

Cuối cùng là chương trình xử lý Webserver:

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", readDHTTemperature().c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readDHTHumidity().c_str());
});

Khi chúng ta gửi yêu cầu trên root URL, chúng ta sẽ gửi một đoạn mã HTML trong biến index_html. Chúng ta cũng sẽ gọi hàm processor để thay thế văn bản thành giá trị thực tế phù hợp:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

Khi nhận được yêu cầu trên URL/temperature, chúng ta cần gửi giá trị nhiệt độ đã đượcc cập nhật. Vì chúng là văn bản được hiển thị ở dạng ký tự nên mình dùng c_str():

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readDHTTemperature().c_str());
});

Tương tự với độ ẩm:

server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readDHTHumidity().c_str());
});

Khởi động Webserver:

server.begin();

Chúng ta đang sử dụng asynchronous web server nên mình sẽ không cần thêm bất kỳ dòng lệnh nào vào vòng lặp loop():

void loop(){

}

Nạp chương trình

Bạn hãy nạp chương trình vào mạch ESP32 và mở màn hình Serial Monitor, tốc độ 115200. Nhấn nút reset trên mạch ESP32 để lấy địa chỉ IP:

Lấy địa chỉ IP của mạch ESP32

Kết quả dự án

Mở trình duyệt và nhập IP của ESP32 vào thanh tìm kiếm. Một trang Web mới sẽ hiển thị với thông tin nhiệt độ, độ ẩm mới nhất. Các giá trị này sẽ tự động cập nhật mà không cần bạn phải F5 hay restart lại trang:

Đo nhiệt độ và độ ẩm với DHT22 DHT11 ESP32

Lời kết

Trong hướng dẫn trên, mình đã hướng dẫn chi tiết cách tạo một Webserver để đọc giá trị nhiệt độ, độ ẩm với DHT22, DHT11 ESP32 đơn giản, và các giá trị sẽ tự động cập nhật sau mỗi 10 giây.

Chúc các bạn thành công.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *