Created
December 5, 2025 18:16
-
-
Save Ali-Shaikh/3b38a410079c8b1a5cd616ebf3ee0b98 to your computer and use it in GitHub Desktop.
ESP32 Traffic Light with Ultrasonic Sensor - Kids Electronics Project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <WiFi.h> | |
| #include <WebServer.h> | |
| #include <ArduinoOTA.h> | |
| // ================= Find full instructions and more projects like this at https://alishaikh.me/ =========== | |
| // ========== WIFI CONFIGURATION - CHANGE THESE ========== | |
| const char* ssid = ""; // Change to your WiFi name | |
| const char* password = ""; // Change to your WiFi password | |
| // Static IP Configuration | |
| IPAddress local_IP(192, 168, 0, 115); // Change to your desired IP | |
| IPAddress gateway(192, 168, 0, 1); // Usually your router IP | |
| IPAddress subnet(255, 255, 255, 0); // Subnet mask | |
| IPAddress primaryDNS(192, 168, 0, 1); // Optional | |
| // ======================================================= | |
| // OTA Configuration | |
| const char* ota_hostname = "TrafficLight"; // Name that appears in Arduino IDE | |
| // To generate your own: echo -n "yourpassword" | sha256sum | |
| const char* ota_password_hash = ""; | |
| // ======================================================= | |
| // Pin definitions - adjust these based on your actual wiring | |
| const int RED_LED = 25; | |
| const int YELLOW_LED = 26; | |
| const int GREEN_LED = 27; | |
| const int BTN_RED = 33; | |
| const int BTN_YELLOW = 32; | |
| const int BTN_GREEN = 35; | |
| // ========== PROXIMITY SENSOR PINS ========== | |
| const int TRIG_PIN = 18; // Ultrasonic sensor trigger | |
| const int ECHO_PIN = 19; // Ultrasonic sensor echo | |
| // ================================================ | |
| // ========== TIMING CONFIGURATION - ADJUST THESE ========== | |
| // Traffic Light Mode Timings (in milliseconds) | |
| const unsigned long TRAFFIC_RED_TIME = 4000; // How long red stays on | |
| const unsigned long TRAFFIC_YELLOW_TIME = 2000; // How long yellow stays on | |
| const unsigned long TRAFFIC_GREEN_TIME = 4000; // How long green stays on | |
| // Fast Blink Mode Timing (in milliseconds) | |
| const unsigned long FAST_BLINK_TIME = 300; // How long each LED stays on in sequence | |
| // ========== PEEK-A-BOO CONFIGURATION ========== | |
| const int PEEK_DISTANCE_CM = 40; // How close to trigger (40cm = ~16 inches) | |
| // ========== DISTANCE GAME CONFIGURATION ========== | |
| const int FAR_DISTANCE_CM = 100; // Red turns on (far) | |
| const int MID_DISTANCE_CM = 80; // Yellow turns on (medium) | |
| const int CLOSE_DISTANCE_CM = 50; // Green turns on (close) | |
| const int VERY_CLOSE_DISTANCE_CM = 30; // Start blinking (very close!) | |
| // ==================================================== | |
| // ========================================================== | |
| // LED states for toggle mode | |
| bool redState = false; | |
| bool yellowState = false; | |
| bool greenState = false; | |
| // Button state tracking | |
| bool lastRedBtn = HIGH; | |
| bool lastYellowBtn = HIGH; | |
| bool lastGreenBtn = HIGH; | |
| // Debounce timing | |
| unsigned long lastDebounce[3] = {0, 0, 0}; | |
| const int debounceDelay = 50; | |
| // Triple press detection | |
| int greenPressCount = 0; | |
| int yellowPressCount = 0; | |
| int redPressCount = 0; | |
| unsigned long lastGreenPress = 0; | |
| unsigned long lastYellowPress = 0; | |
| unsigned long lastRedPress = 0; | |
| const unsigned long tripleClickWindow = 1000; | |
| // Mode control | |
| enum Mode { MANUAL, TRAFFIC_LIGHT, FAST_BLINK, PEEK_A_BOO, DISTANCE_GAME }; | |
| Mode currentMode = MANUAL; | |
| // Traffic light timing | |
| unsigned long lastTrafficChange = 0; | |
| int trafficState = 0; | |
| // Fast blink timing | |
| unsigned long lastBlinkChange = 0; | |
| int blinkState = 0; | |
| // ========== PEEK-A-BOO STATE ========== | |
| unsigned long lastPeekCheck = 0; | |
| bool wasClose = false; | |
| // ====================================== | |
| // ========== DISTANCE GAME STATE ========== | |
| unsigned long lastDistanceCheck = 0; | |
| unsigned long lastDistanceBlink = 0; | |
| bool distanceBlinkState = false; | |
| long lastStableDistance = 0; | |
| int stableReadings = 0; | |
| // ========================================== | |
| // Web server | |
| WebServer server(80); | |
| void setup() { | |
| Serial.begin(115200); | |
| Serial.println("Traffic Light Controller Starting..."); | |
| pinMode(RED_LED, OUTPUT); | |
| pinMode(YELLOW_LED, OUTPUT); | |
| pinMode(GREEN_LED, OUTPUT); | |
| pinMode(BTN_RED, INPUT_PULLUP); | |
| pinMode(BTN_YELLOW, INPUT_PULLUP); | |
| pinMode(BTN_GREEN, INPUT_PULLUP); | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| // Initialize sensor pins | |
| pinMode(TRIG_PIN, OUTPUT); | |
| pinMode(ECHO_PIN, INPUT); | |
| digitalWrite(TRIG_PIN, LOW); | |
| Serial.println("Waiting for sensor to stabilize..."); | |
| delay(1000); | |
| // Warm-up readings | |
| Serial.println("Warming up sensor..."); | |
| for (int i = 0; i < 10; i++) { | |
| digitalWrite(TRIG_PIN, LOW); | |
| delayMicroseconds(5); | |
| digitalWrite(TRIG_PIN, HIGH); | |
| delayMicroseconds(10); | |
| digitalWrite(TRIG_PIN, LOW); | |
| pulseIn(ECHO_PIN, HIGH, 30000); | |
| delay(150); | |
| } | |
| Serial.println("Sensor ready!"); | |
| // Configure static IP | |
| if (!WiFi.config(local_IP, gateway, subnet, primaryDNS)) { | |
| Serial.println("Static IP configuration failed!"); | |
| } | |
| // Connect to WiFi | |
| Serial.println("Connecting to WiFi..."); | |
| WiFi.begin(ssid, password); | |
| int attempts = 0; | |
| while (WiFi.status() != WL_CONNECTED && attempts < 20) { | |
| delay(500); | |
| Serial.print("."); | |
| attempts++; | |
| } | |
| if (WiFi.status() == WL_CONNECTED) { | |
| Serial.println("\n===================================="); | |
| Serial.println("WiFi Connected!"); | |
| Serial.print("Static IP Address: "); | |
| Serial.println(WiFi.localIP()); | |
| Serial.println("===================================="); | |
| Serial.print("Open this in your phone's browser: http://"); | |
| Serial.println(WiFi.localIP()); | |
| Serial.println("===================================="); | |
| // Setup OTA | |
| setupOTA(); | |
| } else { | |
| Serial.println("\nWiFi Connection Failed!"); | |
| Serial.println("Controller will work with buttons only"); | |
| } | |
| // Setup web server routes | |
| server.on("/", handleRoot); | |
| server.on("/toggle/red", handleToggleRed); | |
| server.on("/toggle/yellow", handleToggleYellow); | |
| server.on("/toggle/green", handleToggleGreen); | |
| server.on("/mode/manual", handleModeManual); | |
| server.on("/mode/traffic", handleModeTraffic); | |
| server.on("/mode/blink", handleModeBlink); | |
| server.on("/mode/peekaboo", handleModePeekaBoo); | |
| server.on("/mode/distance", handleModeDistance); | |
| server.on("/status", handleStatus); | |
| server.begin(); | |
| Serial.println("Web server started"); | |
| Serial.println("\nReady! Press buttons to toggle LEDs."); | |
| Serial.println("Press GREEN 3 times for traffic light mode"); | |
| Serial.println("Press YELLOW 3 times for fast blink mode"); | |
| Serial.println("Press RED 3 times to return to manual mode"); | |
| Serial.println("Use web interface for Peek-a-Boo and Distance Game!"); | |
| } | |
| void setupOTA() { | |
| // Set OTA hostname | |
| ArduinoOTA.setHostname(ota_hostname); | |
| // Set OTA password using SHA256 hash (secure method) | |
| ArduinoOTA.setPasswordHash(ota_password_hash); | |
| ArduinoOTA.onStart([]() { | |
| String type; | |
| if (ArduinoOTA.getCommand() == U_FLASH) { | |
| type = "sketch"; | |
| } else { | |
| type = "filesystem"; | |
| } | |
| Serial.println("Start updating " + type); | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, HIGH); | |
| }); | |
| ArduinoOTA.onEnd([]() { | |
| Serial.println("\nEnd"); | |
| for (int i = 0; i < 5; i++) { | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| delay(100); | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, HIGH); | |
| delay(100); | |
| } | |
| }); | |
| ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |
| Serial.printf("Progress: %u%%\r", (progress / (total / 100))); | |
| static unsigned long lastBlink = 0; | |
| if (millis() - lastBlink > 200) { | |
| digitalWrite(RED_LED, !digitalRead(RED_LED)); | |
| digitalWrite(YELLOW_LED, !digitalRead(YELLOW_LED)); | |
| digitalWrite(GREEN_LED, !digitalRead(GREEN_LED)); | |
| lastBlink = millis(); | |
| } | |
| }); | |
| ArduinoOTA.onError([](ota_error_t error) { | |
| Serial.printf("Error[%u]: ", error); | |
| if (error == OTA_AUTH_ERROR) { | |
| Serial.println("Auth Failed"); | |
| } else if (error == OTA_BEGIN_ERROR) { | |
| Serial.println("Begin Failed"); | |
| } else if (error == OTA_CONNECT_ERROR) { | |
| Serial.println("Connect Failed"); | |
| } else if (error == OTA_RECEIVE_ERROR) { | |
| Serial.println("Receive Failed"); | |
| } else if (error == OTA_END_ERROR) { | |
| Serial.println("End Failed"); | |
| } | |
| for (int i = 0; i < 10; i++) { | |
| digitalWrite(RED_LED, HIGH); | |
| delay(100); | |
| digitalWrite(RED_LED, LOW); | |
| delay(100); | |
| } | |
| }); | |
| ArduinoOTA.begin(); | |
| Serial.println("===================================="); | |
| Serial.println("OTA Update Enabled!"); | |
| Serial.print("Hostname: "); | |
| Serial.println(ota_hostname); | |
| Serial.println("Using SHA256 secure authentication"); | |
| Serial.println("===================================="); | |
| } | |
| void loop() { | |
| unsigned long now = millis(); | |
| ArduinoOTA.handle(); | |
| server.handleClient(); | |
| if (currentMode == MANUAL) { | |
| handleManualMode(now); | |
| } else if (currentMode == TRAFFIC_LIGHT) { | |
| handleTrafficLightMode(now); | |
| } else if (currentMode == FAST_BLINK) { | |
| handleFastBlinkMode(now); | |
| } else if (currentMode == PEEK_A_BOO) { | |
| handlePeekaBooMode(now); | |
| } else if (currentMode == DISTANCE_GAME) { | |
| handleDistanceGameMode(now); | |
| } | |
| delay(10); | |
| } | |
| // ========== PROXIMITY SENSOR FUNCTIONS ========== | |
| long getDistance() { | |
| digitalWrite(TRIG_PIN, LOW); | |
| delayMicroseconds(5); | |
| digitalWrite(TRIG_PIN, HIGH); | |
| delayMicroseconds(10); | |
| digitalWrite(TRIG_PIN, LOW); | |
| long duration = pulseIn(ECHO_PIN, HIGH, 50000); | |
| if (duration == 0 || duration > 25000) { | |
| return 0; | |
| } | |
| long distance = duration * 0.034 / 2; | |
| if (distance < 2 || distance > 400) { | |
| return 0; | |
| } | |
| return distance; | |
| } | |
| // Get stable distance reading (average of 3 readings) | |
| long getStableDistance() { | |
| long total = 0; | |
| int validReadings = 0; | |
| Serial.println("--- Taking 3 sensor readings ---"); | |
| for (int i = 0; i < 3; i++) { | |
| long dist = getDistance(); | |
| Serial.print(" Reading "); | |
| Serial.print(i + 1); | |
| Serial.print(": "); | |
| Serial.print(dist); | |
| Serial.println(" cm"); | |
| if (dist > 0 && dist < 400) { // Valid range | |
| total += dist; | |
| validReadings++; | |
| } | |
| delay(10); | |
| } | |
| if (validReadings > 0) { | |
| long average = total / validReadings; | |
| Serial.print(" Average: "); | |
| Serial.print(average); | |
| Serial.print(" cm (from "); | |
| Serial.print(validReadings); | |
| Serial.println(" valid readings)"); | |
| return average; | |
| } | |
| Serial.println(" No valid readings!"); | |
| return 0; | |
| } | |
| bool isBabyClose() { | |
| long distance = getStableDistance(); | |
| return (distance > 0 && distance < PEEK_DISTANCE_CM); | |
| } | |
| // ========== PEEK-A-BOO MODE ========== | |
| void handlePeekaBooMode(unsigned long now) { | |
| // Allow red button triple-press to exit | |
| bool currentRedBtn = digitalRead(BTN_RED); | |
| if (currentRedBtn == LOW && lastRedBtn == HIGH && (now - lastDebounce[0]) > debounceDelay) { | |
| if (now - lastRedPress < tripleClickWindow) { | |
| redPressCount++; | |
| if (redPressCount >= 3) { | |
| currentMode = MANUAL; | |
| Serial.println("=== RETURNING TO MANUAL MODE ==="); | |
| redPressCount = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| wasClose = false; | |
| } | |
| } else { | |
| redPressCount = 1; | |
| } | |
| lastRedPress = now; | |
| lastDebounce[0] = now; | |
| } | |
| lastRedBtn = currentRedBtn; | |
| // Check sensor every 200ms | |
| if (now - lastPeekCheck > 200) { | |
| bool isClose = isBabyClose(); | |
| // Get and print actual distance | |
| long distance = getStableDistance(); | |
| Serial.print("Peek-a-Boo Distance: "); | |
| Serial.print(distance); | |
| Serial.print(" cm | Trigger threshold: "); | |
| Serial.print(PEEK_DISTANCE_CM); | |
| Serial.print(" cm | Status: "); | |
| Serial.println(isClose ? "CLOSE" : "FAR"); | |
| if (isClose && !wasClose) { | |
| // Baby just got close - PEEK-A-BOO! | |
| Serial.println("*** PEEK-A-BOO! ***"); | |
| // Quick flash | |
| for (int i = 0; i < 3; i++) { | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, HIGH); | |
| delay(100); | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| delay(100); | |
| } | |
| // Leave them all on | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, HIGH); | |
| } else if (!isClose && wasClose) { | |
| // Baby moved away | |
| Serial.println("*** Baby moved away ***"); | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| } else if (isClose) { | |
| // Baby still close - sparkle effect | |
| Serial.println(" Sparkling..."); | |
| if ((now / 300) % 2 == 0) { | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, HIGH); | |
| } else { | |
| int whichLight = (now / 300) % 3; | |
| digitalWrite(RED_LED, whichLight == 0); | |
| digitalWrite(YELLOW_LED, whichLight == 1); | |
| digitalWrite(GREEN_LED, whichLight == 2); | |
| } | |
| } | |
| wasClose = isClose; | |
| lastPeekCheck = now; | |
| } | |
| } | |
| // ========== DISTANCE GAME MODE ========== | |
| void handleDistanceGameMode(unsigned long now) { | |
| // Allow red button triple-press to exit | |
| bool currentRedBtn = digitalRead(BTN_RED); | |
| if (currentRedBtn == LOW && lastRedBtn == HIGH && (now - lastDebounce[0]) > debounceDelay) { | |
| if (now - lastRedPress < tripleClickWindow) { | |
| redPressCount++; | |
| if (redPressCount >= 3) { | |
| currentMode = MANUAL; | |
| Serial.println("=== RETURNING TO MANUAL MODE ==="); | |
| redPressCount = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| } | |
| } else { | |
| redPressCount = 1; | |
| } | |
| lastRedPress = now; | |
| lastDebounce[0] = now; | |
| } | |
| lastRedBtn = currentRedBtn; | |
| // Check sensor every 200ms for stability | |
| if (now - lastDistanceCheck > 200) { | |
| long distance = getStableDistance(); | |
| // OUTPUT DISTANCE TO SERIAL | |
| if (distance > 0) { | |
| Serial.print("Distance: "); | |
| Serial.print(distance); | |
| Serial.println(" cm"); | |
| } else { | |
| Serial.println("Distance: Out of range or no reading"); | |
| } | |
| // Only update if distance changed significantly (more than 5cm) | |
| if (abs(distance - lastStableDistance) > 5) { | |
| lastStableDistance = distance; | |
| stableReadings = 0; | |
| Serial.println(">> Distance changed significantly"); | |
| } else { | |
| stableReadings++; | |
| } | |
| // Use stable distance if we have consistent readings | |
| if (stableReadings > 2) { | |
| distance = lastStableDistance; | |
| } | |
| if (distance > 0 && distance < VERY_CLOSE_DISTANCE_CM) { | |
| // VERY CLOSE! Blink all lights fast! | |
| Serial.println(">>> VERY CLOSE - BLINKING!"); | |
| if (now - lastDistanceBlink > 150) { | |
| distanceBlinkState = !distanceBlinkState; | |
| digitalWrite(RED_LED, distanceBlinkState); | |
| digitalWrite(YELLOW_LED, distanceBlinkState); | |
| digitalWrite(GREEN_LED, distanceBlinkState); | |
| lastDistanceBlink = now; | |
| } | |
| } else { | |
| // Not very close - use progressive lighting | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| if (distance > 0 && distance < FAR_DISTANCE_CM) { | |
| // Within far range - turn on RED | |
| digitalWrite(RED_LED, HIGH); | |
| Serial.println(">> RED ON"); | |
| if (distance < MID_DISTANCE_CM) { | |
| // Within medium range - also turn on YELLOW | |
| digitalWrite(YELLOW_LED, HIGH); | |
| Serial.println(">> RED + YELLOW ON"); | |
| if (distance < CLOSE_DISTANCE_CM) { | |
| // Within close range - turn on all lights! | |
| digitalWrite(GREEN_LED, HIGH); | |
| Serial.println(">> ALL LIGHTS ON"); | |
| } | |
| } | |
| } else if (distance > 0) { | |
| Serial.println(">> Too far - all lights OFF"); | |
| } | |
| } | |
| lastDistanceCheck = now; | |
| } | |
| } | |
| // ===================================================== | |
| // ========== WEB SERVER HANDLERS ========== | |
| void handleRoot() { | |
| String html = "<!DOCTYPE html><html><head>"; | |
| html += "<meta charset='UTF-8'>"; | |
| html += "<meta name='viewport' content='width=device-width, initial-scale=1'>"; | |
| html += "<style>"; | |
| html += "body { font-family: Arial; text-align: center; margin: 10px; background: #f0f0f0; }"; | |
| html += ".container { max-width: 400px; margin: 0 auto; background: white; padding: 15px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"; | |
| html += "h1 { color: #333; margin: 10px 0; font-size: 24px; }"; | |
| html += "h2 { color: #333; margin: 15px 0 10px 0; font-size: 18px; }"; | |
| html += ".status { font-size: 16px; margin: 10px 0; padding: 10px; background: #e8f4f8; border-radius: 5px; }"; | |
| html += ".btn { width: 100%; padding: 18px; margin: 5px 0; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; color: white; font-weight: bold; }"; | |
| html += ".btn-red { background: #dc3545; }"; | |
| html += ".btn-yellow { background: #ffc107; color: #333; }"; | |
| html += ".btn-green { background: #28a745; }"; | |
| html += ".btn-manual { background: #6c757d; }"; | |
| html += ".btn-traffic { background: #007bff; }"; | |
| html += ".btn-blink { background: #0fbcac; }"; | |
| html += ".btn-peekaboo { background: #f74da2; }"; | |
| html += ".btn-distance { background: #e46411; }"; | |
| html += ".btn:active { opacity: 0.7; }"; | |
| html += ".mode-section { margin-top: 15px; padding-top: 15px; border-top: 2px solid #ddd; }"; | |
| html += ".ota-info { font-size: 11px; color: #666; margin-top: 15px; padding: 8px; background: #f8f9fa; border-radius: 5px; }"; | |
| html += ".info { font-size: 11px; color: #666; margin: 5px 0; padding: 6px; background: #fff3cd; border-radius: 5px; line-height: 1.3; }"; | |
| html += "</style>"; | |
| html += "<script>"; | |
| html += "function updateStatus() {"; | |
| html += " fetch('/status').then(r => r.json()).then(data => {"; | |
| html += " document.getElementById('mode').textContent = data.mode;"; | |
| html += " document.getElementById('red').textContent = data.red;"; | |
| html += " document.getElementById('yellow').textContent = data.yellow;"; | |
| html += " document.getElementById('green').textContent = data.green;"; | |
| html += " });"; | |
| html += "}"; | |
| html += "function sendCommand(url) {"; | |
| html += " fetch(url).then(() => setTimeout(updateStatus, 100));"; | |
| html += "}"; | |
| html += "setInterval(updateStatus, 1000);"; | |
| html += "</script>"; | |
| html += "</head><body>"; | |
| html += "<div class='container'>"; | |
| html += "<h1>🚦 Traffic Light 01</h1>"; | |
| html += "<div class='status'>"; | |
| html += "<strong>Mode:</strong> <span id='mode'>MANUAL</span><br>"; | |
| html += "Red: <span id='red'>OFF</span> | "; | |
| html += "Yellow: <span id='yellow'>OFF</span> | "; | |
| html += "Green: <span id='green'>OFF</span>"; | |
| html += "</div>"; | |
| html += "<h2>Toggle LEDs</h2>"; | |
| html += "<button class='btn btn-red' onclick='sendCommand(\"/toggle/red\")'>Red</button>"; | |
| html += "<button class='btn btn-yellow' onclick='sendCommand(\"/toggle/yellow\")'>Yellow</button>"; | |
| html += "<button class='btn btn-green' onclick='sendCommand(\"/toggle/green\")'>Green</button>"; | |
| html += "<div class='mode-section'>"; | |
| html += "<h2>Modes</h2>"; | |
| html += "<button class='btn btn-manual' onclick='sendCommand(\"/mode/manual\")'>⚙ Manual</button>"; | |
| html += "<button class='btn btn-blink' onclick='sendCommand(\"/mode/blink\")'>⚡ Fast Blink</button>"; | |
| html += "<button class='btn btn-peekaboo' onclick='sendCommand(\"/mode/peekaboo\")'>👶 Peek-a-Boo</button>"; | |
| html += "<div class='info'>Lights flash when baby peeks close (" + String(PEEK_DISTANCE_CM) + "cm)!</div>"; | |
| html += "<button class='btn btn-distance' onclick='sendCommand(\"/mode/distance\")'>🏃 Distance Game</button>"; | |
| html += "<div class='info'>" + String(FAR_DISTANCE_CM) + "cm=Red | " + String(MID_DISTANCE_CM) + "cm=Red+Yellow | " + String(CLOSE_DISTANCE_CM) + "cm=All | " + String(VERY_CLOSE_DISTANCE_CM) + "cm=Blink!</div>"; | |
| html += "<button class='btn btn-traffic' onclick='sendCommand(\"/mode/traffic\")'>🚦 Traffic Light</button>"; | |
| html += "</div>"; | |
| html += "<div class='ota-info'>"; | |
| html += "OTA: " + String(ota_hostname) + " | IP: " + WiFi.localIP().toString(); | |
| html += "</div>"; | |
| html += "</div>"; | |
| html += "<script>updateStatus();</script>"; | |
| html += "</body></html>"; | |
| server.send(200, "text/html", html); | |
| } | |
| void handleToggleRed() { | |
| if (currentMode == MANUAL) { | |
| redState = !redState; | |
| digitalWrite(RED_LED, redState); | |
| Serial.println(redState ? "Web: Red ON" : "Web: Red OFF"); | |
| } | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleToggleYellow() { | |
| if (currentMode == MANUAL) { | |
| yellowState = !yellowState; | |
| digitalWrite(YELLOW_LED, yellowState); | |
| Serial.println(yellowState ? "Web: Yellow ON" : "Web: Yellow OFF"); | |
| } | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleToggleGreen() { | |
| if (currentMode == MANUAL) { | |
| greenState = !greenState; | |
| digitalWrite(GREEN_LED, greenState); | |
| Serial.println(greenState ? "Web: Green ON" : "Web: Green OFF"); | |
| } | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleModeManual() { | |
| currentMode = MANUAL; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| redState = false; | |
| yellowState = false; | |
| greenState = false; | |
| wasClose = false; | |
| Serial.println("Web: MANUAL MODE"); | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleModeTraffic() { | |
| currentMode = TRAFFIC_LIGHT; | |
| trafficState = 0; | |
| lastTrafficChange = millis(); | |
| wasClose = false; | |
| Serial.println("Web: TRAFFIC LIGHT MODE"); | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleModeBlink() { | |
| currentMode = FAST_BLINK; | |
| blinkState = 0; | |
| lastBlinkChange = millis(); | |
| wasClose = false; | |
| Serial.println("Web: FAST BLINK MODE"); | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleModePeekaBoo() { | |
| currentMode = PEEK_A_BOO; | |
| wasClose = false; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| Serial.println("Web: PEEK-A-BOO MODE"); | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleModeDistance() { | |
| currentMode = DISTANCE_GAME; | |
| lastStableDistance = 0; | |
| stableReadings = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| Serial.println("Web: DISTANCE GAME MODE"); | |
| server.send(200, "text/plain", "OK"); | |
| } | |
| void handleStatus() { | |
| String json = "{"; | |
| json += "\"mode\":\""; | |
| if (currentMode == MANUAL) json += "MANUAL"; | |
| else if (currentMode == TRAFFIC_LIGHT) json += "TRAFFIC LIGHT"; | |
| else if (currentMode == FAST_BLINK) json += "FAST BLINK"; | |
| else if (currentMode == PEEK_A_BOO) json += "PEEK-A-BOO"; | |
| else json += "DISTANCE GAME"; | |
| json += "\","; | |
| json += "\"red\":\"" + String(digitalRead(RED_LED) ? "ON" : "OFF") + "\","; | |
| json += "\"yellow\":\"" + String(digitalRead(YELLOW_LED) ? "ON" : "OFF") + "\","; | |
| json += "\"green\":\"" + String(digitalRead(GREEN_LED) ? "ON" : "OFF") + "\""; | |
| json += "}"; | |
| server.send(200, "application/json", json); | |
| } | |
| // ========== BUTTON HANDLING ========== | |
| void handleManualMode(unsigned long now) { | |
| bool currentRedBtn = digitalRead(BTN_RED); | |
| if (currentRedBtn == LOW && lastRedBtn == HIGH && (now - lastDebounce[0]) > debounceDelay) { | |
| if (now - lastRedPress < tripleClickWindow) { | |
| redPressCount++; | |
| if (redPressCount >= 3) { | |
| Serial.println("=== ALREADY IN MANUAL MODE ==="); | |
| redPressCount = 0; | |
| } | |
| } else { | |
| redPressCount = 1; | |
| } | |
| lastRedPress = now; | |
| redState = !redState; | |
| digitalWrite(RED_LED, redState); | |
| Serial.println(redState ? "Red ON" : "Red OFF"); | |
| lastDebounce[0] = now; | |
| } | |
| lastRedBtn = currentRedBtn; | |
| bool currentYellowBtn = digitalRead(BTN_YELLOW); | |
| if (currentYellowBtn == LOW && lastYellowBtn == HIGH && (now - lastDebounce[1]) > debounceDelay) { | |
| if (now - lastYellowPress < tripleClickWindow) { | |
| yellowPressCount++; | |
| if (yellowPressCount >= 3) { | |
| currentMode = FAST_BLINK; | |
| Serial.println("=== FAST BLINK MODE ACTIVATED ==="); | |
| yellowPressCount = 0; | |
| lastBlinkChange = now; | |
| blinkState = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| lastDebounce[1] = now; | |
| lastYellowBtn = currentYellowBtn; | |
| return; | |
| } | |
| } else { | |
| yellowPressCount = 1; | |
| } | |
| lastYellowPress = now; | |
| yellowState = !yellowState; | |
| digitalWrite(YELLOW_LED, yellowState); | |
| Serial.println(yellowState ? "Yellow ON" : "Yellow OFF"); | |
| lastDebounce[1] = now; | |
| } | |
| lastYellowBtn = currentYellowBtn; | |
| bool currentGreenBtn = digitalRead(BTN_GREEN); | |
| if (currentGreenBtn == LOW && lastGreenBtn == HIGH && (now - lastDebounce[2]) > debounceDelay) { | |
| if (now - lastGreenPress < tripleClickWindow) { | |
| greenPressCount++; | |
| if (greenPressCount >= 3) { | |
| currentMode = TRAFFIC_LIGHT; | |
| Serial.println("=== TRAFFIC LIGHT MODE ACTIVATED ==="); | |
| greenPressCount = 0; | |
| trafficState = 0; | |
| lastTrafficChange = now; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| lastDebounce[2] = now; | |
| lastGreenBtn = currentGreenBtn; | |
| return; | |
| } | |
| } else { | |
| greenPressCount = 1; | |
| } | |
| lastGreenPress = now; | |
| greenState = !greenState; | |
| digitalWrite(GREEN_LED, greenState); | |
| Serial.println(greenState ? "Green ON" : "Green OFF"); | |
| lastDebounce[2] = now; | |
| } | |
| lastGreenBtn = currentGreenBtn; | |
| } | |
| void handleTrafficLightMode(unsigned long now) { | |
| bool currentRedBtn = digitalRead(BTN_RED); | |
| if (currentRedBtn == LOW && lastRedBtn == HIGH && (now - lastDebounce[0]) > debounceDelay) { | |
| if (now - lastRedPress < tripleClickWindow) { | |
| redPressCount++; | |
| if (redPressCount >= 3) { | |
| currentMode = MANUAL; | |
| Serial.println("=== RETURNING TO MANUAL MODE ==="); | |
| redPressCount = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| redState = false; | |
| yellowState = false; | |
| greenState = false; | |
| } | |
| } else { | |
| redPressCount = 1; | |
| } | |
| lastRedPress = now; | |
| lastDebounce[0] = now; | |
| } | |
| lastRedBtn = currentRedBtn; | |
| switch (trafficState) { | |
| case 0: | |
| if (now - lastTrafficChange > TRAFFIC_RED_TIME) { | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, HIGH); | |
| digitalWrite(GREEN_LED, LOW); | |
| trafficState = 1; | |
| lastTrafficChange = now; | |
| Serial.println("Traffic: YELLOW"); | |
| } else if (now - lastTrafficChange == 0 || digitalRead(RED_LED) == LOW) { | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| } | |
| break; | |
| case 1: | |
| if (now - lastTrafficChange > TRAFFIC_YELLOW_TIME) { | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, HIGH); | |
| trafficState = 2; | |
| lastTrafficChange = now; | |
| Serial.println("Traffic: GREEN"); | |
| } | |
| break; | |
| case 2: | |
| if (now - lastTrafficChange > TRAFFIC_GREEN_TIME) { | |
| digitalWrite(RED_LED, HIGH); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| trafficState = 0; | |
| lastTrafficChange = now; | |
| Serial.println("Traffic: RED"); | |
| } | |
| break; | |
| } | |
| } | |
| void handleFastBlinkMode(unsigned long now) { | |
| bool currentRedBtn = digitalRead(BTN_RED); | |
| if (currentRedBtn == LOW && lastRedBtn == HIGH && (now - lastDebounce[0]) > debounceDelay) { | |
| if (now - lastRedPress < tripleClickWindow) { | |
| redPressCount++; | |
| if (redPressCount >= 3) { | |
| currentMode = MANUAL; | |
| Serial.println("=== RETURNING TO MANUAL MODE ==="); | |
| redPressCount = 0; | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| redState = false; | |
| yellowState = false; | |
| greenState = false; | |
| } | |
| } else { | |
| redPressCount = 1; | |
| } | |
| lastRedPress = now; | |
| lastDebounce[0] = now; | |
| } | |
| lastRedBtn = currentRedBtn; | |
| if (now - lastBlinkChange > FAST_BLINK_TIME) { | |
| digitalWrite(RED_LED, LOW); | |
| digitalWrite(YELLOW_LED, LOW); | |
| digitalWrite(GREEN_LED, LOW); | |
| switch (blinkState) { | |
| case 0: | |
| digitalWrite(RED_LED, HIGH); | |
| blinkState = 1; | |
| Serial.println("Blink: RED"); | |
| break; | |
| case 1: | |
| digitalWrite(YELLOW_LED, HIGH); | |
| blinkState = 2; | |
| Serial.println("Blink: YELLOW"); | |
| break; | |
| case 2: | |
| digitalWrite(GREEN_LED, HIGH); | |
| blinkState = 0; | |
| Serial.println("Blink: GREEN"); | |
| break; | |
| } | |
| lastBlinkChange = now; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment