const express = require("express"); const http = require("http"); const app = express(); const server = http.createServer(app); // Store LED states let ledStates = { led1: false, led2: false, led3: false, led4: false, chase: false }; // Store SSE clients (ESP8266 devices) let sseClients = []; app.use(express.json()); app.use(express.static(".")); // SSE endpoint for ESP8266 app.get("/events", (req, res) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.setHeader("Access-Control-Allow-Origin", "*"); console.log("New SSE client connected"); // Send current state immediately Object.keys(ledStates).forEach(led => { const message = `data: ${led}:${ledStates[led] ? 1 : 0}\n\n`; res.write(message); console.log(`Sent initial state: ${led}:${ledStates[led] ? 1 : 0}`); }); // Add client to list sseClients.push(res); console.log(`Total ESP8266 clients: ${sseClients.length}`); // Send keepalive every 30 seconds const keepaliveInterval = setInterval(() => { try { res.write(`:keepalive\n\n`); } catch (err) { clearInterval(keepaliveInterval); } }, 30000); // Remove client on disconnect req.on("close", () => { clearInterval(keepaliveInterval); sseClients = sseClients.filter(client => client !== res); console.log(`ESP8266 disconnected. Total clients: ${sseClients.length}`); }); }); // Get current LED states (for frontend) app.get("/api/leds", (req, res) => { res.json(ledStates); }); // Get connection status app.get("/api/status", (req, res) => { res.json({ connected: sseClients.length > 0, clientCount: sseClients.length }); }); // Toggle individual LED (for frontend) app.post("/api/led/:ledName", (req, res) => { const { ledName } = req.params; const { state } = req.body; if (!ledStates.hasOwnProperty(ledName)) { return res.status(400).json({ error: "Invalid LED name" }); } ledStates[ledName] = state; // If turning on chase, turn off individual LEDs 1-3 if (ledName === "chase" && state) { ledStates.led1 = false; ledStates.led2 = false; ledStates.led3 = false; } // Broadcast to all ESP8266 devices via SSE const message = `data: ${ledName}:${state ? 1 : 0}\n\n`; console.log(`Broadcasting: ${ledName}:${state ? 1 : 0} to ${sseClients.length} clients`); sseClients.forEach((client, index) => { try { client.write(message); } catch (err) { console.error(`Error sending to client ${index}:`, err.message); } }); // If chase was turned on, also send LED1-3 off commands if (ledName === "chase" && state) { ["led1", "led2", "led3"].forEach(led => { const msg = `data: ${led}:0\n\n`; console.log(`Broadcasting: ${led}:0`); sseClients.forEach(client => { try { client.write(msg); } catch (err) { console.error("Error sending to client:", err.message); } }); }); } console.log(`${ledName} set to ${state ? "ON" : "OFF"}`); res.json({ success: true, ledStates }); }); // Turn all LEDs off app.post("/api/all-off", (req, res) => { Object.keys(ledStates).forEach(led => { ledStates[led] = false; const message = `data: ${led}:0\n\n`; console.log(`Broadcasting: ${led}:0`); sseClients.forEach(client => { try { client.write(message); } catch (err) { console.error("Error sending to client:", err.message); } }); }); console.log("All LEDs turned OFF"); res.json({ success: true, ledStates }); }); const PORT = process.env.PORT || 3000; const HOST = "0.0.0.0"; server.listen(PORT, HOST, () => { console.log(`\n=================================`); console.log(`Server running at http://${HOST}:${PORT}`); console.log(`SSE endpoint: http://${HOST}:${PORT}/events`); console.log(`API endpoint: http://${HOST}:${PORT}/api/leds`); console.log(`=================================\n`); });