ESP32 REST API Server với JSON Hướng Dẫn Đầy Đủ

WebServerJson Example - REST API Server

Tổng Quan

Ví dụ này trình bày cách tạo REST API server trên ESP32 để xử lý JSON requests và responses, hoàn hảo cho các ứng dụng web hiện đại và backend của ứng dụng di động.

Tính Năng

  • REST API endpoints với xử lý JSON request/response
  • Xử lý POST request với phân tích dữ liệu JSON
  • GET endpoints để truy xuất dữ liệu
  • JSON responses chuyên nghiệp với HTTP status codes chính xác
  • Xử lý lỗi với thông báo lỗi JSON phù hợp
  • Hỗ trợ CORS cho cross-origin requests

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 Thư Viện

Làm theo hướng dẫn từng bước:

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

Ví Dụ Web Server Json

  • Trên Arduino IDE, đi tới File Examples Web Server for ESP32 WebServerJson để mở code ví dụ

API Endpoints

GET /api/data

Trả về thông báo thành công với timestamp theo định dạng JSON.

Response:

{ "status": "success", "message": "GET request received", "timestamp": 12345 }

Giá trị timestamp biểu thị mili giây kể từ khi ESP32 khởi động (từ hàm millis()).

POST /api/data

Nhận dữ liệu JSON và trả về giá trị key đã nhận.

Request:

{ "key": "your_value" }

Response:

{ "status": "success", "message": "Data received", "received_key": "your_value" }

Hướng Dẫn Thiết Lập

1. Cấu Hình Mạng

Chỉnh sửa thông tin đăng nhập WiFi trong file WebServerJson.ino:

const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

2. Upload Code và Theo Dõi Output

  1. Kết nối ESP32 với máy tính của bạn
  2. Chọn đúng bo mạch và cổng trong Arduino IDE
  3. Upload sketch WebServerJson.ino
  4. Mở Serial Monitor (9600 baud)
  5. Đợi kết nối WiFi
  6. Ghi chú địa chỉ IP được hiển thị
  7. Nếu bạn không thấy địa chỉ IP trong Serial monitor, hãy nhấn nút reset trên bo mạch ESP32

Sử Dụng

Test với cURL

Thay thế your-esp32-ip bằng địa chỉ IP thực tế được hiển thị trong Serial Monitor của bạn.

Test GET Request
curl -X GET http://your-esp32-ip/api/data

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/data { "status": "success", "message": "GET request received", "timestamp": 12345 }

Timestamp hiển thị mili giây kể từ khi ESP32 khởi động.

Test POST Request với Dữ Liệu JSON
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}"

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}" { "status": "success", "message": "Data received", "received_key": "test_value" }
Test POST Request với Dữ Liệu Khác
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}"

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}" { "status": "success", "message": "Data received", "received_key": "hello_world" }
Test JSON Không Hợp Lệ (Error Response)
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}" { "status": "error", "message": "Invalid JSON" }
Test Thiếu JSON Body (Error Response)
curl -X POST http://your-esp32-ip/api/data

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data { "status": "error", "message": "No JSON data received" }
Test Method Không Được Hỗ Trợ (Error Response)
curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}"

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}" { "status": "error", "message": "Method not allowed" }
Test Endpoint Không Tồn Tại (404 Error)
curl -X GET http://your-esp32-ip/api/nonexistent

Kết Quả Mong Đợi:

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/nonexistent <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: GET</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

Test với Postman

Test GET Request
  1. Tạo GET request mới
  2. Đặt URL thành http://your-esp32-ip/api/data
  3. Gửi request
  4. Xác minh response chứa status, message và timestamp
Test POST Request
  1. Tạo POST request mới
  2. Đặt URL thành http://your-esp32-ip/api/data
  3. Thêm header: Content-Type: application/json
  4. Thêm JSON body: {"key": "test_value"}
  5. Gửi request
  6. Xác minh response hiển thị giá trị key đã nhận

Test Error Responses

Test xử lý lỗi bằng cách gửi các request không hợp lệ như được hiển thị trong code ví dụ:

Thiếu Dữ Liệu JSON
curl -X POST http://your-esp32-ip/api/data

Mong Đợi: {"status": "error","message": "No JSON data received"}

Định Dạng JSON Không Hợp Lệ
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

Mong Đợi: {"status": "error","message": "Invalid JSON"}

Thiếu Trường Key
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"other_field\": \"value\"}"

Mong Đợi: Key sẽ mặc định là "none" như trong code ví dụ: doc["key"] | "none"

HTTP Method Không Được Hỗ Trợ
curl -X DELETE http://your-esp32-ip/api/data

Mong Đợi: {"status": "error","message": "Method not allowed"}

Giải Thích Code

Cấu Hình Route

// Configure API routes server.addRoute("/api/data", handleApiData);

Cấu Trúc Handler Function

Tất cả handler functions phải tuân theo cấu trúc này:

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { // Handler implementation }

Phát Hiện Method và Xử Lý JSON

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { Serial.print("[API] "); Serial.print(method); Serial.print(" request received"); if (method == "POST") { if (jsonData.length() == 0) { Serial.println("Error: No JSON data received"); 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; } StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, jsonData); if (!error) { const char* key = doc["key"] | "none"; String response = JSON_RESPONSE; response.replace("%KEY%", key); server.sendResponse(client, response.c_str(), "application/json"); } else { 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\"}"); } } else if (method == "GET") { String response = JSON_GET_RESPONSE; response.replace("%TIMESTAMP%", String(millis())); 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\"}"); } }

Ví Dụ Tích Hợp

JavaScript Frontend

// Control LED async function controlLED(action) { try { const response = await fetch('http://your-esp32-ip/api/led', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: action }) }); const result = await response.json(); console.log('LED control result:', result); } catch (error) { console.error('Error:', error); } } // Get sensor data async function getSensorData() { try { const response = await fetch('http://your-esp32-ip/api/sensor'); const data = await response.json(); console.log('Sensor data:', data); } catch (error) { console.error('Error:', error); } }

Python Client

import requests import json

Control LED

def control_led(action): url = "http://your-esp32-ip/api/led" data = {"action": action} response = requests.post(url, json=data) return response.json()

Get status

def get_status(): url = "http://your-esp32-ip/api/status" response = requests.get(url) return response.json()

Usage

result = control_led("on") print(result) status = get_status() print(status)

Xử Lý Lỗi

HTTP Status Codes

  • 200: Thành công
  • 400: Bad Request (JSON không hợp lệ, thiếu tham số)
  • 404: Endpoint không tìm thấy
  • 405: Method không được phép
  • 500: Lỗi máy chủ nội bộ

Định Dạng Error Response

Dựa trên code ví dụ thực tế, các lỗi khác nhau trả về thông báo cụ thể:

Lỗi Thiếu Dữ Liệu JSON
{ "status": "error", "message": "No JSON data received" }

Trả về khi: POST request được gửi mà không có JSON body

Lỗi Định Dạng JSON Không Hợp Lệ
{ "status": "error", "message": "Invalid JSON" }

Trả về khi: Dữ liệu JSON không thể phân tích được (lỗi cú pháp)

Lỗi Method Không Được Phép
{ "status": "error", "message": "Method not allowed" }

Trả về khi: Sử dụng HTTP methods không được hỗ trợ (PUT, DELETE, PATCH, v.v.)

Lỗi 404 Not Found
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: [HTTP_METHOD]</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

Trả về khi: Truy cập endpoints không tồn tại

Tùy Chỉnh

Thêm Handler Functions Mới

// Create additional handler for a new endpoint void handleApiStatus(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { String json = "{\"status\":\"online\",\"uptime\":" + String(millis() / 1000) + "}"; server.sendResponse(client, json.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\"}"); } } // Register the new route in setup() server.addRoute("/api/status", handleApiStatus);

JSON Responses Dựa Trên Template

Ví dụ sử dụng template strings để định dạng JSON nhất quán:

const char JSON_RESPONSE[] PROGMEM = R"rawliteral( { "status": "success", "message": "Data received", "received_key": "%KEY%" } )rawliteral"; // Usage in handler String response = JSON_RESPONSE; response.replace("%KEY%", extractedValue); server.sendResponse(client, response.c_str(), "application/json");

Xử Lý Sự Cố

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

Vấn Đề Kết Nối

Nếu bạn không thể kết nối với API endpoints:

  1. Xác minh ESP32 của bạn đã kết nối với WiFi (kiểm tra Serial Monitor)
  2. Đảm bảo client và ESP32 của bạn cùng trên một mạng
  3. Kiểm tra địa chỉ IP có chính xác không
  4. Xác minh ESP32 chưa reset (điều này sẽ thay đổi IP)

Lỗi Phân Tích JSON

Nếu bạn nhận được responses "Invalid JSON":

  1. Đảm bảo Content-Type header được đặt thành application/json
  2. Xác minh cú pháp JSON của bạn hợp lệ
  3. Kiểm tra các ký tự đặc biệt được escape đúng cách
  4. Đảm bảo JSON payload không quá lớn (giới hạn hiện tại: 200 bytes)

Vấn Đề POST Request

Nếu POST requests trả về "No JSON data received":

  1. Xác minh bạn đang gửi JSON body với request
  2. Kiểm tra Content-Length header được đặt đúng
  3. Đảm bảo HTTP method thực sự là POST
  4. Test với JSON đơn giản như {"key": "test"}

Vấn Đề Memory

Nếu ESP32 trở nên không phản hồi:

  1. Theo dõi việc sử dụng memory - StaticJsonDocument sử dụng 200 bytes
  2. Giảm kích thước JSON_RESPONSE template nếu cần
  3. Cân nhắc sử dụng DynamicJsonDocument cho dữ liệu có kích thước biến đổi
  4. Kiểm tra memory leaks trong custom handler functions

Mẹo Tối Ưu Hiệu Suất

Tối Ưu Xử Lý JSON

// Use appropriate document size for your data StaticJsonDocument<200> doc; // For small JSON objects StaticJsonDocument<1024> doc; // For larger JSON objects // Reuse string objects to reduce memory allocation String response; response.reserve(256); // Pre-allocate space response = JSON_RESPONSE; response.replace("%KEY%", value);

Xử Lý Response Hiệu Quả

// Send responses directly for simple cases client.print(F("{\"status\":\"ok\",\"value\":")); client.print(sensorValue); client.print(F("}")); // Use PROGMEM for large static responses const char LARGE_RESPONSE[] PROGMEM = R"({ "status": "success", "data": { ... } })";

Bước Tiếp Theo

  • Khám phá WebServerQueryStrings.ino để xử lý URL parameters
  • Thử WebServerWithWebSocket.ino cho giao tiếp thời gian thực
  • Cân nhắc tích hợp với databases hoặc cloud services

Tài Nguyên Học Tập