Tài Liệu Tham Khảo Thư Viện ESP32 WebServer

Tổng Quan

Thư viện DIYables_ESP32_WebServer cung cấp giải pháp toàn diện để tạo web server đa trang với hỗ trợ WebSocket trên các board ESP32.

Phần Cứng Cần Thiết

1×mô-đun phát triển ESP-WROOM-32
1×Alternatively, ESP32 Uno-form board
1×Alternatively, ESP32 S3 Uno-form board
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 Expansion Board for ESP32
1×(Khuyến nghị) Breakout Expansion Board for ESP32
1×(Khuyến nghị) Power Splitter for ESP32

Or you can buy the following kits:

1×DIYables ESP32 Starter Kit (ESP32 included)
1×DIYables Sensor Kit (30 sensors/displays)
1×DIYables Sensor Kit (18 sensors/displays)

Cài Đặt

Các Bước Nhanh

Thực hiện theo hướng dẫn từng bước sau:

  • Nếu đây là lần đầu bạn sử dụng ESP32, hãy tham khảo hướng dẫn về thiết lập môi trường cho ESP32 trong Arduino IDE.
  • Kết nối board ESP32 với máy tính bằng cáp USB.
  • Khởi động Arduino IDE trên máy tính.
  • Chọn board ESP32 phù hợp (ví dụ: ESP32) và cổng COM.
  • Mở Library Manager bằng cách nhấn vào biểu tượng Library Manager ở phía bên trái của Arduino IDE.
  • Tìm kiếm Web Server for ESP32 và định vị mWebSockets by DIYables.
  • Nhấn vào nút Install để thêm thư viện mWebSockets.
ESP32 web server thư viện

Hỗ Trợ WebSocket (Tích Hợp Sẵn)

Tính năng WebSocket hiện đã được tích hợp trực tiếp vào thư viện này!

Việc triển khai WebSocket được dựa trên thư viện mWebSockets xuất sắc của Dawid Kurek, đã được tích hợp và tối ưu hóa đặc biệt cho ESP32 để dễ sử dụng:

  • Không cần cài đặt thư viện bổ sung - Hỗ trợ WebSocket được tích hợp sẵn
  • Tối ưu cho ESP32 - Triển khai đơn giản hóa dành riêng cho nền tảng
  • Tương thích WiFi - Sử dụng ngăn xếp WiFi gốc của ESP32
  • Tuân thủ RFC 6455 - Hỗ trợ đầy đủ giao thức WebSocket
  • Giao tiếp thời gian thực - Trao đổi dữ liệu hai chiều

Cách Sử Dụng:

  • Cho mọi thứ (Web Server + WebSocket): Sử dụng #include <DIYables_ESP32_WebServer.h>
  • Chỉ vậy thôi! - Tính năng WebSocket tự động có sẵn khi cần
  • Tiết kiệm bộ nhớ - Code WebSocket không sử dụng được tự động tối ưu hóa bởi compiler

Ghi Nhận Công Trạng: Triển khai WebSocket được chuyển thể từ thư viện mWebSockets (Giấy phép LGPL-2.1) của Dawid Kurek, được sửa đổi và tích hợp để tương thích liền mạch với ESP32.

Các Class Của Thư Viện

Class DIYables_ESP32_WebServer

Class chính để tạo và quản lý web server.

Constructor

DIYables_ESP32_WebServer(int port = 80)

Tạo một phiên bản web server trên cổng được chỉ định (mặc định: 80).

Các Phương Thức

begin()
void begin() void begin(const char* ssid, const char* password)

Khởi động web server và bắt đầu lắng nghe các kết nối đến.

Các phiên bản nạp chồng:

  • begin(): Chỉ khởi động server (WiFi phải được kết nối riêng bởi ứng dụng của bạn)
  • begin(ssid, password): Kết nối WiFi và khởi động server trong một lệnh gọi (cách tiếp cận cũ)
setNotFoundHandler()
void setNotFoundHandler(RouteHandler handler)

Đặt một handler tùy chỉnh cho phản hồi 404 Not Found.

printWifiStatus()
void printWifiStatus()

In trạng thái kết nối WiFi và địa chỉ IP ra Serial Monitor.

handleClient()
void handleClient()

Xử lý các yêu cầu client đến. Điều này nên được gọi liên tục trong vòng lặp chính.

on()
void on(const String &uri, HTTPMethod method, THandlerFunction fn) void on(const String &uri, THandlerFunction fn) // mặc định là GET

Đăng ký một hàm handler cho URI và phương thức HTTP cụ thể.

Tham số:

  • uri: Đường dẫn URI (ví dụ: "/", "/led", "/api/data")
  • method: Phương thức HTTP (GET, POST, PUT, DELETE, v.v.)
  • fn: Hàm handler để thực thi khi route được truy cập

Lưu ý: Thư viện này sử dụng phương thức addRoute() thay vì on(). Xem bên dưới để biết cách sử dụng đúng.

addRoute()
void addRoute(const String &uri, RouteHandler handler)

Đăng ký một hàm handler cho URI cụ thể. Đây là phương thức thực tế được sử dụng trong thư viện.

Định Dạng Hàm RouteHandler:

Các handler route phải tuân theo chữ ký chính xác này:

void handlerFunction(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData)

Tham số:

  • client: Tham chiếu WiFiClient để gửi phản hồi
  • method: Phương thức HTTP dưới dạng chuỗi ("GET", "POST", v.v.)
  • request: URI yêu cầu đầy đủ
  • params: Tham số query (đối tượng QueryParams)
  • jsonData: JSON payload cho yêu cầu POST (rỗng cho GET)

Ví Dụ Triển Khai Handler:

  1. Handler GET Cơ Bản:
void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { String response = "<html><body><h1>Hello World</h1></body></html>"; server.sendResponse(client, response.c_str()); } } void setup() { server.addRoute("/", handleHome); }
  1. Handler JSON API (GET và POST):
void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "POST") { if (jsonData.length() == 0) { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"No JSON data received\"}"); return; } // Xử lý dữ liệu JSON StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, jsonData); if (error) { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Invalid JSON\"}"); return; } const char* key = doc["key"] | "none"; String response = "{\"status\":\"success\",\"received_key\":\"" + String(key) + "\"}"; server.sendResponse(client, response.c_str(), "application/json"); } else if (method == "GET") { String response = "{\"status\":\"success\",\"message\":\"GET request received\"}"; server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } } void setup() { server.addRoute("/api/data", handleApiData); }
  1. Handler Query Parameters:
void handleLedControl(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { // Truy cập tham số query String action = params.getValue("action"); // ?action=on if (action == "on") { digitalWrite(LED_PIN, HIGH); server.sendResponse(client, "LED turned ON"); } else if (action == "off") { digitalWrite(LED_PIN, LOW); server.sendResponse(client, "LED turned OFF"); } else { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: text/plain"); client.println("Connection: close"); client.println(); client.print("Invalid action. Use ?action=on or ?action=off"); } } } void setup() { server.addRoute("/led", handleLedControl); }
sendResponse()
void sendResponse(WiFiClient& client, const char* content, const char* contentType = "text/html")

Gửi phản hồi HTTP đến client.

Tham số:

  • client: Tham chiếu WiFiClient (được cung cấp trong hàm handler)
  • content: Nội dung phần thân phản hồi
  • contentType: Loại MIME của phản hồi (mặc định: "text/html")

Ví Dụ Sử Dụng:

// Gửi phản hồi HTML server.sendResponse(client, "<h1>Hello World</h1>"); // Gửi phản hồi JSON server.sendResponse(client, "{\"status\":\"ok\"}", "application/json"); // Gửi văn bản thuần server.sendResponse(client, "Success", "text/plain");
Các Phương Thức Authentication
enableAuthentication()
void enableAuthentication(const char* username, const char* password, const char* realm = "ESP32 Server")

Kích hoạt HTTP Basic Authentication cho tất cả các route. Khi được kích hoạt, tất cả routes đều yêu cầu authentication.

Tham số:

  • username: Tên người dùng để authentication (tối đa 31 ký tự)
  • password: Mật khẩu để authentication (tối đa 31 ký tự)
  • realm: Realm authentication hiển thị trong trình duyệt (tối đa 63 ký tự, tùy chọn)

Ví Dụ Sử Dụng:

// Kích hoạt authentication với realm mặc định server.enableAuthentication("admin", "password123"); // Kích hoạt authentication với realm tùy chỉnh server.enableAuthentication("admin", "secure456", "My ESP32 Device");
disableAuthentication()
void disableAuthentication()

Vô hiệu hóa authentication, làm cho tất cả routes có thể truy cập công khai trở lại.

Ví Dụ Sử Dụng:

server.disableAuthentication();
isAuthenticationEnabled()
bool isAuthenticationEnabled()

Trả về true nếu authentication hiện đang được kích hoạt, ngược lại là false.

Ví Dụ Sử Dụng:

if (server.isAuthenticationEnabled()) { Serial.println("Authentication is active"); } else { Serial.println("All routes are public"); }
send401()
void send401(WiFiClient& client)

Gửi phản hồi 401 Unauthorized với header WWW-Authenticate phù hợp. Điều này được tự động gọi khi authentication thất bại, nhưng có thể được sử dụng thủ công trong các handler tùy chỉnh.

Ví Dụ Sử Dụng:

// Phản hồi 401 thủ công trong handler tùy chỉnh server.send401(client);
Gửi Phản Hồi Thủ Công

Để kiểm soát nhiều hơn về HTTP headers và mã trạng thái:

void sendCustomResponse(WiFiClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println("Connection: close"); client.println(); client.print("{\"custom\":\"response\"}"); }

Truy Cập Query Parameters

Cấu Trúc QueryParams

Đối tượng QueryParams chứa các tham số query được phân tích từ URL:

struct QueryParams { int count; // Số lượng tham số struct { const char* key; // Tên tham số const char* value; // Giá trị tham số } params[MAX_PARAMS]; }

Truy Cập Query Parameters

void handleWithParams(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { // Truy cập tham số cụ thể bằng cách lặp qua String unit = "C"; // giá trị mặc định for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == "unit") { unit = params.params[i].value; break; } } // Sử dụng tham số String response = "Unit selected: " + unit; server.sendResponse(client, response.c_str()); } // Ví dụ URL: // /temperature?unit=F // /led?state=on&brightness=50

Các Hàm Hỗ Trợ Parameter

Tạo các hàm hỗ trợ để truy cập parameter dễ dàng hơn:

String getParam(const QueryParams& params, const String& key, const String& defaultValue = "") { for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == key) { return String(params.params[i].value); } } return defaultValue; } bool hasParam(const QueryParams& params, const String& key) { for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == key) { return true; } } return false; } // Sử dụng trong handlers: void handleLed(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { String state = getParam(params, "state", "off"); int brightness = getParam(params, "brightness", "100").toInt(); if (state == "on") { digitalWrite(LED_PIN, HIGH); server.sendResponse(client, "LED turned ON with brightness " + String(brightness)); } else { digitalWrite(LED_PIN, LOW); server.sendResponse(client, "LED turned OFF"); } }

Các Class WebSocket (Tích Hợp Sẵn)

WebSocketServer
WebSocketServer wsServer(81); // Cổng 81 cho WebSocket

Alias cho net::WebSocketServer - được đơn giản hóa cho người mới bắt đầu.

WebSocket
WebSocket ws;

Alias cho net::WebSocket - đại diện cho một kết nối WebSocket.

Các Phương Thức WebSocket
begin()
void begin()

Khởi động WebSocket server.

loop()
void loop()

Xử lý các sự kiện WebSocket. Gọi điều này trong vòng lặp chính của bạn.

onConnection()
void onConnection([](WebSocket &ws) { // Xử lý kết nối WebSocket mới });

Đặt callback cho các kết nối WebSocket mới.

onMessage()
void onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { // Xử lý tin nhắn WebSocket đến });

Đặt callback cho các tin nhắn WebSocket đến.

onClose()
void onClose([](WebSocket &ws, const WebSocket::CloseCode code, const char *reason, uint16_t length) { // Xử lý đóng kết nối WebSocket });

Đặt callback cho việc đóng kết nối WebSocket.

send()
void send(const String &message) void send(const char *message, size_t length)

Gửi tin nhắn qua WebSocket.

close()
void close()

Đóng kết nối WebSocket.

Các Phương Thức WebSocket Bổ Sung
broadcastTXT()
void broadcastTXT(const char* payload) void broadcastTXT(const String& payload)

Phát tin nhắn văn bản đến tất cả client WebSocket đã kết nối.

broadcastBIN()
void broadcastBIN(const uint8_t* payload, size_t length)

Phát dữ liệu nhị phân đến tất cả client WebSocket đã kết nối.

connectedClients()
size_t connectedClients()

Trả về số lượng client WebSocket hiện đang kết nối.

isListening()
bool isListening()

Trả về true nếu WebSocket server đang actively lắng nghe các kết nối.

Các Loại Sự Kiện WebSocket
DataType Enum
  • WebSocket::DataType::TEXT - Loại tin nhắn văn bản
  • WebSocket::DataType::BINARY - Loại dữ liệu nhị phân
CloseCode Enum

Các mã đóng WebSocket chuẩn cho lý do kết thúc kết nối.

Sử Dụng WebSocket Nâng Cao

Thiết Lập Event Handler

void setup() { Serial.begin(9600); // Khởi tạo WiFi và servers WiFi.begin(ssid, password); server.begin(); wsServer.begin(); // Thiết lập các event handler WebSocket wsServer.onConnection([](WebSocket &ws) { Serial.print("Client connected from: "); Serial.println(ws.getRemoteIP()); ws.send("{\"type\":\"welcome\",\"message\":\"Connected to ESP32\"}"); }); wsServer.onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { handleWebSocketMessage(ws, message, length); }); wsServer.onClose([](WebSocket &ws, const WebSocket::CloseCode code, const char *reason, uint16_t length) { Serial.println("Client disconnected"); }); }

Xử Lý Tin Nhắn

void handleWebSocketMessage(WebSocket &ws, const char *message, uint16_t length) { String msg = String(message); Serial.println("Received: " + msg); // Xử lý tin nhắn JSON if (msg.indexOf("\"type\":\"led\"") >= 0) { if (msg.indexOf("\"action\":\"on\"") >= 0) { digitalWrite(LED_PIN, HIGH); ws.send("{\"type\":\"led_status\",\"status\":\"on\"}"); } else if (msg.indexOf("\"action\":\"off\"") >= 0) { digitalWrite(LED_PIN, LOW); ws.send("{\"type\":\"led_status\",\"status\":\"off\"}"); } } // Chức năng echo String response = "Echo: " + msg; ws.send(response.c_str()); }

Phát Dữ Liệu Cảm Biến

void loop() { server.handleClient(); wsServer.loop(); // Phát dữ liệu cảm biến mỗi 5 giây static unsigned long lastBroadcast = 0; if (millis() - lastBroadcast > 5000) { if (wsServer.connectedClients() > 0) { float temperature = getTemperature(); String sensorData = "{"; sensorData += "\"type\":\"sensor\","; sensorData += "\"temperature\":" + String(temperature, 1) + ","; sensorData += "\"timestamp\":" + String(millis()); sensorData += "}"; wsServer.broadcastTXT(sensorData); } lastBroadcast = millis(); } }

Các Phương Thức HTTP

Thư viện hỗ trợ các phương thức HTTP chuẩn:

  • HTTP_GET
  • HTTP_POST
  • HTTP_PUT
  • HTTP_DELETE
  • HTTP_PATCH
  • HTTP_HEAD
  • HTTP_OPTIONS

Tích Hợp JavaScript Phía Client

Kết Nối WebSocket Cơ Bản

// Kết nối đến WebSocket server ESP32 const ws = new WebSocket('ws://your-esp32-ip:81'); ws.onopen = function(event) { console.log('Connected to ESP32 WebSocket'); document.getElementById('status').textContent = 'Connected'; }; ws.onmessage = function(event) { console.log('Received:', event.data); try { const data = JSON.parse(event.data); handleEsp32Message(data); } catch (e) { // Xử lý tin nhắn văn bản thuần console.log('Text message:', event.data); } }; ws.onclose = function(event) { console.log('Disconnected from ESP32'); document.getElementById('status').textContent = 'Disconnected'; // Tự động kết nối lại sau 3 giây setTimeout(connectWebSocket, 3000); }; ws.onerror = function(error) { console.error('WebSocket error:', error); };

Gửi Lệnh

// Gửi lệnh điều khiển LED function controlLED(action) { if (ws.readyState === WebSocket.OPEN) { const command = { type: 'led', action: action, timestamp: Date.now() }; ws.send(JSON.stringify(command)); } } // Gửi yêu cầu dữ liệu cảm biến function requestSensorData() { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({type: 'get_sensor'})); } }

Xử Lý Tin Nhắn

function handleEsp32Message(data) { switch(data.type) { case 'sensor': updateTemperatureDisplay(data.temperature); break; case 'led_status': updateLEDStatus(data.status); break; case 'welcome': console.log('Welcome message:', data.message); break; default: console.log('Unknown message type:', data); } }

Ví Dụ WebSocket

Echo Server Đơn Giản

wsServer.onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { String response = "Echo: " + String(message); ws.send(response.c_str()); });

Xử Lý Lệnh JSON

void processWebSocketCommand(WebSocket &ws, const String& message) { if (message.indexOf("\"type\":\"led\"") >= 0) { if (message.indexOf("\"action\":\"on\"") >= 0) { digitalWrite(LED_PIN, HIGH); ws.send("{\"type\":\"led_status\",\"status\":\"on\",\"success\":true}"); } else if (message.indexOf("\"action\":\"off\"") >= 0) { digitalWrite(LED_PIN, LOW); ws.send("{\"type\":\"led_status\",\"status\":\"off\",\"success\":true}"); } } else if (message.indexOf("\"type\":\"get_sensor\"") >= 0) { float temp = getTemperature(); String response = "{\"type\":\"sensor\",\"temperature\":" + String(temp, 1) + "}"; ws.send(response.c_str()); } }

Triển Khai Heartbeat

void setupHeartbeat() { static unsigned long lastHeartbeat = 0; if (millis() - lastHeartbeat > 30000) { // Mỗi 30 giây if (wsServer.connectedClients() > 0) { String heartbeat = "{\"type\":\"heartbeat\",\"timestamp\":" + String(millis()) + "}"; wsServer.broadcastTXT(heartbeat); } lastHeartbeat = millis(); } }

Khắc Phục Sự Cố WebSocket

Các Vấn Đề Thường Gặp

Kết Nối WebSocket Thất Bại

  • Xác minh cổng WebSocket server (mặc định: 81) có thể truy cập được
  • Đảm bảo địa chỉ IP ESP32 đúng và có thể tiếp cận
  • Sử dụng công cụ phát triển trình duyệt để kiểm tra lỗi kết nối WebSocket

Không Nh