Arduino UNO R4 - Color Sensor via Web

This fun project combines the TCS3200D/TCS230 color sensor with a web-based Minion character. The Arduino UNO R4 WiFi reads colors from the sensor and sends the detected color to a web browser via WebSocket. The Minion on the web page changes its skin color in real-time to match whatever color you put in front of the sensor! To make it easy to build the web interface and handle real-time communication, we will use the DIYables WebApps library.

We also provide a step-by-step video instruction at the bottom of this tutorial.

Arduino UNO R4 TCS3200 TCS230 color sensor web Minion

Overview of TCS3200D/TCS230 Color Sensor and DIYables WebApps

Learn about the TCS3200D/TCS230 color sensor, DIYables WebApps library, and how to create custom web apps in the tutorials below:

Wiring Diagram

This image shows how to connect the TCS3200 color sensor to Arduino UNO R4 WiFi:

TCS3200 Color SensorArduino UNO R4
VCC5V
GNDGND
S0Pin 4
S1Pin 3
S2Pin 6
S3Pin 5
OUTPin 7
The wiring diagram between Arduino UNO R4 and TCS3200 color sensor  showing connection between pins

This image is created using Fritzing. Click to enlarge image

See The best way to supply power to the Arduino Uno R4 and other components.

How It Works

  1. The Arduino reads the color sensor every 1 second.
  2. For each reading, it selects the red, green, and blue filters one at a time using the S2/S3 pins, then measures the pulse width on the OUT pin.
  3. The raw pulse width values are mapped to 0-255 RGB values using calibration values (from the calibration step).
  4. The RGB values are converted to a HEX color string (e.g., #FF8000).
  5. The HEX color string is sent to the web browser via WebSocket using the DIYables WebApps library.
  6. The web page receives the color and updates the Minion's body, arms, and eyelids to that color in real-time.

Arduino UNO R4 Code - Color Sensor Minion Web App

The code consists of 4 files:

  • ColorSensor.ino - Main Arduino sketch: reads the color sensor and sends the color to the web page
  • CustomWebApp.h - Header file: defines the custom web app page class
  • CustomWebApp.cpp - Implementation file: handles WebSocket communication with the "Color Sensor:" identifier
  • custom_page_html.h - Web page: contains the animated Minion HTML/CSS/JavaScript that receives colors and updates the Minion's skin

ColorSensor.ino

/* * This Arduino UNO R4 code was developed by newbiely.com * * This Arduino UNO R4 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-color-sensor-via-web */ #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"; int status = WL_IDLE_STATUS; // TCS3200 Pins 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); // Initialize TCS3200 pins pinMode(S0, OUTPUT); pinMode(S1, OUTPUT); pinMode(S2, OUTPUT); pinMode(S3, OUTPUT); pinMode(sensorOut, INPUT); digitalWrite(S0, HIGH); digitalWrite(S1, LOW); // 20% frequency scaling webAppsServer.addApp(&homePage); webAppsServer.addApp(&customPage); // Check for the WiFi module if (WiFi.status() == WL_NO_MODULE) { Serial.println("Communication with WiFi module failed!"); // Stop program execution while (true); } String fv = WiFi.firmwareVersion(); if (fv < WIFI_FIRMWARE_LATEST_VERSION) { Serial.println("Please upgrade the firmware"); } // Attempt to connect to WiFi network while (status != WL_CONNECTED) { // Connect to WPA/WPA2 network (change if using open or WEP) status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // Wait 10 seconds for connection delay(10000); } webAppsServer.begin(); Serial.println("Color Web Server Ready!"); } void loop() { webAppsServer.loop(); // Read color 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 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

/* * This Arduino UNO R4 code was developed by newbiely.com * * This Arduino UNO R4 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-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

/* * This Arduino UNO R4 code was developed by newbiely.com * * This Arduino UNO R4 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-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

/* * This Arduino UNO R4 code was developed by newbiely.com * * This Arduino UNO R4 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-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> 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://' + location.hostname + ':81'); 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); // 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; } }; } 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

Detailed Instructions

Follow these instructions step by step:

  • If this is your first time using the Arduino Uno R4 WiFi, refer to the tutorial on setting up the environment for Arduino Uno R4 WiFi in the Arduino IDE.
  • Calibrate your color sensor first by following the TCS3200D/TCS230 calibration guide. Note down your calibration values (redMin, redMax, greenMin, greenMax, blueMin, blueMax).
  • Wire the components according to the wiring diagram above.
  • Connect the Arduino Uno R4 WiFi board to your computer using a USB cable.
  • Launch the Arduino IDE on your computer.
  • Select the appropriate Arduino Uno R4 board (e.g., Arduino Uno R4 WiFi) and COM port.
  • Navigate to the Libraries icon on the left bar of the Arduino IDE.
  • Search "DIYables WebApps", then find the DIYables WebApps library by DIYables.
  • Click Install button to install the library.
  • You will be asked for installing some other library dependencies.
  • Click Install All button to install all library dependencies.
Arduino UNO R4 DIYables WebApps library
Arduino UNO R4 DIYables WebApps dependency
  • Create a new sketch in Arduino IDE and name it ColorSensor.
  • Copy and paste all 4 files above into the Arduino IDE project. The Arduino IDE project should have 4 files as shown below:
Arduino UNO R4 Color Sensor Web App project files in Arduino IDE
  • In the ColorSensor.ino file, update the Wi-Fi details (SSID and password) with your own:
const char WIFI_SSID[] = "YOUR_WIFI_NAME"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
  • Update the calibration values in the map() calls inside loop() with your own calibration values from the calibration step. For example, if your calibration gave you redMin = 42, redMax = 210, greenMin = 55, greenMax = 185, blueMin = 60, blueMax = 172, change the lines to:
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 the Upload button in Arduino IDE to upload the code to Arduino UNO R4.
  • Open the Serial Monitor. You will see output like this:
COM6
Send
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
Autoscroll Show timestamp
Clear output
9600 baud  
Newline  
  • If you do not see anything, reboot the Arduino board.
  • Take note of the IP address displayed, and enter this address into the address bar of a web browser on your smartphone or PC.
  • Example: http://192.168.0.2
  • You will see the home page. Click the Color Sensor WebApp link.
  • Or you can access the Minion page directly by IP address followed by /custom. For example: http://192.168.0.2/custom
  • You will see the animated Minion character on the web page.
  • Place a colored object in front of the TCS3200 sensor — the Minion's skin color will change in real-time to match the detected color!

You can see the step-by-step instructions in the video below.

How the Code Works

Arduino Side (ColorSensor.ino)

The main sketch does the following:

  • Initializes the TCS3200 sensor pins: S0, S1 are set for 20% frequency scaling. S2, S3 are used to select color filters.
  • Reads color every 1 second: In the loop(), the Arduino selects the red, green, and blue filters one by one, reads the pulse width using pulseIn(), and maps the raw values to 0-255 RGB values using your calibration numbers.
  • Converts to HEX: The RGB values are formatted into a HEX color string like #FF8000 using sprintf().
  • Sends to web browser: The HEX string is sent to all connected web clients via customPage.sendToWeb().

Web Page Side (custom_page_html.h)

The HTML page contains:

  • An animated Minion character built entirely with CSS — including blinking eyes, a laughing mouth, and randomly moving pupils.
  • WebSocket connection: The JavaScript connects to the Arduino's WebSocket server (port 81) and listens for incoming color messages.
  • Color update: When a message like #FF8000 is received, the Minion's body, arms, and eyelids smoothly transition to the new color using CSS transition.
  • Auto-reconnect: If the WebSocket connection drops, the page automatically tries to reconnect every 2 seconds.
  • Responsive design: The Minion scales automatically to fit different screen sizes (phones, tablets, desktops).

Communication Protocol

This project uses the DIYables WebApps custom web app framework with the identifier "Color Sensor:":

  • Arduino sends: Color Sensor:#FF8000 (identifier + HEX color)
  • Web page receives: Strips the identifier and applies the remaining HEX color #FF8000 to the Minion

For more details about the communication protocol and how to customize the web app, see the DIYables WebApps Custom WebApp tutorial.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!