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 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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
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";
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LED Control</title>
|
||||
<script src="static/main.js"></script>
|
||||
<link rel="stylesheet" href="static/main.css" />
|
||||
<title>Light Component</title>
|
||||
<!-- Link to the external CSS file -->
|
||||
<link rel="stylesheet" href="static/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Control LEDs</h1>
|
||||
<button onclick="selectControls()">Controls</button>
|
||||
<button onclick="selectSettings()">Settings</button>
|
||||
|
||||
<!-- 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>
|
||||
<!-- The light-component will be added dynamically by main.js -->
|
||||
<div id="app"></div>
|
||||
<!-- Import the JavaScript files -->
|
||||
<script type="module" src="static/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
115
src/web.py
115
src/web.py
|
@ -1,26 +1,15 @@
|
|||
from microdot import Microdot, send_file, Response
|
||||
from microdot.utemplate import Template
|
||||
from microdot.websocket import with_websocket
|
||||
import machine
|
||||
from settings import set_settings
|
||||
import wifi
|
||||
import json
|
||||
import aioespnow
|
||||
|
||||
def web(settings, patterns):
|
||||
def web(settings):
|
||||
app = Microdot()
|
||||
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('/')
|
||||
async def index_hnadler(request):
|
||||
mac = wifi.get_mac().hex()
|
||||
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
|
||||
async def index_handler(request):
|
||||
return Template('/index.html').render(settings=settings)
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def static_handler(request, path):
|
||||
|
@ -29,34 +18,96 @@ def web(settings, patterns):
|
|||
return 'Not found', 404
|
||||
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")
|
||||
@with_websocket
|
||||
async def ws(request, ws):
|
||||
# Register the client's WebSocket connection
|
||||
print("WebSocket connection established")
|
||||
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
if data:
|
||||
# Process the received data
|
||||
await send(data)
|
||||
_, status_code = set_settings(data, settings, patterns)
|
||||
#await ws.send(status_code)
|
||||
try:
|
||||
# Parse the JSON message from the client
|
||||
message = json.loads(data)
|
||||
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:
|
||||
break
|
||||
|
||||
async def send(data):
|
||||
print("WebSocket connection closed")
|
||||
|
||||
msg = {"delay": 1000}
|
||||
@app.get("/light")
|
||||
async def get_lights(request):
|
||||
return json.dumps(settings)
|
||||
|
||||
@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:
|
||||
return json.dumps({"error": "Light not found"}), 404
|
||||
|
||||
@app.post("/light/<light>")
|
||||
async def add_light(request, light):
|
||||
try:
|
||||
if not await e.asend(peer, data):
|
||||
print("Heartbeat: peer not responding:", peer)
|
||||
else:
|
||||
print("Heartbeat: ping", peer)
|
||||
except:
|
||||
return
|
||||
# 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
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
app = web(settings)
|
||||
app.run()
|
||||
|
|
Loading…
Reference in New Issue