Add basic draggable item that saves to the server
This commit is contained in:
parent
5c35e68ab2
commit
deca1b6c37
44
src/main.py
44
src/main.py
|
@ -1,63 +1,23 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import aioespnow
|
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
from patterns import Patterns
|
|
||||||
from web import web
|
from web import web
|
||||||
import gc
|
import gc
|
||||||
import utime
|
|
||||||
import machine
|
import machine
|
||||||
import ntptime
|
|
||||||
import time
|
|
||||||
import wifi
|
|
||||||
import json
|
|
||||||
import network
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|
||||||
settings = Settings()
|
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")
|
print("Starting")
|
||||||
|
w = web(settings)
|
||||||
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
|
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 = machine.WDT(timeout=10000)
|
||||||
wdt.feed()
|
wdt.feed()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
#print(time.localtime())
|
gc.collect()
|
||||||
|
|
||||||
# gc.collect()
|
|
||||||
for i in range(60):
|
for i in range(60):
|
||||||
wdt.feed()
|
wdt.feed()
|
||||||
await asyncio.sleep_ms(500)
|
await asyncio.sleep_ms(500)
|
||||||
|
|
||||||
|
|
||||||
# cleanup before ending the application
|
# cleanup before ending the application
|
||||||
await server
|
await server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|
|
@ -11,14 +11,7 @@ class Settings(dict):
|
||||||
self.load() # Load settings from file during initialization
|
self.load() # Load settings from file during initialization
|
||||||
|
|
||||||
def set_defaults(self):
|
def set_defaults(self):
|
||||||
self["num_leds"] = 50
|
self = {}
|
||||||
self["pattern"] = "on"
|
|
||||||
self["color1"] = "#00ff00"
|
|
||||||
self["color2"] = "#ff0000"
|
|
||||||
self["delay"] = 100
|
|
||||||
self["brightness"] = 10
|
|
||||||
self["name"] = "led-controller"
|
|
||||||
self["ap_password"] = ""
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
try:
|
try:
|
||||||
|
@ -39,57 +32,3 @@ class Settings(dict):
|
||||||
print(f"Error loading settings")
|
print(f"Error loading settings")
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.save()
|
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()
|
|
||||||
|
|
|
@ -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);
|
|
@ -1,214 +1,105 @@
|
||||||
let delayTimeout;
|
// Import the LightComponent from light-component.js
|
||||||
let brightnessTimeout;
|
import { LightComponent } from "./light-component.js";
|
||||||
let colorTimeout;
|
import { getWebSocket } from "./websocket.js";
|
||||||
let color2Timeout;
|
|
||||||
let ws; // Variable to hold the WebSocket connection
|
|
||||||
let connectionStatusElement; // Variable to hold the connection status element
|
|
||||||
|
|
||||||
// Function to update the connection status indicator
|
// Wait for the DOM to be fully loaded
|
||||||
function updateConnectionStatus(status) {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
if (!connectionStatusElement) {
|
// Select the container where the light-components will be added
|
||||||
connectionStatusElement = document.getElementById("connection-status");
|
const appContainer = document.getElementById("app");
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to establish WebSocket connection
|
// Fetch the JSON data from the /light endpoint
|
||||||
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);
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(path, {
|
const response = await fetch("/light");
|
||||||
method: "POST",
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const lightData = await response.json();
|
||||||
|
|
||||||
|
// 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: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify({ x, y }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Updated position for component ${componentId}: x=${x}, y=${y}`,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error during POST request:", error);
|
console.error("Error updating position on server:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(path) {
|
// Function to send the selected color to the server via WebSocket
|
||||||
try {
|
function sendColorToServer(componentId, color) {
|
||||||
const response = await fetch(path);
|
const websocket = getWebSocket();
|
||||||
if (!response.ok) {
|
const message = JSON.stringify({
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
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) {
|
} 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";
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -1,97 +1,16 @@
|
||||||
{% args settings, patterns, mac %}
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>LED Control</title>
|
<title>Light Component</title>
|
||||||
<script src="static/main.js"></script>
|
<!-- Link to the external CSS file -->
|
||||||
<link rel="stylesheet" href="static/main.css" />
|
<link rel="stylesheet" href="static/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Control LEDs</h1>
|
<!-- The light-component will be added dynamically by main.js -->
|
||||||
<button onclick="selectControls()">Controls</button>
|
<div id="app"></div>
|
||||||
<button onclick="selectSettings()">Settings</button>
|
<!-- Import the JavaScript files -->
|
||||||
|
<script type="module" src="static/main.js"></script>
|
||||||
<!-- Main LED Controls -->
|
|
||||||
<div id="controls">
|
|
||||||
<div id="pattern_buttons">
|
|
||||||
{% for p in patterns %}
|
|
||||||
<button class="pattern_button" value="{{p}}">{{p}}</button>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Pattern buttons will be inserted here -->
|
|
||||||
</div>
|
|
||||||
<form id="delay_form" method="post" action="/delay">
|
|
||||||
<label for="delay">Delay:</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="delay"
|
|
||||||
name="delay"
|
|
||||||
min="1"
|
|
||||||
max="1000"
|
|
||||||
value="{{settings['delay']}}"
|
|
||||||
step="10"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<form id="brightness_form" method="post" action="/brightness">
|
|
||||||
<label for="brightness">Brightness:</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
id="brightness"
|
|
||||||
name="brightness"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
value="{{settings['brightness']}}"
|
|
||||||
step="1"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<form id="color_form" method="post" action="/color">
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
id="color"
|
|
||||||
name="color"
|
|
||||||
value="{{settings['color1']}}"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<form id="color2_form" method="post" action="/color2">
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
id="color2"
|
|
||||||
name="color2"
|
|
||||||
value="{{settings['color2']}}"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
|
|
||||||
|
|
||||||
<div id="settings_menu" style="display: none">
|
|
||||||
<h2>Settings</h2>
|
|
||||||
|
|
||||||
<form id="name_form" method="post" action="/name">
|
|
||||||
<label for="name">Name:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
name="num_leds"
|
|
||||||
value="{{settings['name']}}"
|
|
||||||
/>
|
|
||||||
<input type="submit" value="Update Name" />
|
|
||||||
</form>
|
|
||||||
<!-- Separate form for submitting num_leds -->
|
|
||||||
<form id="num_leds_form" method="post" action="/num_leds">
|
|
||||||
<label for="num_leds">Number of LEDs:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="num_leds"
|
|
||||||
name="num_leds"
|
|
||||||
value="{{settings['num_leds']}}"
|
|
||||||
/>
|
|
||||||
<input type="submit" value="Update Number of LEDs" />
|
|
||||||
</form>
|
|
||||||
<p>Mac address: {{mac}}</p>
|
|
||||||
</div>
|
|
||||||
<div id="connection-status"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
115
src/web.py
115
src/web.py
|
@ -1,26 +1,15 @@
|
||||||
from microdot import Microdot, send_file, Response
|
from microdot import Microdot, send_file, Response
|
||||||
from microdot.utemplate import Template
|
from microdot.utemplate import Template
|
||||||
from microdot.websocket import with_websocket
|
from microdot.websocket import with_websocket
|
||||||
import machine
|
|
||||||
from settings import set_settings
|
|
||||||
import wifi
|
|
||||||
import json
|
import json
|
||||||
import aioespnow
|
|
||||||
|
|
||||||
def web(settings, patterns):
|
def web(settings):
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
|
|
||||||
e.active(True)
|
|
||||||
peer = bytes.fromhex("e4b323c5411d")
|
|
||||||
peer = b'\xbb\xbb\xbb\xbb\xbb\xbb'
|
|
||||||
e.add_peer(peer)
|
|
||||||
print(peer)
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def index_hnadler(request):
|
async def index_handler(request):
|
||||||
mac = wifi.get_mac().hex()
|
return Template('/index.html').render(settings=settings)
|
||||||
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
|
|
||||||
|
|
||||||
@app.route("/static/<path:path>")
|
@app.route("/static/<path:path>")
|
||||||
def static_handler(request, path):
|
def static_handler(request, path):
|
||||||
|
@ -29,34 +18,96 @@ def web(settings, patterns):
|
||||||
return 'Not found', 404
|
return 'Not found', 404
|
||||||
return send_file('static/' + path)
|
return send_file('static/' + path)
|
||||||
|
|
||||||
@app.post("/settings")
|
|
||||||
def settings_handler(request):
|
|
||||||
# Keep the POST handler for compatibility or alternative usage if needed
|
|
||||||
# For WebSocket updates, the /ws handler is now primary
|
|
||||||
return set_settings(request.body.decode('utf-8'), settings, patterns)
|
|
||||||
|
|
||||||
@app.route("/ws")
|
@app.route("/ws")
|
||||||
@with_websocket
|
@with_websocket
|
||||||
async def ws(request, ws):
|
async def ws(request, ws):
|
||||||
|
# Register the client's WebSocket connection
|
||||||
|
print("WebSocket connection established")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = await ws.receive()
|
data = await ws.receive()
|
||||||
if data:
|
if data:
|
||||||
# Process the received data
|
try:
|
||||||
await send(data)
|
# Parse the JSON message from the client
|
||||||
_, status_code = set_settings(data, settings, patterns)
|
message = json.loads(data)
|
||||||
#await ws.send(status_code)
|
light = message.get("light")
|
||||||
|
if message["light"] in settings.get("lights"):
|
||||||
|
settings["lights"][light].update(message["settings"])
|
||||||
|
if message["save"]:
|
||||||
|
settings.save()
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("Invalid JSON received")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
async def send(data):
|
print("WebSocket connection closed")
|
||||||
|
|
||||||
msg = {"delay": 1000}
|
@app.get("/light")
|
||||||
try:
|
async def get_lights(request):
|
||||||
if not await e.asend(peer, data):
|
return json.dumps(settings)
|
||||||
print("Heartbeat: peer not responding:", peer)
|
|
||||||
|
@app.get("/light/<light>")
|
||||||
|
async def get_light(request, light):
|
||||||
|
light_data = settings.get(light, None)
|
||||||
|
if light_data:
|
||||||
|
return json.dumps(light_data), 200
|
||||||
else:
|
else:
|
||||||
print("Heartbeat: ping", peer)
|
return json.dumps({"error": "Light not found"}), 404
|
||||||
except:
|
|
||||||
return
|
@app.post("/light/<light>")
|
||||||
|
async def add_light(request, light):
|
||||||
|
try:
|
||||||
|
# Parse the JSON request body
|
||||||
|
data = request.json
|
||||||
|
# Check if the light already exists
|
||||||
|
if light in settings:
|
||||||
|
return json.dumps({"error": "Light already exists"}), 409
|
||||||
|
|
||||||
|
# Add the new light to the settings
|
||||||
|
settings[light] = data
|
||||||
|
print(settings)
|
||||||
|
settings.save()
|
||||||
|
return json.dumps(
|
||||||
|
{"message": "Light added successfully",
|
||||||
|
"light": light}
|
||||||
|
), 200
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception: {e}")
|
||||||
|
return json.dumps({"error": "Invalid JSON request"}), 400
|
||||||
|
|
||||||
|
@app.patch("/light/<light>")
|
||||||
|
async def update_light(request, light):
|
||||||
|
try:
|
||||||
|
# Parse the JSON request body
|
||||||
|
data = request.json
|
||||||
|
print(light, data)
|
||||||
|
# Check if the light exists
|
||||||
|
if light not in settings:
|
||||||
|
return json.dumps({"error": "Light not found"}), 404
|
||||||
|
|
||||||
|
# Update the existing light with the provided data
|
||||||
|
settings[light].update(data)
|
||||||
|
settings.save() # Uncomment if using persistent storage
|
||||||
|
return json.dumps(
|
||||||
|
{"message": "Light updated successfully", "light": settings[light]}
|
||||||
|
), 200
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return json.dumps({"error": "Invalid JSON request"}), 400
|
||||||
|
|
||||||
|
@app.delete("/light/<light>")
|
||||||
|
async def del_light(request, light):
|
||||||
|
if light in settings:
|
||||||
|
# Remove the light from the settings
|
||||||
|
del settings[light]
|
||||||
|
settings.save()
|
||||||
|
return json.dumps({"message": "Light deleted successfully"})
|
||||||
|
else:
|
||||||
|
return json.dumps({"error": "Light not found"}), 404
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = web(settings)
|
||||||
|
app.run()
|
||||||
|
|
Loading…
Reference in New Issue