Arduino Máy chủ Web Đa trang
Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách biến Arduino thành một máy chủ web có thể xử lý nhiều trang cùng lúc, ví dụ như index.html, temperature.html, led.html, error_404.html, và error_405.html... Nội dung của từng trang, bao gồm HTML, CSS và JavaScript, sẽ được lưu trữ ở các file riêng biệt trên Arduino IDE. Bằng cách truy cập Arduino Web Server từ trình duyệt web trên máy tính hoặc điện thoại thông minh của bạn, bạn sẽ có thể xem và điều khiển cảm biến và các thiết bị được kết nối với Arduino thông qua web. Ngoài ra, máy chủ web sẽ được thiết kế để chấp nhận liên kết có hoặc không có phần mở rộng .html.
Bằng cách làm theo hướng dẫn này, bạn sẽ có thể biến Arduino của bạn thành một máy chủ web với một số tính năng thú vị:
Nhiều trang web đang hoạt động đồng thời.
Nội dung HTML (gồm HTML, CSS và Javascript) cho mỗi trang được lưu riêng trong một tệp tin của nó.
Nội dung HTML có thể được cập nhật động với các giá trị thời gian thực từ các cảm biến, làm cho các trang web động và đáp ứng.
Bạn có thể truy cập các trang có hoặc không có phần mở rộng .html. Ví dụ, bạn có thể sử dụng các liên kết như http://192.168.0.2/led hoặc http://192.168.0.2/led.html để truy cập cùng một trang điều khiển LED.
Máy chủ web xử lý các mã lỗi HTTP như 404 Not Found và 405 Method Not Allowed
Nghe có vẻ phức tạp, nhưng đừng lo! Hướng dẫn này cung cấp chỉ dẫn từng bước, và mã nguồn được thiết kế thân thiện với người mới bắt đầu, đảm bảo bạn có thể hiểu dễ dàng và tự tạo máy chủ web Arduino cho riêng mình.
| 1 | × | Arduino UNO R4 WiFi | | |
| 1 | × | Alternatively, DIYables STEM V4 IoT | | |
| 1 | × | (Tùy chọn) DIYables STEM V4 IoT | | |
| 1 | × | USB Cable Type-A to Type-C (for USB-A PC) | | |
| 1 | × | USB Cable Type-C to Type-C (for USB-C PC) | | |
| 1 | × | (Khuyến nghị) Screw Terminal Block Shield for Arduino UNO R4 | | |
| 1 | × | (Khuyến nghị) Breadboard Shield for Arduino UNO R4 | | |
| 1 | × | (Khuyến nghị) Enclosure for Arduino UNO R4 | | |
| 1 | × | (Khuyến nghị) Power Splitter for Arduino UNO R4 | | |
| 1 | × | (Khuyến nghị) Prototyping Base Plate & Breadboard Kit for Arduino UNO | | |
Or you can buy the following kits:
| 1 | × | DIYables STEM V4 IoT Starter Kit (Arduino included) | | |
| 1 | × | DIYables Sensor Kit (30 sensors/displays) | | |
| 1 | × | DIYables Sensor Kit (18 sensors/displays) | | |
Nếu bạn chưa quen với Arduino Uno R4 và Web Server (bao gồm sơ đồ chân, cách nó hoạt động và lập trình), bạn có thể tìm hiểu về chúng thông qua các bài hướng dẫn sau:
Khi trình duyệt web gửi một yêu cầu HTTP tới bo mạch Arduino, Arduino cần được lập trình để thực hiện các tác vụ sau:
Tạo một máy chủ web có thể lắng nghe các yêu cầu HTTP từ trình duyệt web.
Đọc dòng đầu tiên của tiêu đề HTTP khi nó nhận được một yêu cầu HTTP.
Định tuyến các yêu cầu dựa trên dòng đầu tiên của yêu cầu HTTP để xác định những trang web mà Arduino nên trả về.
(Tùy chọn) Phân tích tiêu đề HTTP của yêu cầu để nhận diện các lệnh điều khiển do người dùng gửi.
(Tùy chọn) Điều khiển các thiết bị được kết nối với Arduino dựa trên các lệnh điều khiển đã nhận.
Gửi một phản hồi HTTP trở lại trình duyệt web, trong đó có:
Một tiêu đề phản hồi HTTP.
Nội dung thân phản hồi HTTP, chứa nội dung HTML và (nếu có) dữ liệu cảm biến hoặc trạng thái thiết bị.
Thông qua việc thực hiện những nhiệm vụ này, Arduino có thể xử lý hiệu quả các yêu cầu HTTP và cung cấp các phản hồi phù hợp cho trình duyệt web, cho phép điều khiển và tương tác dựa trên web với các thiết bị được kết nối với Arduino.
Chức năng định tuyến là nhiệm vụ quan trọng nhất, và nó sẽ được giải thích chi tiết. Các phần khác được đề cập trong hướng dẫn Arduino - Máy chủ web. Khi bạn đã hiểu thuật toán định tuyến, chúng ta sẽ tiến hành xem toàn bộ mã nguồn cho một máy chủ web có nhiều trang.
Trước khi viết mã cho hàm định tuyến, bạn nên tạo một danh sách các trang web và các phương thức HTTP tương ứng sẽ có sẵn trên Arduino. Trong hướng dẫn này, chúng ta sẽ chỉ hỗ trợ phương thức GET. Tuy nhiên, bạn có thể dễ dàng mở rộng nó để bao gồm các phương thức HTTP khác nếu cần. Dưới đây là một danh sách ví dụ:
Lấy trang chủ
Lấy trang nhiệt độ
Lấy trang cửa
Lấy trang đèn LED
Sau đó, bạn cần tạo một danh sách các trường tiêu đề của yêu cầu HTTP ở dòng đầu tiên tương ứng với danh sách trang:
GET trang chủ:
GET trang nhiệt độ:
GET trang cửa:
GET trang đèn LED:
Tóm lại, chúng ta có danh sách sau:
GET /
GET /index.html
GET /temperature.html
GET /door.html
GET /led.html
Dưới đây là mã Arduino đầy đủ tạo ra một máy chủ web có nhiều trang. Để đơn giản hóa, nội dung HTML cho từng trang rất đơn giản và được nhúng trực tiếp vào mã Arduino. Ở phần tiếp theo, chúng ta sẽ học cách tách nội dung HTML cho từng trang thành các tệp riêng biệt, làm cho mã nguồn trở nên gọn gàng và dễ quản lý hơn.
#include <UnoR4WiFi_WebServer.h>
const char WIFI_SSID[] = "YOUR_WIFI_SSID";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
UnoR4WiFi_WebServer server;
float getTemperature() {
float temp_x100 = random(0, 10000);
return temp_x100 / 100;
}
void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, "This is home page");
}
void handleTemperature(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, "This is temperature page");
}
void handleDoor(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, "This is door page");
}
void handleLed(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, "This is LED page");
}
void handleNotFound(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, "Page Not Found");
}
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("Arduino Uno R4 WiFi - Multi-Page Web Server");
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.addRoute("/", handleHome);
server.addRoute("/index", handleHome);
server.addRoute("/index.html", handleHome);
server.addRoute("/temperature", handleTemperature);
server.addRoute("/temperature.html", handleTemperature);
server.addRoute("/door", handleDoor);
server.addRoute("/door.html", handleDoor);
server.addRoute("/led", handleLed);
server.addRoute("/led.html", handleLed);
server.setNotFoundHandler(handleNotFound);
server.begin();
Serial.println("\n=== Web Server Ready! ===");
Serial.print("Visit: http://");
Serial.println(WiFi.localIP());
}
void loop() {
server.handleClient();
}
Mở Trình quản lý thư viện bằng cách nhấp vào biểu tượng ở bên trái của Arduino IDE.
Tìm kiếm Máy chủ Web cho Arduino Uno R4 WiFi và xác định thư viện Máy chủ Web do DIYables tạo ra.
Nhấp vào nút Cài đặt để thêm thư viện Máy chủ Web.
Sao chép mã ở trên và mở bằng Arduino IDE
Thay đổi thông tin wifi (SSID và mật khẩu) trong mã cho phù hợp với bạn
Nhấn nút Tải lên trên Arduino IDE để tải mã lên cho Arduino
Mở Serial Monitor
Kiểm tra kết quả trên Serial Monitor
Connecting to YOUR_WIFI_SSID
connected!
IP address: 192.168.0.254
Starting web server on IP: 192.168.0.254
=== Web Server Ready! ===
Visit: http://192.168.0.254
Bạn sẽ thấy một địa chỉ IP trên Màn hình Serial, ví dụ: 192.168.0.2
Gõ lần lượt danh sách sau vào thanh địa chỉ của trình duyệt trên điện thoại thông minh hoặc PC của bạn.
192.168.0.2
192.168.0.2/index
192.168.0.2/index.html
192.168.0.2/led
192.168.0.2/led.html
192.168.0.2/door
192.168.0.2/door.html
192.168.0.2/temperature
192.168.0.2/temperature.html
192.168.0.2/blabla
192.168.0.2/blabla.html
Lưu ý rằng bạn cần thay đổi 192.168.0.2 thành địa chỉ IP bạn nhận được trên Serial Monitor.
Bạn sẽ thấy các trang sau: trang chủ, trang đèn LED, trang cửa, trang nhiệt độ và trang Không tìm thấy.
Bạn cũng có thể kiểm tra đầu ra trên Serial Monitor.
Đoạn mã trước có nội dung HTML rất đơn giản cho mỗi trang. Tuy nhiên, nếu muốn tạo giao diện bắt mắt với nhiều mã HTML, mã có thể trở nên lớn và rối. Để đơn giản hóa, chúng ta sẽ học cách tách HTML khỏi mã Arduino. Điều này cho phép chúng ta lưu trữ HTML trong các tệp riêng biệt, giúp quản lý và làm việc với nó dễ dàng hơn.
Mở Arduino IDE.
Tạo một sketch mới và đặt cho nó một tên, ví dụ, ArduinoWebServer.ino.
Sao chép mã được cung cấp và dán vào tệp đó.
#include <UnoR4WiFi_WebServer.h>
#include "index.h"
#include "temperature.h"
#include "door.h"
#include "led.h"
#include "error_404.h"
const char WIFI_SSID[] = "YOUR_WIFI_SSID";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
UnoR4WiFi_WebServer server;
float getTemperature() {
float temp_x100 = random(0, 10000);
return temp_x100 / 100;
}
void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, HTML_CONTENT_HOME);
}
void handleTemperature(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
String html = String(HTML_CONTENT_TEMPERATURE);
html.replace("TEMPERATURE_MARKER", String(getTemperature(), 2));
server.sendResponse(client, html.c_str());
}
void handleDoor(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
String html = String(HTML_CONTENT_DOOR);
html.replace("DOOR_STATE_MARKER", "OPENED");
server.sendResponse(client, html.c_str());
}
void handleLed(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
String html = String(HTML_CONTENT_LED);
html.replace("LED_STATE_MARKER", "OFF");
server.sendResponse(client, html.c_str());
}
void handleNotFound(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
String html = String(HTML_CONTENT_404 );
server.sendResponse(client, html.c_str());
}
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("Arduino Uno R4 WiFi - Multi-Page Web Server");
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.addRoute("/", handleHome);
server.addRoute("/index", handleHome);
server.addRoute("/index.html", handleHome);
server.addRoute("/temperature", handleTemperature);
server.addRoute("/temperature.html", handleTemperature);
server.addRoute("/door", handleDoor);
server.addRoute("/door.html", handleDoor);
server.addRoute("/led", handleLed);
server.addRoute("/led.html", handleLed);
server.setNotFoundHandler(handleNotFound);
server.begin();
Serial.println("\n=== Web Server Ready! ===");
Serial.print("Visit: http://");
Serial.println(WiFi.localIP());
}
void loop() {
server.handleClient();
}
Thay đổi thông tin WiFi (SSID và mật khẩu) trong mã sang của bạn
Tạo tệp index.h trên Arduino IDE bằng cách:
const char *HTML_CONTENT_HOME = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<ul>
<li><a href="/led">LED Page</a></li>
<li><a href="/temperature">Temperature Page</a></li>
<li><a href="/door">Door Page</a></li>
</ul>
</body>
</html>
)"""";
const char *HTML_CONTENT_TEMPERATURE = R""""(
<!DOCTYPE html>
<html>
<head>
<title>Arduino - Web Temperature</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<meta charset="utf-8">
<link rel="icon" href="https://diyables.io/images/page/diyables.svg">
<style>
body { font-family: "Georgia"; text-align: center; font-size: width/2pt;}
h1 { font-weight: bold; font-size: width/2pt;}
h2 { font-weight: bold; font-size: width/2pt;}
button { font-weight: bold; font-size: width/2pt;}
</style>
<script>
var cvs_width = 200, cvs_height = 450;
function init() {
var canvas = document.getElementById("cvs");
canvas.width = cvs_width;
canvas.height = cvs_height + 50;
var ctx = canvas.getContext("2d");
ctx.translate(cvs_width/2, cvs_height - 80);
update_view(TEMPERATURE_MARKER);
}
function update_view(temp) {
var canvas = document.getElementById("cvs");
var ctx = canvas.getContext("2d");
var radius = 70;
var offset = 5;
var width = 45;
var height = 330;
ctx.clearRect(-cvs_width/2, -350, cvs_width, cvs_height);
ctx.strokeStyle="blue";
ctx.fillStyle="blue";
var x = -width/2;
ctx.lineWidth=2;
for (var i = 0; i <= 100; i+=5) {
var y = -(height - radius)*i/100 - radius - 5;
ctx.beginPath();
ctx.lineTo(x, y);
ctx.lineTo(x - 20, y);
ctx.stroke();
}
ctx.lineWidth=5;
for (var i = 0; i <= 100; i+=20) {
var y = -(height - radius)*i/100 - radius - 5;
ctx.beginPath();
ctx.lineTo(x, y);
ctx.lineTo(x - 25, y);
ctx.stroke();
ctx.font="20px Georgia";
ctx.textBaseline="middle";
ctx.textAlign="right";
ctx.fillText(i.toString(), x - 35, y);
}
ctx.lineWidth=16;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.rect(-width/2, -height, width, height);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, -height, width/2, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle="#e6e6ff";
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.rect(-width/2, -height, width, height);
ctx.fill();
ctx.beginPath();
ctx.arc(0, -height, width/2, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle="#ff1a1a";
ctx.beginPath();
ctx.arc(0, 0, radius - offset, 0, 2 * Math.PI);
ctx.fill();
temp = Math.round(temp * 100) / 100;
var y = (height - radius)*temp/100.0 + radius + 5;
ctx.beginPath();
ctx.rect(-width/2 + offset, -y, width - 2*offset, y);
ctx.fill();
ctx.fillStyle="red";
ctx.font="bold 34px Georgia";
ctx.textBaseline="middle";
ctx.textAlign="center";
ctx.fillText(temp.toString() + "°C", 0, 100);
}
window.onload = init;
</script>
</head>
<body>
<h1>Arduino - Web Temperature</h1>
<canvas id="cvs"></canvas>
</body>
</html>
)"""";
const char *HTML_CONTENT_DOOR = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>Door Page</title>
</head>
<body>
<h1>Door Page</h1>
<p>Door State: <span style="color: red;">DOOR_STATE_MARKER</span></p>
</body>
</html>
)"""";
const char *HTML_CONTENT_LED = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>LED Page</title>
</head>
<body>
<h1>LED Page</h1>
<p>LED State: <span style="color: red;">LED_STATE_MARKER</span></p>
</body>
</html>
)"""";
const char *HTML_CONTENT_404 = R""""(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>404 - Page Not Found</title>
<style>
h1 {color: #ff4040;}
</style>
</head>
<body>
<h1>404</h1>
<p>Oops! The page you are looking for could not be found on Arduino Web Server.</p>
<p>Please check the URL or go back to the <a href="/">homepage</a>.</p>
<p>Or check <a href="https://arduinogetstarted.com/tutorials/arduino-web-server-multiple-pages"> Arduino Web Server</a> tutorial.</p>
</body>
</html>
)"""";
※ Lưu ý:
Nếu bạn thực hiện các thay đổi đối với nội dung HTML bên trong tệp index.h nhưng không sửa đổi bất kỳ điều gì trong tệp ArduinoWebServer.ino, Arduino IDE sẽ không làm mới hoặc cập nhật nội dung HTML khi bạn biên dịch và tải mã lên ESP32.
Để bắt buộc Arduino IDE cập nhật nội dung HTML trong trường hợp này, bạn cần thực hiện một sửa đổi trong tệp ArduinoWebServer.ino. Ví dụ, bạn có thể thêm một dòng trống hoặc chèn một nhận xét. Hành động này kích hoạt IDE nhận ra rằng đã có sự thay đổi trong dự án, đảm bảo nội dung HTML được cập nhật của bạn được đưa vào tải lên.