บทความนี้เป็นการทดลองทำให้ไมโครคอนโทรลเลอร์ esp8266 เป็นเครื่องให้บริการเว็บเพื่อแสดงผลค่าอุณหภูมิและความชื้นจากเซ็นเซอร์ DHT11 โดยใช้ไลบรารีของ Adafruit ดังภาพที่ 1 และเมื่อกำหนดให้ไมโครคอรโทรลเลอร์ทำงานในโหมด SoftAP เพื่อให้ลูกข่ายหรือผู้ใช้เชื่อมต่อ WiFi เข้ามาหลังจากนั้นใช้ Browser เข้าไปยัง IP หมายเลข 192.168.4.1 ซึ่งเป็นหมายเลขของ esp8266
อุปกรณ์
จากภาพที่ 1 จะพบว่าทีมงานเราใช้รายการอุปกรณ์ดังนี้
- NodeMCU
- NodeMCU Base
- DHT11 Module
การเชื่อมต่อ
จากวงจรในภาพที่ 2 จะพบว่าทางทีมงานเราเลือกใช้ขา GPIO05 หรือ D1 ของไมโครคอนโทรลเลอร์ esp8266 และเลือกใช้โมดูล DHT11 ที่มีการต่อ R เอาไว้ในตัวดังภาพที่ 4 ดังนั้นให้เชื่อมขาตามภาพที่ 3 และ 4 คือ
- เส้นสีเหลืองต่อเข้ากับ 3V3 และ +
- เส้นสีน้ำตาลต่อเข้ากับ GND และ –
- เส้นสีส้มต่อเข้ากับ D1 หรือ GPIO05 และ s
ทดสอบโปรแกรมเพื่อให้แน่ใจว่าทำงานได้ถูกต้องด้วยโค้ดต่อไปนี้ และตัวอย่างของผลลัพธ์เป็นดังภาพที่ 5
#include <DHT.h>
DHT dht = DHT(5, DHT11); // D1
float minC = 100.0f, maxC = 0.0f; // อุณหภูมิต่ำสุด/สูงสุด
float minH = 100.0f, maxH = 0.0f; // ความชื้นต่ำสุด/สูงสุด
void setup() {
Serial.begin(115200);
Serial.println("\n\n\n");
dht.begin();
}
void loop() {
float h = dht.readHumidity();
float tc = dht.readTemperature();
float tf = dht.readTemperature(true);
if (isnan(h) || isnan(tc) || isnan(tf)) {
Serial.println("DHT11 connect failed!");
return;
}
float hic = dht.computeHeatIndex(tc, h, false);
float hif = dht.computeHeatIndex(tf, h);
if (minC > tc) {
minC = tc;
}
if (maxC < tc) {
maxC = tc;
}
if (minH > h) {
minH = h;
}
if (maxH < h) {
maxH = h;
}
Serial.printf("Temperature: %.2fC/%.2fF Huminitt: %.2f%%, Heat index: %.2fC/%.2fF\n",
tc, tf, h, hic, hif);
Serial.printf("Temp. (%.2fC-%.2fC) Hum. (%.2f%%-%.2f%%)\n",
minC, maxC, minH, maxH);
delay(10000);
}
ตัวอย่างโปรแกรม
จากตัวอย่างการทดสอบการทำงานของวงจรเชื่อมต่อกับเซ็นเซอร์ DHT11 ให้กลายเป็น Server จะได้ว่าทางเราได้สร้างตัวแปรภายนอกสำหรับเก็บค่าต่าง ๆ ดังต่อไปนี้
float minC = 100.0f, maxC = 0.0f; // อุณหภูมิต่ำสุด/สูงสุด
float minH = 100.0f, maxH = 0.0f; // ความชื้นต่ำสุด/สูงสุด
float hic; // headt index ในหน่วย C
float hif;// headt index ในหน่วย F
float h; // ค่าความชื้น
float tc; // ค่าอุณหภูมิในหน่วย C
float tf; // ค่าอุณหภูมิในหน่วย F
นอกจากนี้ได้แยกส่วนของการอ่านค่าเอาไว้ในฟังก์ชันสำหรับอ่านค่าชื่อ getDHT11() ดังนี้ ซึ่งได้มีการตรวจสอบกรณีที่เซ็นเซอร์ไม่ทำงานด้วยการให้ค่าเป็น -1.0 โดยไม่คำนวณต่า min/max ของอุณหภูมิและความชื้นใหม่
void getDHT11() {
h = dht.readHumidity();
tc = dht.readTemperature();
tf = dht.readTemperature(true);
if (isnan(h) || isnan(tc) || isnan(tf)) {
h = -1.0f;
tc = -1.0f;
tf = -1.0f;
hic = -1.0f;
hif = -1.0f;
return;
}
hic = dht.computeHeatIndex(tc, h, false);
hif = dht.computeHeatIndex(tf, h);
if (minC > tc) {
minC = tc;
}
if (maxC < tc) {
maxC = tc;
}
if (minH > h) {
minH = h;
}
if (maxH < h) {
maxH = h;
}
}
โดยหลักการทำงานของ HTTP จะทำการส่งข้อความร้องขอชุดแรกเป็นรูปแบบดังนี้มาที่พอร์ตหมายเลข 80 (สำหรับกรณีที่เป็น http)
GET ทรัพยากรที่ร้องขอ HTTP/1.1
เช่น GET / HTTP/1.1 หมายถึงร้องขอเรียกหน้าเว็บหลัก เราจึงเขียนฟังก์ชันสำหรับทำหน้าที่ตอบสนองหน้าเว็บหลักที่เรียก getDHT11() เพื่ออ่านค่าปัจจุบันของอุณหภูมิความชื้นพร้อมทั้งคำนวณ Heat Index กับค่าต่ำสุด/สูงสุดของอุณหภูมิแบบองศาเซลเซียสและความชื้นสัมพัทธ์ หลังจากนั้นทำการตอบกลับไปยังเครื่องลูกข่ายที่ร้องขอเข้ามาโดยส่งรหัส HTTP/1.1 200 OK เป็นข้อมูลชุดแรกซึ่งรหัส 200 เป็นรหัสตอบกลับให้ทราบว่าการร้องขอของลูกข่ายนั้นประสบความสำเร็จ โดยรหัสตอบกลับอื่น ๆ ได้แก่
- 200 การร้องขอประสบความสำเร็จ และได้ตอบกลับข้อมูลกลับมา
- 404 ไม่พบทรัพยากรที่ร้องขอ
- 302 สิ่งที่ร้องขอนั้นย้ายที่อยู่ไปแล้ว
- 500 เครื่องให้บริการไม่พร้อมให้บริการในสิ่งที่ร้องขอ
String htmlPage() {
String html;
getDHT11();
html.reserve(2048); // prevent ram fragmentation
html = F("HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n" // the connection will be closed after completion of the response
"Refresh: 5\r\n" // refresh the page automatically every 5 sec
"\r\n"
"<!DOCTYPE HTML>"
"<html><head></head><body>"
"<h1>DHT11</h1>");
html += F("<div>Temperature:");
html += tc;
html += F("C/");
html += tf;
html += F("F</div>");
html += F("<div>Huminity:");
html += h;
html += F("%</div>");
html += F("<div>Temperature:");
html += minC;
html += F("C-");
html += maxC;
html += F("C</div>");
html += F("<div>Huminity:");
html += minH;
html += F("%-");
html += maxH;
html += F("%</div>");
html += F("</body></html>\r\n");
return html;
}
ดังนั้น เมื่อนำทั้งหมดไปรวมกับตัวอย่างเรื่องของ WiFiServer เข้ากับการตอบสนองเป็นหน้าเว็บเพจรายงานค่าที่ได้จากเซนเซอร์จึงเขียนได้ดังนี้
#include <DHT.h>
#include <ESP8266WiFi.h>
#define AP_NAME "JarutEx"
#define AP_PASSWD "123456789"
IPAddress myIP(192, 168, 4, 1);
IPAddress gwIP(192, 168, 4, 10);
IPAddress subnet(255, 255, 255, 0);
DHT dht = DHT(5, DHT11); // D1
WiFiServer server(80);
float minC = 100.0f, maxC = 0.0f; // อุณหภูมิต่ำสุด/สูงสุด
float minH = 100.0f, maxH = 0.0f; // ความชื้นต่ำสุด/สูงสุด
float hic; // headt index ในหน่วย C
float hif;// headt index ในหน่วย F
float h; // ค่าความชื้น
float tc; // ค่าอุณหภูมิในหน่วย C
float tf; // ค่าอุณหภูมิในหน่วย F
void getDHT11() {
h = dht.readHumidity();
tc = dht.readTemperature();
tf = dht.readTemperature(true);
if (isnan(h) || isnan(tc) || isnan(tf)) {
h = -1.0f;
tc = -1.0f;
tf = -1.0f;
hic = -1.0f;
hif = -1.0f;
return;
}
hic = dht.computeHeatIndex(tc, h, false);
hif = dht.computeHeatIndex(tf, h);
if (minC > tc) {
minC = tc;
}
if (maxC < tc) {
maxC = tc;
}
if (minH > h) {
minH = h;
}
if (maxH < h) {
maxH = h;
}
}
void setup() {
Serial.begin(115200);
Serial.println("\n\n\n");
dht.begin();
if (WiFi.softAPConfig( myIP, gwIP, subnet )) {
if (WiFi.softAP(AP_NAME, AP_PASSWD, 8, false, 5)) {
Serial.print("IP Address : ");
Serial.println(WiFi.softAPIP());
} else {
Serial.println("softAP() failed!!");
while (true);
}
} else {
Serial.println("softAPConfig() failed!");
while (true);
}
server.begin();
}
String htmlPage() {
String html;
getDHT11();
html.reserve(2048); // prevent ram fragmentation
html = F("HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n" // the connection will be closed after completion of the response
"Refresh: 5\r\n" // refresh the page automatically every 5 sec
"\r\n"
"<!DOCTYPE HTML>"
"<html><head></head><body>"
"<h1>DHT11</h1>");
html += F("<div>Temperature:");
html += tc;
html += F("C/");
html += tf;
html += F("F</div>");
html += F("<div>Huminity:");
html += h;
html += F("%</div>");
html += F("<div>Temperature:");
html += minC;
html += F("C-");
html += maxC;
html += F("C</div>");
html += F("<div>Huminity:");
html += minH;
html += F("%-");
html += maxH;
html += F("%</div>");
html += F("</body></html>\r\n");
return html;
}
void loop() {
WiFiClient client = server.available();
if (client) {
Serial.println("\n[Client connected]");
while (client.connected()) {
if (client.available()) {
String req = client.readStringUntil('\r');
Serial.print(req);
if (req.indexOf("GET / HTTP/1.1")) {
client.println(htmlPage());
break;
}
}
}
while (client.available()) {
client.read();
}
client.stop();
Serial.println("[Client disconnected]");
}
}
ตัวอย่างผลลัพธ์การทำงานเป็นดังภาพที่ 6
คลาส ESP8266WebServer
จากตัวอย่างการทำต้วเองเป็นเครื่องให้บริการเว็บจะพบว่า การเขียนโปรแกรมจะต้องคอยตรวจสอบข้อความที่เข้ามาและทำการคัดแยกเพื่อตีความสิ่งที่ได้รับ เช่น เมื่อพบ “GET / HTTP/1.1” หมายถึงลูกข่ายร้องขอเข้าถึงไดเร็กทอรีรากหรือหน้าเว็บหลัก ถ้าเป็นการร้องขอหาเว็บอื่นเช่น index9.html จะกลายเป็น “GET /index9.html HTTP/1.1” ดังนั้น ผู้เขียนโปรแกรมจะต้องเพิ่มส่วนของการแยกคำเพื่อให้มั่นใจว่าลูกข่ายร้องขอทรัพยากรใด ด้วยเหตุนี้จึงมีคลาส ESP8266WebServer ที่ช่วยให้การเขียนโปรแกรมเพื่อทำหน้าที่ให้บริการเว็บสะดวกยิ่งขึ้น
จากตัวอย่างก่อนหน้านี้เมื่อเขียนด้วย ESP8266WebServer จะได้โค้ดดังนี้
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#define AP_NAME "JarutEx"
#define AP_PASSWD "123456789"
IPAddress myIP(192, 168, 4, 1);
IPAddress gwIP(192, 168, 4, 10);
IPAddress subnet(255, 255, 255, 0);
DHT dht = DHT(2, DHT11);
ESP8266WebServer server(80);
float minC = 100.0f, maxC = 0.0f; // อุณหภูมิต่ำสุด/สูงสุด
float minH = 100.0f, maxH = 0.0f; // ความชื้นต่ำสุด/สูงสุด
float hic; // headt index ในหน่วย C
float hif;// headt index ในหน่วย F
float h; // ค่าความชื้น
float tc; // ค่าอุณหภูมิในหน่วย C
float tf; // ค่าอุณหภูมิในหน่วย F
void getDHT11() {
h = dht.readHumidity();
tc = dht.readTemperature();
tf = dht.readTemperature(true);
if (isnan(h) || isnan(tc) || isnan(tf)) {
h = -1.0f;
tc = -1.0f;
tf = -1.0f;
hic = -1.0f;
hif = -1.0f;
return;
}
hic = dht.computeHeatIndex(tc, h, false);
hif = dht.computeHeatIndex(tf, h);
if (minC > tc) {
minC = tc;
}
if (maxC < tc) {
maxC = tc;
}
if (minH > h) {
minH = h;
}
if (maxH < h) {
maxH = h;
}
}
void setup() {
dht.begin();
Serial.begin(115200);
Serial.println("\n\r\n\r");
if (WiFi.softAPConfig( myIP, gwIP, subnet )) {
if (WiFi.softAP(AP_NAME, AP_PASSWD, 8, false, 5)) {
Serial.print("IP Address : ");
Serial.println(WiFi.softAPIP());
} else {
while (true);
}
} else {
while (true);
}
server.on("/", htmlPage);
server.begin();
}
void htmlPage() {
String html;
getDHT11();
html.reserve(2048); // prevent ram fragmentation
html = F(
"<!DOCTYPE HTML>"
"<html><head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>"
"html {font-family: Arial; display: inline-block; text-align: center;}"
"h1 {font-size: 3.0rem;}"
"p {font-size: 3.0rem;}"
"body {max-width: 800px; margin:0px auto; padding-bottom: 16px;}"
"</style>"
"</head><body>"
"<h1>DHT11</h1>"
);
html += F("<div>Temperature:");
html += tc;
html += F("C/");
html += tf;
html += F("F</div>");
html += F("<div>Huminity:");
html += h;
html += F("%</div>");
html += F("<div>Temperature:");
html += minC;
html += F("C-");
html += maxC;
html += F("C</div>");
html += F("<div>Huminity:");
html += minH;
html += F("%-");
html += maxH;
html += F("%</div>");
html += F("</body></html>\r\n");
server.send(200, "text/html", html);
}
void loop() {
server.handleClient();
}
จากตัวอย่างจะได้ว่าทางทีมงานเราได้ปรับแก้ส่วนของ HTML เพิ่มเติมในเรื่องการควบคุมการแสดงผลใน CSS เพื่อให้ตัวอักษรแสดงได้เหมาะสมกับหลายอุปกรณ์มากขึ้น ในส่วนของการใช้ ESP8266WebServer ถูกนำมาใช้แทน WiFiServer และเหลือการทำงานเพียง 3 ส่วนหลัก คือ
- การตั้งค่าการดักการร้องขอ และการตอบกลับ
- ด้วยการใช้ server.on( ทรัพยากร, ฟังก์ชั้นตอบกลับ )
- ในฟังก์ชันตอบกลับเรียก server.send( รหัสตอบกลับ, ประเภทข้อมูล, ข้อมูล )
- การเริ่มต้นทำงาน ด้วยการเรียก server.begin() ใน setup()
- การรอการร้องขอจากลูกข่ายด้วยการเรียก server.handleClient() ใน loop()
สรุป
จากบทความนี้จะพบว่าถ้าเราเข้าใจหลักการสื่อสารของโพรโทคอลต่าง ผู้เขียนโปรแกรมสามารถใช้ไมโครคอนโทรลเลอร์ esp8266 เป็นตัวให้บริการหรือลูกข่ายเพื่อสื่อสารการทำงานกับโพรโทคอลนั้นได้ อย่างในตัวอย่างครั้งนี้เป็นการใช้เรื่องของ WiFiServer ของ esp8266 ดักการทำงานที่พอร์ต 80 ซึ่งเป็นพอร์ตของโพรโทคอล HTTP และเมื่อพบข้อความที่ร้องขอทรัพยากรเราจึงตอบกลับเป็นรูปแบบที่ HTTP เข้าใจ ผลลัพธ์ที่ออกมาทำให้แสดงผลบนเว็บบราวเซอร์ได้ถูกต้องดังภาพที่ 6 สุดท้ายนี้ขอให้สนุกกับการเขียนโปรแกรมครับ
ท่านใดต้องการพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ
แหล่งอ้างอิง
- ESP8266 Arduino Core :Server
(C) 2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-08-01, 2021-08-08, 2021-08-10, 2021-11-08