diff --git a/src/main.py b/src/main.py index 01e81fb..1064bc1 100644 --- a/src/main.py +++ b/src/main.py @@ -1,63 +1,23 @@ import asyncio -import aioespnow from settings import Settings -from patterns import Patterns from web import web import gc -import utime import machine -import ntptime -import time -import wifi -import json -import network - - async def main(): - settings = Settings() - patterns = Patterns(10, settings["num_leds"], selected=settings["pattern"]) - network.WLAN(network.WLAN.IF_STA).active(True) - - e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support - e.active(True) - w = web(settings, patterns) - print(settings) - # start the server in a bacakground task print("Starting") + w = web(settings) server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80)) - async def send(): - peer = bytes.fromhex("e4b323c5411d") - perr = b'\xbb\xbb\xbb\xbb\xbb\xbb' - e.add_peer(peer) - print(peer) - msg = {"delay": 1000} - while True: - if not await e.asend(peer, json.dumps(msg).encode()): - print("Heartbeat: peer not responding:", peer) - else: - print("Heartbeat: ping", peer) - await asyncio.sleep(1) - #asyncio.create_task(send()) - - wdt = machine.WDT(timeout=10000) wdt.feed() while True: - #print(time.localtime()) - - # gc.collect() + gc.collect() for i in range(60): wdt.feed() await asyncio.sleep_ms(500) - - # cleanup before ending the application await server - - - asyncio.run(main()) diff --git a/src/settings.py b/src/settings.py index cf381c9..c3f09b7 100644 --- a/src/settings.py +++ b/src/settings.py @@ -11,14 +11,7 @@ class Settings(dict): self.load() # Load settings from file during initialization def set_defaults(self): - self["num_leds"] = 50 - self["pattern"] = "on" - self["color1"] = "#00ff00" - self["color2"] = "#ff0000" - self["delay"] = 100 - self["brightness"] = 10 - self["name"] = "led-controller" - self["ap_password"] = "" + self = {} def save(self): try: @@ -39,57 +32,3 @@ class Settings(dict): print(f"Error loading settings") self.set_defaults() self.save() - -# Example usage -def main(): - settings = Settings() - print(f"Number of LEDs: {settings['num_leds']}") - settings['num_leds'] = 100 - print(f"Updated number of LEDs: {settings['num_leds']}") - settings.save() - - # Create a new Settings object to test loading - new_settings = Settings() - print(f"Loaded number of LEDs: {new_settings['num_leds']}") - print(settings) - -def set_settings(raw_json, settings, patterns): - try: - data = json.loads(raw_json) - print(data) - for key, value in data.items(): - print(key, value) - if key == "color1": - patterns.set_color1(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB - elif key == "color2": - patterns.set_color2(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB - elif key == "num_leds": - patterns.update_num_leds(4, value) - elif key == "pattern": - if not patterns.select(value): - return "Pattern doesn't exist", 400 - elif key == "delay": - delay = int(data["delay"]) - patterns.set_delay(delay) - elif key == "brightness": - brightness = int(data["brightness"]) - patterns.set_brightness(brightness) - elif key == "name": - settings[key] = value - settings.save() - machine.reset() - elif key == "sync": - patterns.sync() - return "OK", 200 - else: - return "Invalid key", 400 - settings[key] = value - settings.save() - return "OK", 200 - - except (KeyError, ValueError): - return "Bad request", 400 - -# Run the example -if __name__ == "__main__": - main() diff --git a/src/static/light-component.js b/src/static/light-component.js new file mode 100644 index 0000000..1f32642 --- /dev/null +++ b/src/static/light-component.js @@ -0,0 +1,143 @@ +import { getWebSocket } from "./websocket.js"; + +export class LightComponent extends HTMLElement { + constructor() { + super(); + + // Create a shadow DOM for encapsulation + const shadow = this.attachShadow({ mode: "open" }); + + // Create the content for the component + const style = document.createElement("style"); + style.textContent = ` + :host { + display: block; + width: 100px; + height: 100px; + background-color: #4caf50; + color: white; + text-align: center; + line-height: 100px; + cursor: grab; + position: absolute; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } + + :host:active { + cursor: grabbing; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); + } + + .color-picker { + position: absolute; + top: 0; + right: 0; + width: 50px; + height: 50px; + cursor: pointer; + } + `; + + // Create the main content (draggable area) + const content = document.createElement("div"); + content.textContent = this.textContent || "Light Me Up!"; + content.style.position = "absolute"; + content.style.top = "0"; + content.style.left = "0"; + content.style.width = "100%"; + content.style.height = "100%"; + content.style.display = "flex"; + content.style.justifyContent = "center"; + content.style.alignItems = "center"; + + // Create the color picker + const colorPicker = document.createElement("input"); + colorPicker.type = "color"; + colorPicker.classList.add("color-picker"); + colorPicker.value = "#4caf50"; // Default color + colorPicker.addEventListener("input", () => { + this.style.backgroundColor = colorPicker.value; + this.dispatchEvent( + new CustomEvent("color-change", { + detail: { lightId: this.lightId, color: colorPicker.value }, + }), + ); + }); + + // Append the style, content, and color picker to the shadow DOM + shadow.appendChild(style); + shadow.appendChild(content); + shadow.appendChild(colorPicker); + + // Add event listeners for drag-and-drop + content.addEventListener("mousedown", this.handleMouseDown.bind(this)); + document.addEventListener("mousemove", this.handleMouseMove.bind(this)); + document.addEventListener("mouseup", this.handleMouseUp.bind(this)); + } + + // Track the initial mouse position and component position + handleMouseDown(event) { + event.preventDefault(); + + // Get the initial mouse position relative to the component + this.initialMouseX = event.clientX; + this.initialMouseY = event.clientY; + + // Get the initial position of the component + const rect = this.getBoundingClientRect(); + this.initialComponentX = rect.left; + this.initialComponentY = rect.top; + + // Add a class to indicate dragging + this.classList.add("dragging"); + } + + // Update the component's position as the mouse moves + handleMouseMove(event) { + if (!this.classList.contains("dragging")) return; + + // Calculate the new position of the component + const newX = this.initialComponentX + (event.clientX - this.initialMouseX); + const newY = this.initialComponentY + (event.clientY - this.initialMouseY); + + // Update the component's position + this.style.left = `${newX}px`; + this.style.top = `${newY}px`; + } + + // Stop dragging when the mouse is released + handleMouseUp() { + // Check if the component is being dragged + if (!this.classList.contains("dragging")) { + return; // Do nothing if not dragging + } + + // Remove the dragging class + this.classList.remove("dragging"); + + // Get the current position of the component + const rect = this.getBoundingClientRect(); + const newX = rect.left; + const newY = rect.top; + + // Dispatch an event to notify the parent about the updated position + this.dispatchEvent( + new CustomEvent("position-change", { + detail: { lightId: this.lightId, x: newX, y: newY }, + }), + ); + } + + // Add a property to hold the lightId + set lightId(id) { + this._lightId = id; + } + + get lightId() { + return this._lightId; + } +} + +// Define the custom element +customElements.define("light-component", LightComponent); diff --git a/src/static/main.js b/src/static/main.js index da7c0a9..73a5863 100644 --- a/src/static/main.js +++ b/src/static/main.js @@ -1,214 +1,105 @@ -let delayTimeout; -let brightnessTimeout; -let colorTimeout; -let color2Timeout; -let ws; // Variable to hold the WebSocket connection -let connectionStatusElement; // Variable to hold the connection status element +// Import the LightComponent from light-component.js +import { LightComponent } from "./light-component.js"; +import { getWebSocket } from "./websocket.js"; -// Function to update the connection status indicator -function updateConnectionStatus(status) { - if (!connectionStatusElement) { - connectionStatusElement = document.getElementById("connection-status"); - } - if (connectionStatusElement) { - connectionStatusElement.className = ""; // Clear existing classes - connectionStatusElement.classList.add(status); - // Optionally, you could also update text content based on status - // connectionStatusElement.textContent = status.charAt(0).toUpperCase() + status.slice(1); - } -} +// Wait for the DOM to be fully loaded +document.addEventListener("DOMContentLoaded", async () => { + // Select the container where the light-components will be added + const appContainer = document.getElementById("app"); -// Function to establish WebSocket connection -function connectWebSocket() { - // Determine the WebSocket URL based on the current location - const wsUrl = `ws://${window.location.host}/ws`; - ws = new WebSocket(wsUrl); - - updateConnectionStatus("connecting"); // Indicate connecting state - - ws.onopen = function (event) { - console.log("WebSocket connection opened:", event); - updateConnectionStatus("open"); // Indicate open state - // Optionally, you could send an initial message here - }; - - ws.onmessage = function (event) { - console.log("WebSocket message received:", event.data); - }; - - ws.onerror = function (event) { - console.error("WebSocket error:", event); - updateConnectionStatus("closed"); // Indicate error state (treat as closed) - }; - - ws.onclose = function (event) { - if (event.wasClean) { - console.log( - `WebSocket connection closed cleanly, code=${event.code}, reason=${event.reason}`, - ); - updateConnectionStatus("closed"); // Indicate closed state - } else { - console.error("WebSocket connection died"); - updateConnectionStatus("closed"); // Indicate closed state - } - // Attempt to reconnect after a delay - setTimeout(connectWebSocket, 1000); - }; -} - -// Function to send data over WebSocket -function sendWebSocketData(data) { - if (ws && ws.readyState === WebSocket.OPEN) { - console.log("Sending data over WebSocket:", data); - ws.send(JSON.stringify(data)); - } else { - console.error("WebSocket is not connected. Cannot send data:", data); - // You might want to queue messages or handle this in a different way - } -} - -// Keep the post and get functions for now, they might still be useful -async function post(path, data) { - console.log(`POST to ${path}`, data); + // Fetch the JSON data from the /light endpoint try { - const response = await fetch(path, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); + const response = await fetch("/light"); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } - } catch (error) { - console.error("Error during POST request:", error); - } -} + const lightData = await response.json(); -async function get(path) { - try { - const response = await fetch(path); - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); + // Map to store backend IDs and their corresponding components + const componentMap = new Map(); + + // Function to create and configure a light component + function createLightComponent(data, key) { + const lightComponent = document.createElement("light-component"); + lightComponent.style.left = `${data.x}px`; // Set the x position + lightComponent.style.top = `${data.y}px`; // Set the y position + lightComponent.style.backgroundColor = data.settings?.color || "#4caf50"; // Set the background color + lightComponent.textContent = data.name || "Light Me Up!"; // Set the text content + + // Set the lightId property + lightComponent.lightId = key; // Use the backend ID as the lightId + + // Store the component in the map + componentMap.set(key, lightComponent); + + // Append the light component to the container + appContainer.appendChild(lightComponent); + + // Handle position change + lightComponent.addEventListener("position-change", (event) => { + const { lightId, x, y } = event.detail; + updatePositionOnServer(lightId, x, y); + }); + + // Handle color change + lightComponent.addEventListener("color-change", (event) => { + const { lightId, color } = event.detail; + sendColorToServer(lightId, color); + }); + + // Example: Add a click event listener to the light-component + lightComponent.addEventListener("click", () => { + console.log(`Light component clicked! ID: ${lightComponent.lightId}`); + }); + } + + // Iterate over the JSON data and create light components + for (const key in lightData) { + if (lightData.hasOwnProperty(key)) { + const light = lightData[key]; + createLightComponent(light, key); // Pass the backend ID + } + } + + // Function to send the updated position to the server via a PATCH request + async function updatePositionOnServer(componentId, x, y) { + try { + const response = await fetch(`/light/${componentId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ x, y }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + console.log( + `Updated position for component ${componentId}: x=${x}, y=${y}`, + ); + } catch (error) { + console.error("Error updating position on server:", error); + } + } + + // Function to send the selected color to the server via WebSocket + function sendColorToServer(componentId, color) { + const websocket = getWebSocket(); + const message = JSON.stringify({ + componentId, + color: color, + }); + + if (websocket.readyState === WebSocket.OPEN) { + websocket.send(message); + console.log("Sent color to server:", message); + } else { + console.warn("WebSocket is not open. Unable to send color."); + } } - return await response.json(); } catch (error) { - console.error("Error during GET request:", error); + console.error("Error fetching light data:", error); } -} - -async function updateColor(event) { - event.preventDefault(); - clearTimeout(colorTimeout); - colorTimeout = setTimeout(function () { - const color = document.getElementById("color").value; - sendWebSocketData({ color1: color }); - }, 500); -} - -async function updateColor2(event) { - event.preventDefault(); - clearTimeout(color2Timeout); - color2Timeout = setTimeout(function () { - const color = document.getElementById("color2").value; - sendWebSocketData({ color2: color }); - }, 500); -} - -async function updatePattern(pattern) { - sendWebSocketData({ pattern: pattern }); -} - -async function updateBrightness(event) { - event.preventDefault(); - clearTimeout(brightnessTimeout); - brightnessTimeout = setTimeout(function () { - const brightness = document.getElementById("brightness").value; - sendWebSocketData({ brightness: brightness }); - }, 500); -} - -async function updateDelay(event) { - event.preventDefault(); - clearTimeout(delayTimeout); - delayTimeout = setTimeout(function () { - const delay = document.getElementById("delay").value; - sendWebSocketData({ delay: delay }); - }, 500); -} - -async function updateNumLeds(event) { - event.preventDefault(); - const numLeds = document.getElementById("num_leds").value; - sendWebSocketData({ num_leds: parseInt(numLeds) }); -} - -async function updateName(event) { - event.preventDefault(); - const name = document.getElementById("name").value; - sendWebSocketData({ name: name }); -} - -function createPatternButtons(patterns) { - const container = document.getElementById("pattern_buttons"); - container.innerHTML = ""; // Clear previous buttons - - patterns.forEach((pattern) => { - const button = document.createElement("button"); - button.type = "button"; - button.textContent = pattern; - button.value = pattern; - button.addEventListener("click", async function (event) { - event.preventDefault(); - await updatePattern(pattern); - }); - container.appendChild(button); - }); -} - -document.addEventListener("DOMContentLoaded", async function () { - // Get the connection status element once the DOM is ready - connectionStatusElement = document.getElementById("connection-status"); - - // Establish WebSocket connection on page load - connectWebSocket(); - - document.getElementById("color").addEventListener("input", updateColor); - document.getElementById("color2").addEventListener("input", updateColor2); - document.getElementById("delay").addEventListener("input", updateDelay); - document - .getElementById("brightness") - .addEventListener("input", updateBrightness); - document - .getElementById("num_leds_form") - .addEventListener("submit", updateNumLeds); - document.getElementById("name_form").addEventListener("submit", updateName); - document.getElementById("delay").addEventListener("touchend", updateDelay); - document - .getElementById("brightness") - .addEventListener("touchend", updateBrightness); - - document.querySelectorAll(".pattern_button").forEach((button) => { - console.log(button.value); - button.addEventListener("click", async (event) => { - event.preventDefault(); - await updatePattern(button.value); - }); - }); }); - -// Function to toggle the display of the settings menu -function selectSettings() { - const settingsMenu = document.getElementById("settings_menu"); - controls = document.getElementById("controls"); - settingsMenu.style.display = "block"; - controls.style.display = "none"; -} - -function selectControls() { - const settingsMenu = document.getElementById("settings_menu"); - controls = document.getElementById("controls"); - settingsMenu.style.display = "none"; - controls.style.display = "block"; -} diff --git a/src/static/styles.css b/src/static/styles.css new file mode 100644 index 0000000..e238081 --- /dev/null +++ b/src/static/styles.css @@ -0,0 +1,20 @@ +/* Default styles for the light component */ +light-component { + display: block; + width: 100px; + height: 100px; + background-color: #4caf50; + color: white; + text-align: center; + line-height: 100px; + cursor: grab; + position: absolute; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +/* Styles when the component is being dragged */ +light-component:active { + cursor: grabbing; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); +} diff --git a/src/static/websocket.js b/src/static/websocket.js new file mode 100644 index 0000000..10c8333 --- /dev/null +++ b/src/static/websocket.js @@ -0,0 +1,26 @@ +// websocket.js +let websocket = null; + +export function getWebSocket() { + if (!websocket) { + // Replace 'ws://your-server-url' with your WebSocket server URL + websocket = new WebSocket(`ws://${window.location.host}/ws`); + + // Handle WebSocket connection open + websocket.onopen = () => { + console.log("WebSocket connection established"); + }; + + // Handle WebSocket connection close + websocket.onclose = () => { + console.log("WebSocket connection closed"); + }; + + // Handle WebSocket errors + websocket.onerror = (error) => { + console.error("WebSocket error:", error); + }; + } + + return websocket; +} diff --git a/src/templates/index.html b/src/templates/index.html index 240bd3d..3e4bbc4 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -1,97 +1,16 @@ -{% args settings, patterns, mac %}
-