Ce projet amusant combine le capteur de couleur TCS3200D/TCS230 avec un personnage Minion basé sur le web. L'Arduino UNO R4 WiFi lit les couleurs du capteur et envoie la couleur détectée à un navigateur web via WebSocket. Le Minion sur la page web change la couleur de sa peau en temps réel pour correspondre à la couleur que vous placez devant le capteur ! Pour faciliter la création de l'interface web et gérer la communication en temps réel, nous utiliserons la Exemple d'application Web personnalisée Arduino - Tutoriel d'une interface Web simple pour les débutants..
Nous fournissons également des instructions vidéo étape par étape en bas de ce tutoriel.
Cette image montre comment connecter le capteur de couleur TCS3200 à l'Arduino UNO R4 WiFi :
| Capteur de couleur TCS3200 | Arduino UNO R4 |
| VCC | 5V |
| GND | GND |
| S0 | Pin 4 |
| S1 | Pin 3 |
| S2 | Pin 6 |
| S3 | Pin 5 |
| OUT | Pin 7 |

Cette image a été créée avec Fritzing. Cliquez pour agrandir l'image.
Voir Comment alimenter l'Arduino UNO R4..
Le code comprend 4 fichiers :
ColorSensor.ino - Sketch Arduino principal : lit le capteur de couleur et envoie la couleur à la page web
CustomWebApp.h - Fichier d'en-tête : définit la classe de page d'application web personnalisée
CustomWebApp.cpp - Fichier d'implémentation : gère la communication WebSocket avec l'identifiant "Color Sensor:"
custom_page_html.h - Page web : contient le HTML/CSS/JavaScript du Minion animé qui reçoit les couleurs et met à jour la peau du Minion
ColorSensor.ino
#include <DIYablesWebApps.h>
#include "CustomWebApp.h"
const char WIFI_SSID[] = "YOUR_WIFI_SSID";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
int status = WL_IDLE_STATUS;
const int S0 = 4, S1 = 3, S2 = 6, S3 = 5, sensorOut = 7;
UnoR4ServerFactory serverFactory;
DIYablesWebAppServer webAppsServer(serverFactory, 80, 81);
DIYablesHomePage homePage;
CustomWebAppPage customPage;
unsigned long lastColorRead = 0;
void setup() {
Serial.begin(9600);
pinMode(S0, OUTPUT);
pinMode(S1, OUTPUT);
pinMode(S2, OUTPUT);
pinMode(S3, OUTPUT);
pinMode(sensorOut, INPUT);
digitalWrite(S0, HIGH);
digitalWrite(S1, LOW);
webAppsServer.addApp(&homePage);
webAppsServer.addApp(&customPage);
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
while (true);
}
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println("Please upgrade the firmware");
}
while (status != WL_CONNECTED) {
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
delay(10000);
}
webAppsServer.begin();
Serial.println("Color Web Server Ready!");
}
void loop() {
webAppsServer.loop();
if (millis() - lastColorRead > 1000) {
digitalWrite(S2, LOW);
digitalWrite(S3, LOW);
int r = map(pulseIn(sensorOut, LOW), 31, 150, 255, 0);
digitalWrite(S2, HIGH);
digitalWrite(S3, HIGH);
int g = map(pulseIn(sensorOut, LOW), 35, 180, 255, 0);
digitalWrite(S2, LOW);
digitalWrite(S3, HIGH);
int b = map(pulseIn(sensorOut, LOW), 30, 150, 255, 0);
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
#ifndef CUSTOM_WEBAPP_H
#define CUSTOM_WEBAPP_H
#include <DIYablesWebApps.h>
class CustomWebAppPage : public DIYablesWebAppPageBase {
private:
static const String APP_IDENTIFIER;
public:
CustomWebAppPage();
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;
void onCustomMessageReceived(void (*callback)(const String& payload));
void sendToWeb(const String& message);
};
#endif
CustomWebApp.cpp
#include "CustomWebApp.h"
#include "custom_page_html.h"
const String CustomWebAppPage::APP_IDENTIFIER = "Color Sensor:";
void (*customMessageCallback)(const String& payload) = nullptr;
CustomWebAppPage::CustomWebAppPage() : DIYablesWebAppPageBase("/custom") {
}
void CustomWebAppPage::handleHTTPRequest(IWebClient& client) {
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);
if (messageStr.startsWith(APP_IDENTIFIER)) {
String payload = messageStr.substring(APP_IDENTIFIER.length());
if (customMessageCallback) {
customMessageCallback(payload);
}
}
}
void CustomWebAppPage::onCustomMessageReceived(void (*callback)(const String& payload)) {
customMessageCallback = callback;
}
void CustomWebAppPage::sendToWeb(const String& message) {
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
#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>
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>
const APP_IDENTIFIER = 'Color Sensor:';
let ws = null;
function connectWebSocket() {
ws = new WebSocket('ws:
ws.onopen = () => document.getElementById('status-text').textContent = "Arduino Uno R4 - Color Sensor";
ws.onclose = () => setTimeout(connectWebSocket, 2000);
ws.onmessage = (event) => {
if (event.data.startsWith(APP_IDENTIFIER)) {
let color = event.data.substring(APP_IDENTIFIER.length);
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;
}
};
}
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
Suivez ces instructions étape par étape :
Câblez les composants selon le schéma de câblage ci-dessus.
Connectez la carte Arduino Uno R4 WiFi à votre ordinateur avec un câble USB.
Lancez l'IDE Arduino sur votre ordinateur.
Sélectionnez la carte Arduino Uno R4 appropriée (par exemple, Arduino Uno R4 WiFi) et le port COM.
Naviguez vers l'icône Libraries dans la barre de gauche de l'IDE Arduino.
Recherchez "DIYables WebApps", puis trouvez la bibliothèque DIYables WebApps par DIYables.
Cliquez sur le bouton Install pour installer la bibliothèque.
Il vous sera demandé d'installer d'autres dépendances de bibliothèque.
Cliquez sur le bouton Install All pour installer toutes les dépendances de bibliothèque.


Créez un nouveau sketch dans l'IDE Arduino et nommez-le ColorSensor.
Copiez et collez les 4 fichiers ci-dessus dans le projet IDE Arduino. Le projet IDE Arduino devrait avoir 4 fichiers comme montré ci-dessous :
const char WIFI_SSID[] = "VOTRE_NOM_WIFI";
const char WIFI_PASSWORD[] = "VOTRE_MOT_DE_PASSE_WIFI";
Mettez à jour les valeurs de calibration dans les appels map() à l'intérieur de loop() avec vos propres valeurs de calibration de l'étape de calibration. Par exemple, si votre calibration vous a donné redMin = 42, redMax = 210, greenMin = 55, greenMax = 185, blueMin = 60, blueMax = 172, changez les lignes en :
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);
Color Web Server Ready!
INFO: Added app /
INFO: Added app /custom
DIYables WebApp Library
Platform: Arduino Uno R4 WiFi
Network connected!
IP address: 192.168.0.2
HTTP server started on port 80
WebSocket server started on port 81
==========================================
DIYables WebApp Ready!
==========================================
📱 Web Interface: http://192.168.0.2
🔗 WebSocket: ws://192.168.0.2:81
📋 Available Applications:
🏠 Home Page: http://192.168.0.2/
🔧 Color Sensor WebApp: http://192.168.0.2/custom
==========================================
Sent to Minion: #FFD200
Sent to Minion: #00C832
Sent to Minion: #0028FF
Si vous ne voyez rien, redémarrez la carte Arduino.
Notez l'adresse IP affichée, et entrez cette adresse dans la barre d'adresse d'un navigateur web sur votre smartphone ou PC.
Exemple : http://192.168.0.2
Vous verrez la page d'accueil. Cliquez sur le lien Color Sensor WebApp.
Ou vous pouvez accéder directement à la page Minion par l'adresse IP suivie de /custom. Par exemple : http://192.168.0.2/custom
Vous verrez le personnage Minion animé sur la page web.
Placez un objet coloré devant le capteur TCS3200 — la couleur de peau du Minion changera en temps réel pour correspondre à la couleur détectée !
Vous pouvez voir les instructions étape par étape dans la vidéo ci-dessous.
Le sketch principal fait ce qui suit :
Initialise les broches du capteur TCS3200 : S0, S1 sont configurées pour une mise à l'échelle de fréquence de 20%. S2, S3 sont utilisées pour sélectionner les filtres de couleur.
Lit la couleur toutes les 1 seconde : Dans la loop(), l'Arduino sélectionne les filtres rouge, vert et bleu un par un, lit la largeur d'impulsion en utilisant pulseIn(), et mappe les valeurs brutes vers des valeurs RGB 0-255 en utilisant vos nombres de calibration.
Convertit en hexadécimal : Les valeurs RGB sont formatées en chaîne de couleur hexadécimale comme #FF8000 en utilisant sprintf().
Envoie au navigateur web : La chaîne hexadécimale est envoyée à tous les clients web connectés via customPage.sendToWeb().
La page HTML contient :
Un personnage Minion animé construit entièrement avec CSS — incluant des yeux qui clignent, une bouche qui rit, et des pupilles qui bougent aléatoirement.
Connexion WebSocket : Le JavaScript se connecte au serveur WebSocket de l'Arduino (port 81) et écoute les messages de couleur entrants.
Mise à jour des couleurs : Quand un message comme #FF8000 est reçu, le corps, les bras et les paupières du Minion font une transition fluide vers la nouvelle couleur en utilisant la transition CSS.
Reconnexion automatique : Si la connexion WebSocket se perd, la page essaie automatiquement de se reconnecter toutes les 2 secondes.
Design responsive : Le Minion s'adapte automatiquement pour s'ajuster aux différentes tailles d'écran (téléphones, tablettes, ordinateurs de bureau).
Ce projet utilise le framework d'application web personnalisée DIYables WebApps avec l'identifiant "Color Sensor:" :
Pour plus de détails sur le protocole de communication et comment personnaliser l'application web, consultez le Exemple d'application Web personnalisée Arduino - Tutoriel d'une interface Web simple pour les débutants..