ESP32 Cảm Biến Màu qua Web

Trong dự án thú vị này, bạn sẽ kết nối cảm biến màu TCS3200D/TCS230 với ESP32 và truyền màu sắc phát hiện được đến trình duyệt web theo thời gian thực. Trang web có nhân vật Minion hoạt hình vui nhộn với màu da cập nhật trực tiếp dựa trên những gì cảm biến nhìn thấy. Để đơn giản hóa việc xây dựng giao diện web và quản lý giao tiếp WebSocket, dự án này sử dụng Ví dụ WebApp tùy chỉnh cho ESP32 - Hướng dẫn giao diện web đơn giản cho người mới bắt đầu.

ESP32 tcs3200 tcs230 color sensor web minion

Dưới đây là tóm tắt những gì sẽ xảy ra:

Video hướng dẫn từng bước cũng có sẵn ở cuối tutorial này.

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×Module Cảm Biến Nhận Dạng Màu TCS3200D/TCS230
1×dây jumper
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)

Kiến Thức Cơ Bản

Nếu bạn mới sử dụng cảm biến màu TCS3200D/TCS230 hoặc DIYables ESP32 WebApps, các hướng dẫn sau sẽ giúp bạn nắm vững kiến thức:

Sơ Đồ Đấu Nối

Sơ đồ dưới đây cho thấy cách đấu nối cảm biến màu TCS3200 với ESP32:

Cảm Biến Màu TCS3200ESP32
VCC5V (VIN)
GNDGND
S0GPIO 17
S1GPIO 16
S2GPIO 18
S3GPIO 5
OUTGPIO 19
ESP32 and tcs3200 color sensor sơ đồ đấu dây

This image is created using Fritzing. Click to enlarge image

Nếu bạn chưa rõ cách cấp nguồn cho ESP32 và các linh kiện khác, xem: Cách Cung Cấp Nguồn Điện Cho ESP32.

Cách Hoạt Động

Dưới đây là quy trình từng bước của dự án này:

  1. Mỗi giây, ESP32 đọc cảm biến màu bằng cách chuyển đổi giữa các bộ lọc đỏ, xanh lá cây và xanh dương bằng các chân điều khiển S2/S3, và đo độ rộng xung tại chân OUT.
  2. Các giá trị độ rộng xung thô được chuyển đổi thành giá trị RGB 0-255 bằng dữ liệu hiệu chuẩn (có được từ ESP32 - Cảm Biến Màu TCS3200D/TCS230).
  3. Các giá trị RGB được định dạng thành chuỗi màu HEX như #FF8000.
  4. Chuỗi màu này được phát sóng đến tất cả trình duyệt web được kết nối qua WebSocket thông qua thư viện DIYables ESP32 WebApps.
  5. Trên trang web, JavaScript nhận màu và ngay lập tức áp dụng nó cho cơ thể, tay và mi mắt của nhân vật Minion.

Mã ESP32 - Ứng Dụng Web Minion Cảm Biến Màu

Dự án này bao gồm 4 file:

  • ColorSensorESP32.ino - Sketch chính: khởi tạo cảm biến, đọc màu sắc và gửi chúng đến trang web
  • CustomWebApp.h - Header file: khai báo class trang ứng dụng web tùy chỉnh
  • CustomWebApp.cpp - Implementation file: quản lý tin nhắn WebSocket sử dụng định danh "Color sensor:"
  • custom_page_html.h - Trang web: Minion hoạt hình được xây dựng bằng HTML/CSS/JavaScript phản ứng với màu sắc đến

ColorSensorESP32.ino

/* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-color-sensor-via-web */ #include <DIYables_ESP32_Platform.h> #include <DIYablesWebApps.h> #include "CustomWebApp.h" // CHANGE THESE TO YOUR WIFI DETAILS const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; // Configure TCS3200 pins for ESP32 const int S0 = 17; const int S1 = 16; const int S2 = 18; const int S3 = 5; const int sensorOut = 19; // Create server and pages ESP32ServerFactory serverFactory; DIYablesWebAppServer webAppsServer(serverFactory, 80, 81); DIYablesHomePage homePage; CustomWebAppPage customPage; unsigned long lastColorRead = 0; void setup() { Serial.begin(9600); delay(1000); Serial.println("Starting Custom WebApp..."); // Initialize TCS3200 pins pinMode(S0, OUTPUT); pinMode(S1, OUTPUT); pinMode(S2, OUTPUT); pinMode(S3, OUTPUT); pinMode(sensorOut, INPUT); // Set frequency scaling to 20% digitalWrite(S0, HIGH); digitalWrite(S1, LOW); // Add pages to server webAppsServer.addApp(&homePage); webAppsServer.addApp(&customPage); // Start WiFi and web server if (!webAppsServer.begin(WIFI_SSID, WIFI_PASSWORD)) { while (1) { Serial.println("Failed to connect to WiFi!"); delay(1000); } } Serial.println("Custom WebApp ready!"); customPage.sendToWeb("Arduino is ready!"); } void loop() { // Handle web server webAppsServer.loop(); // Send sensor data every 1 second if (millis() - lastColorRead > 1000) { // Read Red color digitalWrite(S2, LOW); digitalWrite(S3, LOW); int r = map(pulseIn(sensorOut, LOW), 31, 150, 255, 0); // Read Green color digitalWrite(S2, HIGH); digitalWrite(S3, HIGH); int g = map(pulseIn(sensorOut, LOW), 35, 180, 255, 0); // Read Blue color digitalWrite(S2, LOW); digitalWrite(S3, HIGH); int b = map(pulseIn(sensorOut, LOW), 30, 150, 255, 0); // Convert to HEX color and send to Web char hexColor[8]; sprintf(hexColor, "#%02X%02X%02X", constrain(r, 0, 255), constrain(g, 0, 255), constrain(b, 0, 255)); customPage.sendToWeb(String(hexColor)); Serial.println("Sent to Minion: " + String(hexColor)); lastColorRead = millis(); } }

CustomWebApp.h

/* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-color-sensor-via-web */ #ifndef CUSTOM_WEBAPP_H #define CUSTOM_WEBAPP_H #include <DIYablesWebApps.h> /** * Simple Custom WebApp Page * * This is a template for creating your own custom web applications. * It provides basic controls like buttons and sliders that communicate * with your Arduino in real-time. */ class CustomWebAppPage : public DIYablesWebAppPageBase { private: // WebSocket message identifier for this custom app static const String APP_IDENTIFIER; public: CustomWebAppPage(); // ======================================== // REQUIRED METHODS - USED BY LIBRARY - DON'T CHANGE THESE! // ======================================== void handleHTTPRequest(IWebClient& client) override; void handleWebSocketMessage(IWebSocket& ws, const char* message, uint16_t length) override; const char* getPageInfo() const override; String getNavigationInfo() const override; // ======================================== // YOUR METHODS - USE THESE IN YOUR CODE! // ======================================== void onCustomMessageReceived(void (*callback)(const String& payload)); void sendToWeb(const String& message); }; #endif

CustomWebApp.cpp

/* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-color-sensor-via-web */ #include "CustomWebApp.h" #include "custom_page_html.h" // Define the static member - WebSocket message identifier for this custom app const String CustomWebAppPage::APP_IDENTIFIER = "Color sensor:"; // Callback function for handling messages from web browser void (*customMessageCallback)(const String& payload) = nullptr; CustomWebAppPage::CustomWebAppPage() : DIYablesWebAppPageBase("/custom") { } void CustomWebAppPage::handleHTTPRequest(IWebClient& client) { // Send the HTML page to web browser sendHTTPHeader(client); client.print(CUSTOM_PAGE_HTML); } void CustomWebAppPage::handleWebSocketMessage(IWebSocket& ws, const char* message, uint16_t length) { String messageStr = String(message, length); Serial.print("Color sensor WebApp received: "); Serial.println(messageStr); // Only handle messages that start with our app identifier if (messageStr.startsWith(APP_IDENTIFIER)) { String payload = messageStr.substring(APP_IDENTIFIER.length()); // Remove identifier // Call your callback function with the payload if (customMessageCallback) { customMessageCallback(payload); } } } void CustomWebAppPage::onCustomMessageReceived(void (*callback)(const String& payload)) { customMessageCallback = callback; } void CustomWebAppPage::sendToWeb(const String& message) { // Send message to web browser with app identifier String fullMessage = APP_IDENTIFIER + message; broadcastToAllClients(fullMessage.c_str()); } const char* CustomWebAppPage::getPageInfo() const { return "🔧 Color sensor WebApp"; } String CustomWebAppPage::getNavigationInfo() const { String result = "<a href=\""; result += getPagePath(); result += "\" class=\"app-card custom\" style=\"background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);\">"; result += "<h3>🔧 Color sensor WebApp</h3>"; result += "<p>Simple template for your own apps</p>"; result += "</a>"; return result; }

custom_page_html.h

/* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-color-sensor-via-web */ #ifndef CUSTOM_PAGE_HTML_H #define CUSTOM_PAGE_HTML_H const char CUSTOM_PAGE_HTML[] PROGMEM = R"HTML_WRAPPER( <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Mobile Laughing Minion</title> <style> /* GIỮ NGUYÊN TOÀN BỘ CSS GỐC CỦA BẠN */ body { margin: 0; padding: 20px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; background-color: #f0f8ff; font-family: sans-serif; overflow-x: hidden; } .text { font-size: clamp(16px, 5vw, 24px); font-weight: bold; color: #333; margin-bottom: 20px; text-align: center; z-index: 10; } .scale-wrapper { transform-origin: top center; display: flex; justify-content: center; align-items: flex-start; } .minion-container { position: relative; width: 200px; height: 400px; } .body { position: absolute; top: 20px; left: 25px; width: 150px; height: 300px; background-color: #FFD90F; border-radius: 75px; box-shadow: inset -10px -10px 20px rgba(0,0,0,0.1); overflow: hidden; z-index: 2; transition: background-color 0.5s; } .overalls { position: absolute; bottom: 0; width: 100%; height: 90px; background-color: #225A94; border-radius: 0 0 75px 75px; box-shadow: inset -10px -10px 20px rgba(0,0,0,0.2); } .pocket { position: absolute; bottom: 30px; left: 50px; width: 50px; height: 40px; background-color: #1A4674; border-radius: 10px 10px 20px 20px; border: 2px dashed #fce144; } .strap { position: absolute; top: 65px; left: 0; width: 100%; height: 25px; background-color: #333; z-index: 1; } .goggles-wrapper { position: absolute; top: 50px; left: -5px; width: 160px; display: flex; justify-content: center; z-index: 3; } .goggle { position: relative; width: 50px; height: 50px; background-color: white; border: 12px solid #999; border-radius: 50%; box-shadow: 3px 3px 8px rgba(0,0,0,0.2), inset 3px 3px 8px rgba(0,0,0,0.1); margin: 0 -2px; overflow: hidden; } .pupil { position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; background-color: #4B3621; border-radius: 50%; transform: translate(-50%, -50%); transition: transform 0.2s ease-out; } .pupil::after { content: ''; position: absolute; top: 4px; left: 4px; width: 6px; height: 6px; background-color: black; border-radius: 50%; } .catchlight { position: absolute; top: 2px; right: 4px; width: 4px; height: 4px; background-color: white; border-radius: 50%; z-index: 4; } .eyelid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #FFD90F; border-bottom: 3px solid #D4B200; transform-origin: top; transform: scaleY(0); z-index: 5; animation: blink 4s infinite; } .mouth { position: absolute; top: 145px; left: 35px; width: 80px; height: 45px; background-color: #3E2723; border-radius: 10px 10px 60px 60px; overflow: hidden; z-index: 3; box-shadow: inset 0 5px 10px rgba(0,0,0,0.5); animation: laugh 0.2s infinite alternate ease-in-out; } .teeth { position: absolute; top: 0; left: 0; width: 100%; height: 14px; background-color: #fff; border-radius: 0 0 5px 5px; } .tongue { position: absolute; bottom: -5px; left: 20px; width: 40px; height: 25px; background-color: #FF5252; border-radius: 50%; animation: wag 0.2s infinite alternate ease-in-out; } .arm { position: absolute; top: 140px; width: 25px; height: 80px; background-color: #FFD90F; border-radius: 12px; z-index: 1; transition: background-color 0.5s; } .arm.left { left: 10px; transform: rotate(35deg); } .arm.right { right: 15px; transform: rotate(-35deg); } .glove { position: absolute; bottom: -15px; left: -5px; width: 35px; height: 35px; background-color: #333; border-radius: 50%; } .leg { position: absolute; bottom: 50px; width: 25px; height: 40px; background-color: #225A94; z-index: 1; } .leg.left { left: 60px; } .leg.right { left: 115px; } .shoe { position: absolute; bottom: -15px; left: -10px; width: 45px; height: 20px; background-color: #222; border-radius: 20px 20px 5px 5px; border-bottom: 5px solid #111; } @keyframes blink { 0%, 94%, 100% { transform: scaleY(0); } 97% { transform: scaleY(1); } } @keyframes laugh { 0% { height: 40px; transform: scaleX(1); } 100% { height: 55px; transform: scaleX(1.05); } } @keyframes wag { 0% { transform: translateY(0); } 100% { transform: translateY(-3px); } } </style> </head> <body> <div class="text" id="status-text">Just watch him look around! 👀</div> <div class="scale-wrapper" id="minionWrapper"> <div class="minion-container"> <div class="arm left" id="armL"><div class="glove"></div></div> <div class="arm right" id="armR"><div class="glove"></div></div> <div class="leg left"><div class="shoe"></div></div> <div class="leg right"><div class="shoe"></div></div> <div class="body" id="minionBody"> <div class="overalls"> <div class="pocket"></div> </div> <div class="strap"></div> <div class="goggles-wrapper"> <div class="goggle"><div class="pupil"><div class="catchlight"></div></div><div class="eyelid" id="eyelidL"></div></div> <div class="goggle"><div class="pupil"><div class="catchlight"></div></div><div class="eyelid" id="eyelidR"></div></div> </div> <div class="mouth"> <div class="teeth"></div> <div class="tongue"></div> </div> </div> </div> </div> <script> // LOGIC KẾT NỐI WEBSOCKET const APP_IDENTIFIER = 'Color sensor:'; let ws = null; function connectWebSocket() { ws = new WebSocket('ws://' + location.hostname + ':81'); ws.onopen = () => document.getElementById('status-text').textContent = "ESP32 - Color Sensor"; ws.onclose = () => setTimeout(connectWebSocket, 2000); ws.onmessage = (event) => { if (event.data.startsWith(APP_IDENTIFIER)) { let color = event.data.substring(APP_IDENTIFIER.length); // Cập nhật màu cho thân, tay và mí mắt document.getElementById('minionBody').style.backgroundColor = color; document.getElementById('armL').style.backgroundColor = color; document.getElementById('armR').style.backgroundColor = color; document.getElementById('eyelidL').style.backgroundColor = color; document.getElementById('eyelidR').style.backgroundColor = color; document.getElementById('status-text').style.color = color; } }; } // GIỮ NGUYÊN LOGIC RESIZE & MẮT GỐC function resizeMinion() { const wrapper = document.getElementById('minionWrapper'); const availableWidth = window.innerWidth - 40; const minionTrueWidth = 260; const minionHeight = 400; let scaleFactor = availableWidth / minionTrueWidth; if (scaleFactor > 1.5) scaleFactor = 1.5; wrapper.style.transform = `scale(${scaleFactor})`; wrapper.style.height = `${minionHeight * scaleFactor}px`; } window.addEventListener('resize', resizeMinion); resizeMinion(); connectWebSocket(); const pupils = document.querySelectorAll('.pupil'); function moveEyesAutomatically() { const angle = Math.random() * Math.PI * 2; const distance = Math.random() * 15; const pupilX = Math.cos(angle) * distance; const pupilY = Math.sin(angle) * distance; pupils.forEach(pupil => { pupil.style.transform = `translate(calc(-50% + ${pupilX}px), calc(-50% + ${pupilY}px))`; }); } setInterval(moveEyesAutomatically, 600); </script> </body> </html> )HTML_WRAPPER"; #endif

Các Bước Nhanh

Thực hiện theo các bước sau để chạy dự án:

  • Nếu đây là lần đầu bạn làm việc với ESP32, hãy xem hướng dẫn về ESP32 - Cài Đặt Phần Mềm.
  • Chạy hiệu chuẩn trước bằng ESP32 - Cảm Biến Màu TCS3200D/TCS230. Ghi lại kết quả hiệu chuẩn của bạn (redMin, redMax, greenMin, greenMax, blueMin, blueMax).
  • Kết nối phần cứng như trong sơ đồ đấu nối ở trên.
  • Cắm board ESP32 vào máy tính với cáp USB.
  • Mở Arduino IDE.
  • Chọn board ESP32 đúng (ví dụ: ESP32 Dev Module) và COM port đúng.
  • Đi đến biểu tượng Libraries trên thanh bên trái của Arduino IDE.
  • Tìm kiếm "DIYables ESP32 WebApps" và tìm thư viện của DIYables.
  • Click Install để cài đặt.
  • Khi được nhắc về các phụ thuộc bổ sung, click Install All.
diyables ESP32 webapps thư viện
diyables ESP32 webapps dependency
  • Tạo một sketch mới trong Arduino IDE và đặt tên là ColorSensorESP32.
  • Copy tất cả 4 file được liệt kê ở trên vào dự án. Arduino IDE của bạn sẽ hiển thị 4 tab như này:
ESP32 color sensor web app project files in Arduino ide
  • Trong ColorSensorESP32.ino, thay thế thông tin đăng nhập Wi-Fi bằng chi tiết mạng của bạn:
const char WIFI_SSID[] = "TÊN_WIFI_CỦA_BẠN"; const char WIFI_PASSWORD[] = "MẬT_KHẨU_WIFI_CỦA_BẠN";
  • Thay thế các giá trị hiệu chuẩn trong các lệnh gọi map() bên trong loop() bằng các số bạn đã ghi lại trong quá trình hiệu chuẩn. Ví dụ, nếu hiệu chuẩn của bạn tạo ra redMin = 42, redMax = 210, greenMin = 55, greenMax = 185, blueMin = 60, blueMax = 172, cập nhật các dòng thành:
int r = map(pulseIn(sensorOut, LOW), 42, 210, 255, 0); int g = map(pulseIn(sensorOut, LOW), 55, 185, 255, 0); int b = map(pulseIn(sensorOut, LOW), 60, 172, 255, 0);
  • Click nút Upload để nạp code lên ESP32.
  • Mở Serial Monitor. Bạn sẽ thấy thứ gì đó như:
COM6
Send
Starting Custom WebApp... Custom WebApp ready! INFO: Added app / INFO: Added app /custom DIYables ESP32 WebApp Library Network connected! IP address: 192.168.0.5 HTTP server started on port 80 WebSocket server started on port 81 ========================================== DIYables WebApp Ready! ========================================== 📱 Web Interface: http://192.168.0.5 🔗 WebSocket: ws://192.168.0.5:81 📋 Available Applications: 🏠 Home Page: http://192.168.0.5/ 🔧 Color sensor WebApp: http://192.168.0.5/custom ========================================== Sent to Minion: #FFD200 Sent to Minion: #00C832 Sent to Minion: #0028FF
Autoscroll Show timestamp
Clear output
9600 baud  
Newline  
  • Nếu không có gì xuất hiện, hãy thử nhấn nút reset trên ESP32.
  • Copy địa chỉ IP hiển thị trong Serial Monitor và mở nó trong trình duyệt web trên điện thoại hoặc máy tính của bạn.
  • Ví dụ: http://192.168.0.5
  • Trên trang chủ, nhấn thẻ Color sensor WebApp để mở trang Minion.
  • Hoặc, truy cập trực tiếp http://[ĐỊA_CHỈ_IP]/custom.
  • Bạn sẽ thấy Minion hoạt hình đang cười trên màn hình.
  • Đưa một vật thể có màu gần cảm biến TCS3200 — màu da của Minion sẽ cập nhật ngay lập tức để phản ánh màu được phát hiện!

Bạn có thể làm theo video hướng dẫn từng bước bên dưới.

Hiểu Về Mã Code

Phía ESP32 (ColorSensorESP32.ino)

Sketch chính thực hiện các tác vụ sau:

  • Thiết lập cảm biến TCS3200: Cấu hình S0/S1 cho tỷ lệ tần số 20% và chuẩn bị S2/S3 cho việc chọn bộ lọc.
  • Lấy mẫu màu mỗi giây một lần: Bên trong loop(), ESP32 chu kỳ qua các bộ lọc màu đỏ, xanh lá cây và xanh dương, đo độ rộng xung bằng pulseIn(), và chuyển đổi mỗi lần đọc thành giá trị 0-255 bằng map() với dữ liệu hiệu chuẩn của bạn.
  • Định dạng thành HEX: Ba giá trị RGB được kết hợp thành chuỗi HEX (ví dụ: #FF8000) bằng sprintf()constrain().
  • Phát sóng đến trình duyệt: Màu HEX được truyền đến mọi web client được kết nối thông qua customPage.sendToWeb().

Phía Trang Web (custom_page_html.h)

File HTML chứa:

  • Minion hoạt hình chỉ bằng CSS: Nhân vật có mắt chớp, miệng cười với lưỡi vẫy, và đồng tử di chuyển ngẫu nhiên — tất cả đều được cung cấp bởi hoạt hình CSS và một JavaScript interval nhỏ.
  • WebSocket listener: JavaScript mở kết nối bền vững đến WebSocket server của ESP32 trên port 81 và xử lý tin nhắn màu đến.
  • Ứng dụng màu trực tiếp: Mỗi màu HEX nhận được được áp dụng một cách mượt mà cho cơ thể, tay và mi mắt của Minion bằng transition CSS cho hiệu ứng hình ảnh mượt mà.
  • Kết nối lại tự động: Nếu WebSocket ngắt kết nối, trang sẽ thử lại kết nối mỗi 2 giây mà không cần can thiệp của người dùng.
  • Layout responsive: Minion tự động thay đổi kích thước để phù hợp với bất kỳ kích thước màn hình nào, từ điện thoại đến desktop.

Giao Thức Tin Nhắn

Dự án này tuân theo framework ứng dụng tùy chỉnh DIYables ESP32 WebApps. Tin nhắn được gắn thẻ với định danh "Color sensor:":

  • ESP32 gửi: Color sensor:#FF8000 (tiền tố định danh + giá trị màu HEX)
  • Trình duyệt nhận: JavaScript loại bỏ tiền tố Color sensor: và áp dụng #FF8000 còn lại cho Minion

Để tìm hiểu thêm về mẫu giao tiếp này và cách xây dựng ứng dụng tùy chỉnh của riêng bạn, hãy truy cập Ví dụ WebApp tùy chỉnh cho ESP32 - Hướng dẫn giao diện web đơn giản cho người mới bắt đầu.

Bài hướng dẫn liên quan