Chúng ta sẽ xây dựng ứng dụng dùng cảm biến DHT11 để thu thập nhiệt độ, độ ẩm của môi trường thông qua ESP8266. Thông tin về nhiệt độ và độ ẩm sẽ được hiển thị trên máy tính và hiển thị trên trình duyệt web bằng cách truy cập vào 1 địa chỉ URL được chỉ định.
Mục lục
1. Tổng quan dự án
Hình ảnh bên dưới mô tả tổng quan dự án:

Trong thực tế, khi thiết kế ứng dụng, người dùng cần một giao diện giám sát và điều khiển thân thiện, đồng thời có thể phát triển thêm các tính năng như hiển thị kết quả dưới dạng đồ thị (chart), lưu trữ dữ liệu theo thời gian chỉ định hay điều khiển trạng thái các thiết bị chỉ với 1 click chuột trên máy tính. Các dự án với mô hình phức tạp sẽ cần quản lí các kết nối cũng như dữ liệu của các thiết bị…
Chúng ta sẽ giải quyết những vấn đề trên thông qua ứng dụng đọc nhiệt độ, độ ẩm của môi trường và gửi về server. Đây là một ứng dụng khá đơn giản, hữu ích và dễ làm. Thông qua phần này chúng ta có thể xây dựng được một ứng dụng IoT thực tế, nắm bắt được các kiến thức cơ bản về thu thập dữ liệu, xây dựng thiết bị và server.
Yêu cầu
- Dùng cảm biến DHT11 để thu thập nhiệt độ, độ ẩm của môi trường và kết nối với board mạch ESP8266
- Board mạch ESP8266 sẽ kết nối không dây đến mạng WiFi và gởi dữ liệu về HTTP Server
- Phần cơ bản: HTTP Server hiển thị dữ liệu nhiệt độ, độ ẩm ra màn hình Log trên máy tính
- Phần nâng cao: HTTP Server lưu trữ dữ liệu, và cung cấp file HTML cho người dùng có thể xem qua Browser
Tóm tắt:
- Master (ESP8266) gửi tín hiệu
START
, DHT11 sẽ chuyển từ chế độ tiết kiệm năng lượng (low-power mode) sang chế độ làm việc bình thường (high-speed mode) - DHT11 nhận được tín hiệu và phản hồi đến master, master nhận tín hiệu và bắt đầu quá trình truyền dữ liệu.
- DHT11 sẽ gửi dữ liệu lên bus, mỗi lần gửi là 1 gói 40 bits data.
- Khi muốn kết thúc, Master sẽ gửi tín hiệu
STOP
, kết thúc quá trình truyền nhận dữ liệu
2. Thực hiện
Linh kiện cần có
- Cảm biến DHT11
- Board ESP8266 WiFi Uno
- Dây nối male-female header
- Điện trở 5K Ohm
- Cable kết nối giữa board ESP8266 và máy tính
Thiết kế mạch điện:

3. Server Nodejs
Về phía Web Server, chúng ta cần đảm bảo nó có thể phục vụ cho nhiều Client, với path
là:
/update
thì sẽ thêm mới dữ liệu để lưu trữ, và in ra màn hình/get
trả về dữ liệu đã lưu trữ định dạng JSON/
và còn lại thì trả về fileindex.html
- Mảng dữ liệu lưu trữ có định dạng:
[{"temp": 25, "humd":80, time: "time"}, ...]
Mã nguồn file server.js
//---------------------------------------------------------------------------------------------
var fs = require('fs');
var url = require('url');
var http = require('http');
var querystring = require('querystring');
var db = []; //database
//---------------------------------------------------------------------------------------------
// function gửi yêu cầu(response) từ phía server hoặc nhận yêu cầu (request) của client gửi lên
function requestHandler(request, response) {
// Giả sử địa chỉ nhận được http://192.168.1.7:8000/update?temp=30&humd=40
var uriData = url.parse(request.url);
var pathname = uriData.pathname; // /update?
var query = uriData.query; // temp=30.5&hum=80
var queryData = querystring.parse(query); // queryData.temp = 30.5, queryData.humd = 40
//-----------------------------------------------------------------------------------------
if (pathname == '/update') {
var newData = {
temp: queryData.temp,
humd: queryData.humd,
time: new Date()
};
db.push(newData);
console.log(newData);
response.end();
//-----------------------------------------------------------------------------------------
} else if (pathname == '/get') {
response.writeHead(200, {
'Content-Type': 'application/json'
});
response.end(JSON.stringify(db));
db = [];
//-----------------------------------------------------------------------------------------
} else {
fs.readFile('./index.html', function(error, content) {
response.writeHead(200, {
'Content-Type': 'text/html'
});
response.end(content);
});
}
//-----------------------------------------------------------------------------------------
}
var server = http.createServer(requestHandler);
server.listen(8000);
console.log('Server listening on port 8000');
Bạn có thể truy cập đến đường dẫn của file server.js và thực thi đoạn code trên với dòng lệnh node server.js
, sau đó thử truy vập vào localhost:8000 để xem trang index.html
. Hoặc truy cập vào localhost:8000/update?temp=20&humd=60 để xem màn hình Log in ra kết quả nhiệt độ và độ ẩm.
{ temp: '20', humd: '60', time: 2017-08-21T16:56:23.358Z }
{ temp: '20', humd: '60', time: 2017-08-21T16:57:06.277Z }
{ temp: '20', humd: '60', time: 2017-08-21T16:57:17.708Z }
Ở phần cơ bản chúng ta chưa cần phải quan tâm đến file index.html
và đoạn code Javascript trong đó. Cũng nhưng chưa quan tâm tới việc xử lý khi đường dẫn là /get
hoặc /\*
, mà chỉ quan tâm duy nhất khi nhận được với đường dẫn /update
.
Khi đã hoàn thành phần cơ bản chúng ta sẽ đi đến một ứng dụng khá phổ biến, người dùng cần hiển thị các dữ liệu thu thập một cách trực quan thông qua trình duyệt Web. Vì vậy chúng ta sẽ làm 1 file index.html
chứa mã nguồn Javascript có thể yêu cầu Server trả về dữ liệu mỗi giây để hiển thị lên 1 biểu đồ canvas.
Mã nguồn file index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DHT11</title>
<!-- Nhúng file Javasript tại đường dẫn src để có thể xây dựng 1 graph -->
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
</head>
<body>
<h1> 1. THONG SO NHIET DO, DO AM</h><br>
<h2> Temprature</h2> <input type="text" size="6" id="temp">°C<br>
<h2> Humidity</h2> <input type="text" size="6" id="humd">%<br>
<h1> 2. DO THI</h1><br>
<!-- thiết lập kích thước cho graph thông qua id ChartContainer đã thiết lập ở trên -->
<div id="ChartContainer" style="height: 300px; width:80%;"></div>
<script type="text/javascript">
function httpGetAsync(theUrl, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(JSON.parse(xmlHttp.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
window.onload = function() {
var dataTemp = [];
var dataHumd = [];
var Chart = new CanvasJS.Chart("ChartContainer", {
zoomEnabled: true, // Dùng thuộc tính có thể zoom vào graph
title: {
text: "Temprature & Humidity" // Viết tiêu đề cho graph
},
toolTip: { // Hiển thị cùng lúc 2 trường giá trị nhiệt độ, độ ẩm trên graph
shared: true
},
axisX: {
title: "chart updates every 2 secs" // Chú thích cho trục X
},
data: [{
// Khai báo các thuộc tính của dataTemp và dataHumd
type: "line", // Chọn kiểu dữ liệu đường
xValueType: "dateTime", // Cài đặt kiểu giá trị tại trục X là thuộc tính thời gian
showInLegend: true, // Hiển thị "temp" ở mục chú thích (legend items)
name: "temp",
dataPoints: dataTemp // Dữ liệu hiển thị sẽ lấy từ dataTemp
},
{
type: "line",
xValueType: "dateTime",
showInLegend: true,
name: "humd",
dataPoints: dataHumd
}
],
});
var yHumdVal = 0; // Biến lưu giá trị độ ẩm (theo trục Y)
var yTempVal = 0; // Biến lưu giá trị nhiệt độ (theo trục Y)
var updateInterval = 2000; // Thời gian cập nhật dữ liệu 2000ms = 2s
var time = new Date(); // Lấy thời gian hiện tại
var updateChart = function() {
httpGetAsync('/get', function(data) {
// Gán giá trị từ localhost:8000/get vào textbox để hiển thị
document.getElementById("temp").value = data[0].temp;
document.getElementById("humd").value = data[0].humd;
// Xuất ra màn hình console trên browser giá trị nhận được từ localhost:8000/get
console.log(data);
// Cập nhật thời gian và lấy giá trị nhiệt độ, độ ẩm từ server
time.setTime(time.getTime() + updateInterval);
yTempVal = parseInt(data[0].temp);
yHumdVal = parseInt(data[0].humd);
dataTemp.push({ // cập nhât dữ liệu mới từ server
x: time.getTime(),
y: yTempVal
});
dataHumd.push({
x: time.getTime(),
y: yHumdVal
});
Chart.render(); // chuyển đổi dữ liệu của của graph thành mô hình đồ họa
});
};
updateChart(); // Chạy lần đầu tiên
setInterval(function() { // Cập nhật lại giá trị graph sau thời gian updateInterval
updateChart()
}, updateInterval);
}
</script>
</body>
</html>
Phân tích phần vẽ biểu đồ
- Chúng ta sẽ lấy dữ liệu từ server gửi xuống và vẽ biểu đồ dùng mã Javascrpit, sử dụng tag
<scrpit>code JS </scrpit>
để chèn nội dung code Javascrpit vào file HTML. - Việc lấy dữ liệu được thực thi bằng hàm
httpGetAsync()
. Hàm này sử dụng đối tượngXMLHttpRequest
để lấy dữ liệu từ server mà không cần phải load lại trang, dữ liệuxmlHttp.responseText
lấy từ server tại địa chỉlocalhost:8000/get
ở định dạng JSON nên cần phải chuyển sang dạng Object bằng hàm JSON.parse().
Cần phải có dữ liệu từ ESP8266 gửi lên server thì tại địa chỉ localhost:8000/get mới có dữ liệu.

localhost:8000/get
Sử dụng window.onload = function()
để load lại nội dung của graph, các lệnh trong hàm đã được giải thích trong code.
Truy cập vào trang canvasjs.com/javascript-charts/ để lựa chọn và xây dựng những đồ thị phù hợp với mục đích của bạn.

4. Code ESP8266
ESP8266 sử dụng thư viện HTTPClient để kết nối tới Web Server và lấy dữ liệu nhiệt độ, đổ ẩm thông qua phương thức GET với query là temp
và humd
.
Chuẩn bị
- Cung cấp SSID và PASSWORD WiFi cho board mạch ESP8266 để kết nối vào mạng nội bộ với Web Server.
- Cung cấp địa chỉ IP, port của Web Server.
- Thư viện hỗ trợ lấy dữ liệu của DHT11. Dựa theo chuẩn truyền nhận 1 wire và sự phổ biến của dòng sensor DHTXX (DHT11, DHT22,…), có rất nhiều thư viện được xây dựng lên để việc lập trình với DHT11 trở nên dễ dàng hơn. Trong bài này chúng ta sẽ cài đặt và sử dụng thư viện
DHT sensor library
của Adafruit.

Code ESP8266
#include <DHT.h> // Khai báo sử dụng thư viện DHT
#include <ESP8266WiFi.h> // Khai báo sử dụng thư viện ESP8266WiFi.h để thiết lập chế độ HTTP client cho ESP8266
#define DHTPIN 4 // Chân dữ liệu của DHT11 kết nối với GPIO4 của ESP8266
#define DHTTYPE DHT11 // Loại DHT được sử dụng
DHT dht(DHTPIN, DHTTYPE);
WiFiClient client; // Tạo 1 biến client thuộc kiểu WiFiClient
const char* ssid = "YOUR-WIFI-SSID"; // Tên mạng Wifi được chỉ định sẽ kết nối (SSID)
const char* password = "YOUR-WIFI-PASS"; // Password của mạng Wifi được chỉ định sẽ kết nối
const char* server = "Your-local-IP"; // Địa chỉ IP của máy khi truy cập cùng mạng WiFi
const int port = 8000; // Port của server đã mở
const int sendingInternval = 2 * 1000; // Biến cập nhật dữ liệu sau mỗi 2s
void setup() {
Serial.begin(115200);
dht.begin(); // Khởi tạo DHT1 11 để truyền nhận dữ liệu
Serial.println("Connecting");
// Thiết lập ESP8266 là Station và kết nối đến Wifi. in ra dấu `.` trên terminal nếu chưa được kết nối
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println("\r\nWiFi connected");
}
void loop() {
// Đọc gía trị nhiệt độ (độ C), độ ẩm. Xuất ra thông báo lỗi và thoát ra nếu dữ liệu không phải là số
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
if (client.connect(server, port)) { // Khởi tạo kết nối đến server thông qua IP và PORT đã mở
//---------------------------------------------------------------------------------------
String req_uri = "/update?temp=" + String(temp, 1) + "&humd=" + String(humi, 1);
client.print("GET " + req_uri + " HTTP/1.1\n" + "Host: "+ server +"\n" + "Connection: close\n" + "Content-Length: 0\n" +"\n\n");
//---------------------------------------------------------------------------------------
// temp, humi chuyển từ định dạng float sang định dạng string và in ra màn hình serial // terminal trên Arduino.
Serial.printf("Nhiet do %s - Do am %s\r\n", String(temp, 1).c_str(), String(humi, 1).c_str());
}
client.stop(); // Ngắt kết nối đến server
delay(sendingInternval);
}
Thực hiện sau khi kiểm tra mã nguồn:
Có thể xem thông các thông tin của quá trình truyền nhận dữ liệu với lệnh: curl -v http://192.168.1.7:8000/update?temp=28.0&humd=45.0
. Thông tin hiển thị như bên dưới:
name@yourname:~$ curl -v http://192.168.1.7:8000/update?temp=28.0&humd=45.0
[1] 9277
name@yourname:~$ * Trying 192.168.1.7...
* Connected to 192.168.1.7 (192.168.1.7) port 8000 (#0)
> GET /update?temp=28.0 HTTP/1.1 // > Thông tin gửi từ ESP8266
> Host: 192.168.1.7:8000
> User-Agent: curl/7.47.0
> Accept: */*
> // < Thông tin gửi từ server
< HTTP/1.1 200 OK
< Date: Wed, 23 Aug 2017 17:22:49 GMT
< Connection: keep-alive
< Content-Length: 0
<
* Connection #0 to host 192.168.1.7 left intact
Kết quả hiển thị trên Arduino IDE và màn hình Log của máy tính:
