ESP32 - Sensor de Cor via Web

Neste projeto divertido, você vai conectar um sensor de cor TCS3200D/TCS230 a um ESP32 e enviar as cores detectadas para um navegador web em tempo real. A página web tem um Minion animado e divertido, cuja cor da pele muda ao vivo conforme o que o sensor enxerga. Para simplificar a criação da interface web e o gerenciamento da comunicação WebSocket, este projeto usa a biblioteca DIYables ESP32 WebApps.

ESP32 TCS3200 TCS230 sensor de cor web Minion

Veja um resumo do que acontece:

Um passo a passo em vídeo também está disponível no final deste tutorial.

Pré-requisitos

Se você é iniciante no sensor de cor TCS3200D/TCS230 ou no DIYables ESP32 WebApps, estes tutoriais vão ajudar você a se atualizar:

Diagrama de Ligação

O diagrama abaixo mostra como ligar o sensor de cor TCS3200 a um ESP32:

Sensor de Cor TCS3200ESP32
VCC5V (VIN)
GNDGND
S0GPIO 17
S1GPIO 16
S2GPIO 18
S3GPIO 5
OUTGPIO 19
Diagrama de ligação do ESP32 e do sensor de cor TCS3200

This image is created using Fritzing. Click to enlarge image

Se você não sabe como alimentar o ESP32 e outros componentes, veja as instruções no tutorial a seguir: Como alimentar o ESP32.

Como Funciona

Este é o fluxo passo a passo deste projeto:

  1. A cada segundo, o ESP32 lê o sensor de cor alternando entre os filtros vermelho, verde e azul com os pinos de controle S2/S3 e medindo a largura de pulso no pino OUT.
  2. Os valores brutos de largura de pulso são convertidos em valores RGB de 0 a 255 usando os dados de calibração (obtidos na etapa de calibração do sensor).
  3. Os valores RGB são formatados em uma string de cor HEX, como #FF8000.
  4. Essa string de cor é enviada a todos os navegadores conectados por WebSocket, por meio da biblioteca DIYables ESP32 WebApps.
  5. Na página web, o JavaScript recebe a cor e a aplica na hora ao corpo, aos braços e às pálpebras do personagem Minion.

Código ESP32 - Aplicativo Web Minion com Sensor de Cor

Este projeto tem 4 arquivos:

  • ColorSensorESP32.ino - Sketch principal: inicializa o sensor, lê as cores e as envia para a página web
  • CustomWebApp.h - Arquivo de cabeçalho: declara a classe da página do aplicativo web personalizado
  • CustomWebApp.cpp - Arquivo de implementação: gerencia as mensagens WebSocket usando o identificador "Color sensor:"
  • custom_page_html.h - Página web: o Minion animado feito com HTML/CSS/JavaScript que reage às cores recebidas

ColorSensorESP32.ino

/* * Este código de ESP32 foi desenvolvido por pt.newbiely.com * Este código de ESP32 é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/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

/* * Este código de ESP32 foi desenvolvido por pt.newbiely.com * Este código de ESP32 é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/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

/* * Este código de ESP32 foi desenvolvido por pt.newbiely.com * Este código de ESP32 é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/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

/* * Este código de ESP32 foi desenvolvido por pt.newbiely.com * Este código de ESP32 é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/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

Passos Rápidos

Siga estes passos para colocar o projeto para funcionar:

  • Se esta é a primeira vez que você trabalha com o ESP32, veja o tutorial sobre como configurar o ambiente de desenvolvimento do ESP32 no Arduino IDE.
  • Faça a calibração primeiro usando o guia de calibração do TCS3200D/TCS230 para ESP32. Anote os resultados da calibração (redMin, redMax, greenMin, greenMax, blueMin, blueMax).
  • Conecte o hardware como mostra o diagrama de ligação acima.
  • Conecte a placa ESP32 ao seu computador com um cabo USB.
  • Abra o Arduino IDE.
  • Escolha a placa ESP32 correta (por exemplo, ESP32 Dev Module) e a porta COM correta.
  • Vá até o ícone de Bibliotecas na barra lateral esquerda do Arduino IDE.
  • Pesquise por "DIYables ESP32 WebApps" e encontre a biblioteca da DIYables.
  • Clique em Instalar para instalá-la.
  • Quando o IDE perguntar sobre dependências adicionais, clique em Instalar Tudo.
Biblioteca DIYables ESP32 WebApps
Dependência da DIYables ESP32 WebApps
  • Crie um novo sketch no Arduino IDE e dê a ele o nome ColorSensorESP32.
  • Copie os 4 arquivos listados acima para o projeto. O seu Arduino IDE deve mostrar 4 abas, assim:
Arquivos do projeto de aplicativo web com sensor de cor ESP32 no Arduino IDE
  • Em ColorSensorESP32.ino, troque as credenciais de Wi-Fi pelos dados da sua própria rede:
const char WIFI_SSID[] = "YOUR_WIFI_NAME"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
  • Troque os valores de calibração nas chamadas de map() dentro de loop() pelos números que você anotou durante a calibração. Por exemplo, se a sua calibração deu redMin = 42, redMax = 210, greenMin = 55, greenMax = 185, blueMin = 60, blueMax = 172, atualize as linhas para:
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);
  • Clique no botão Upload para gravar o código no ESP32.
  • Abra o Serial Monitor. Você deve ver algo assim:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
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
Ln 11, Col 1
ESP32 Dev Module on COM15
2
  • Se nada aparecer, tente apertar o botão de reset do ESP32.
  • Copie o endereço IP mostrado no Serial Monitor e abra-o em um navegador web no seu celular ou computador.
  • Por exemplo: http://192.168.0.5
  • Na página inicial, toque no cartão Color sensor WebApp para abrir a página do Minion.
  • Ou, se preferir, vá direto para http://[IP_ADDRESS]/custom.
  • Você verá o Minion animado e sorridente na sua tela.
  • Segure um objeto colorido perto do sensor TCS3200 — a cor da pele do Minion vai mudar na hora para refletir a cor detectada!

Você pode seguir o guia passo a passo em vídeo abaixo.

Entendendo o Código

Lado do ESP32 (ColorSensorESP32.ino)

O sketch principal faz estas tarefas:

  • Configura o sensor TCS3200: Configura S0/S1 para escala de frequência de 20% e prepara S2/S3 para a seleção do filtro.
  • Lê a cor uma vez por segundo: Dentro de loop(), o ESP32 percorre os filtros de cor vermelho, verde e azul, mede a largura de pulso com pulseIn() e converte cada leitura em um valor de 0 a 255 usando map() com os seus dados de calibração.
  • Formata como HEX: Os três valores RGB são combinados em uma string HEX (por exemplo, #FF8000) usando sprintf() e constrain().
  • Envia aos navegadores: A cor HEX é transmitida a todos os clientes web conectados por meio de customPage.sendToWeb().

Lado da Página Web (custom_page_html.h)

O arquivo HTML contém:

  • Um Minion animado só com CSS: O personagem tem olhos que piscam, uma boca rindo com a língua balançando e pupilas que se movem de forma aleatória — tudo com animações CSS e um pequeno intervalo em JavaScript.
  • Ouvinte de WebSocket: O JavaScript abre uma conexão persistente com o servidor WebSocket do ESP32 na porta 81 e processa as mensagens de cor recebidas.
  • Aplicação da cor ao vivo: Cada cor HEX recebida é aplicada suavemente ao corpo, aos braços e às pálpebras do Minion, usando uma transition do CSS para um efeito visual fluido.
  • Reconexão automática: Se o WebSocket se desconectar, a página tenta reconectar a cada 2 segundos, sem precisar de ação do usuário.
  • Layout responsivo: O Minion se ajusta automaticamente a qualquer tamanho de tela, de celulares a desktops.

Protocolo de Mensagens

Este projeto segue o framework de aplicativos personalizados do DIYables ESP32 WebApps. As mensagens são marcadas com o identificador "Color sensor:":

  • O ESP32 envia: Color sensor:#FF8000 (prefixo identificador + valor de cor HEX)
  • O navegador recebe: O JavaScript remove o prefixo Color sensor: e aplica o restante #FF8000 ao Minion

Para saber mais sobre esse padrão de comunicação e como criar os seus próprios aplicativos personalizados, visite o tutorial DIYables ESP32 WebApps Custom WebApp.

Tutoriais Relacionados

※ NOSSAS MENSAGENS

  • Fique à vontade para compartilhar o link deste tutorial. Porém, por favor, não use nosso conteúdo em outros sites. Investimos muito esforço e tempo para criar este conteúdo, por favor respeite nosso trabalho!