Clean up obsolete files
- Remove old web.py, wifi.py, patterns.py - Remove old static files from root - Remove unused component files
This commit is contained in:
291
src/patterns.py
291
src/patterns.py
@@ -1,291 +0,0 @@
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
import utime
|
||||
import random
|
||||
|
||||
class Patterns:
|
||||
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
self.last_update = utime.ticks_ms()
|
||||
self.delay = delay
|
||||
self.brightness = brightness
|
||||
self.patterns = {
|
||||
"off": self.off,
|
||||
"on" : self.on,
|
||||
"color_wipe": self.color_wipe_step,
|
||||
"rainbow_cycle": self.rainbow_cycle_step,
|
||||
"theater_chase": self.theater_chase_step,
|
||||
"blink": self.blink_step,
|
||||
"random_color_wipe": self.random_color_wipe_step,
|
||||
"random_rainbow_cycle": self.random_rainbow_cycle_step,
|
||||
"random_theater_chase": self.random_theater_chase_step,
|
||||
"random_blink": self.random_blink_step,
|
||||
"color_transition": self.color_transition_step,
|
||||
"external": None
|
||||
}
|
||||
self.selected = selected
|
||||
self.color1 = color1
|
||||
self.color2 = color2
|
||||
self.transition_duration = 50 # Duration of color transition in milliseconds
|
||||
self.transition_step = 0
|
||||
|
||||
def sync(self):
|
||||
self.pattern_step=0
|
||||
self.last_update = utime.ticks_ms()
|
||||
|
||||
def tick(self):
|
||||
if self.patterns[self.selected]:
|
||||
self.patterns[self.selected]()
|
||||
|
||||
def update_num_leds(self, pin, num_leds):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
|
||||
def set_delay(self, delay):
|
||||
self.delay = delay
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
self.brightness = brightness
|
||||
|
||||
def set_color1(self, color):
|
||||
print(color)
|
||||
self.color1 = self.apply_brightness(color)
|
||||
|
||||
def set_color2(self, color):
|
||||
self.color2 = self.apply_brightness(color)
|
||||
|
||||
def apply_brightness(self, color):
|
||||
return tuple(int(c * self.brightness / 255) for c in color)
|
||||
|
||||
def select(self, pattern):
|
||||
if pattern in self.patterns:
|
||||
self.selected = pattern
|
||||
return True
|
||||
return False
|
||||
|
||||
def set(self, i, color):
|
||||
self.n[i] = color
|
||||
|
||||
def write(self):
|
||||
self.n.write()
|
||||
|
||||
def fill(self):
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.color1
|
||||
self.n.write()
|
||||
|
||||
def off(self):
|
||||
color = self.color1
|
||||
self.color1 = (0,0,0)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
def on(self):
|
||||
color = self.color1
|
||||
self.color1 = self.apply_brightness(self.color1)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
|
||||
def color_wipe_step(self):
|
||||
color = self.apply_brightness(self.color1)
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step < self.num_leds:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
self.pattern_step += 1
|
||||
else:
|
||||
self.pattern_step = 0
|
||||
self.last_update = current_time
|
||||
|
||||
def rainbow_cycle_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay/5:
|
||||
def wheel(pos):
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
for i in range(self.num_leds):
|
||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step
|
||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 256
|
||||
self.last_update = current_time
|
||||
|
||||
def theater_chase_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
for i in range(self.num_leds):
|
||||
if (i + self.pattern_step) % 3 == 0:
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 3
|
||||
self.last_update = current_time
|
||||
|
||||
def blink_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step % 2 == 0:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
else:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def random_color_wipe_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
if self.pattern_step < self.num_leds:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
self.pattern_step += 1
|
||||
else:
|
||||
self.pattern_step = 0
|
||||
self.last_update = current_time
|
||||
|
||||
def random_rainbow_cycle_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
def wheel(pos):
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
random_offset = random.randint(0, 255)
|
||||
for i in range(self.num_leds):
|
||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step + random_offset
|
||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 256
|
||||
self.last_update = current_time
|
||||
|
||||
def random_theater_chase_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
for i in range(self.num_leds):
|
||||
if (i + self.pattern_step) % 3 == 0:
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 3
|
||||
self.last_update = current_time
|
||||
|
||||
def random_blink_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
if self.pattern_step % 2 == 0:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
else:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def color_transition_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Calculate transition factor based on elapsed time
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
|
||||
# Apply the interpolated color to all LEDs
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
def interpolate_color(self, color1, color2, factor):
|
||||
return (
|
||||
int(color1[0] + (color2[0] - color1[0]) * factor),
|
||||
int(color1[1] + (color2[1] - color1[1]) * factor),
|
||||
int(color1[2] + (color2[2] - color1[2]) * factor)
|
||||
)
|
||||
|
||||
def two_steps_forward_one_step_back_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Move forward 2 steps and backward 1 step
|
||||
if self.direction == 1: # Moving forward
|
||||
if self.scanner_position < self.num_leds - 2:
|
||||
self.scanner_position += 2 # Move forward 2 steps
|
||||
else:
|
||||
self.direction = -1 # Change direction to backward
|
||||
else: # Moving backward
|
||||
if self.scanner_position > 0:
|
||||
self.scanner_position -= 1 # Move backward 1 step
|
||||
else:
|
||||
self.direction = 1 # Change direction to forward
|
||||
|
||||
# Set all LEDs to off
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
|
||||
# Set the current position to the color
|
||||
self.n[self.scanner_position] = self.apply_brightness(self.color1)
|
||||
|
||||
# Apply the color transition
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
self.n[self.scanner_position] = self.apply_brightness(color)
|
||||
|
||||
self.n.write()
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Patterns(4, 180)
|
||||
p.set_color1((255,0,0))
|
||||
p.set_color2((0,255,0))
|
||||
#p.set_delay(10)
|
||||
try:
|
||||
while True:
|
||||
for key in p.patterns:
|
||||
print(key)
|
||||
p.select(key)
|
||||
for _ in range(2000):
|
||||
p.tick()
|
||||
utime.sleep_ms(1)
|
||||
except KeyboardInterrupt:
|
||||
p.fill((0, 0, 0))
|
||||
@@ -1,143 +0,0 @@
|
||||
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,90 +0,0 @@
|
||||
// light-components.js
|
||||
import { LightComponent } from "./light-component.js";
|
||||
import { getWebSocket } from "./websocket.js";
|
||||
|
||||
// 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, appContainer) {
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to create light components from the fetched data
|
||||
export function createLightComponents(appContainer, lightData) {
|
||||
for (const key in lightData) {
|
||||
if (lightData.hasOwnProperty(key)) {
|
||||
const light = lightData[key];
|
||||
createLightComponent(light, key, appContainer); // 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,
|
||||
});
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,81 @@
|
||||
// main.js
|
||||
import { createLightComponents } from "./light-components.js";
|
||||
import { getWebSocket } from "./websocket.js";
|
||||
import "./rgb-slider.js";
|
||||
|
||||
// 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");
|
||||
const ws = new WebSocket("ws://localhost:8000/ws");
|
||||
|
||||
// Fetch the JSON data from the /light endpoint
|
||||
try {
|
||||
const response = await fetch("/light");
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connection established");
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("WebSocket connection closed");
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
|
||||
// Number of sliders (tabs) you want to create
|
||||
const numTabs = 3;
|
||||
|
||||
// Select the container for tabs and content
|
||||
const tabsContainer = document.querySelector(".tabs");
|
||||
const tabContentContainer = document.querySelector(".tab-content");
|
||||
|
||||
// Create tabs dynamically
|
||||
for (let i = 1; i <= numTabs; i++) {
|
||||
// Create the tab button
|
||||
const tabButton = document.createElement("button");
|
||||
tabButton.classList.add("tab");
|
||||
tabButton.id = `tab${i}`;
|
||||
tabButton.textContent = `Tab ${i}`;
|
||||
|
||||
// Add the tab button to the container
|
||||
tabsContainer.appendChild(tabButton);
|
||||
|
||||
// Create the corresponding tab content (RGB slider)
|
||||
const tabContent = document.createElement("div");
|
||||
tabContent.classList.add("tab-pane");
|
||||
tabContent.id = `content${i}`;
|
||||
const slider = document.createElement("rgb-slider");
|
||||
slider.id = i;
|
||||
tabContent.appendChild(slider);
|
||||
|
||||
// Add the tab content to the container
|
||||
tabContentContainer.appendChild(tabContent);
|
||||
|
||||
// Listen for color change on each RGB slider
|
||||
slider.addEventListener("color-change", (e) => {
|
||||
const { r, g, b } = e.detail;
|
||||
console.log(`Color changed in tab ${i}:`, e.detail);
|
||||
// Send RGB data to WebSocket server
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
const colorData = { r, g, b };
|
||||
ws.send(JSON.stringify(colorData));
|
||||
}
|
||||
const lightData = await response.json();
|
||||
|
||||
// Create and configure light components
|
||||
createLightComponents(appContainer, lightData);
|
||||
|
||||
// Initialize WebSocket connection
|
||||
const websocket = getWebSocket();
|
||||
websocket.addEventListener("open", () => {
|
||||
console.log("WebSocket connection established.");
|
||||
});
|
||||
websocket.addEventListener("message", (event) => {
|
||||
console.log("Message from server:", event.data);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching light data:", error);
|
||||
}
|
||||
|
||||
// Function to switch tabs
|
||||
function switchTab(tabId) {
|
||||
const tabs = document.querySelectorAll(".tab");
|
||||
const tabContents = document.querySelectorAll(".tab-pane");
|
||||
|
||||
tabs.forEach((tab) => tab.classList.remove("active"));
|
||||
tabContents.forEach((content) => content.classList.remove("active"));
|
||||
|
||||
// Activate the clicked tab and corresponding content
|
||||
document.getElementById(tabId).classList.add("active");
|
||||
document
|
||||
.getElementById("content" + tabId.replace("tab", ""))
|
||||
.classList.add("active");
|
||||
}
|
||||
|
||||
// Add event listeners to tabs
|
||||
tabsContainer.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("tab")) {
|
||||
switchTab(e.target.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Initially set the first tab as active
|
||||
switchTab("tab1");
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
/* 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);
|
||||
/* General tab styles */
|
||||
.tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Styles when the component is being dragged */
|
||||
light-component:active {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-pane.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -2,15 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Light Component</title>
|
||||
<!-- Link to the external CSS file -->
|
||||
<link rel="stylesheet" href="static/styles.css" />
|
||||
<title>RGB Slider Tabs</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- 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>
|
||||
<div class="tabs"></div>
|
||||
<div class="tab-content"></div>
|
||||
|
||||
<script type="module" src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
113
src/web.py
113
src/web.py
@@ -1,113 +0,0 @@
|
||||
from microdot import Microdot, send_file, Response
|
||||
from microdot.utemplate import Template
|
||||
from microdot.websocket import with_websocket
|
||||
import json
|
||||
|
||||
def web(settings):
|
||||
app = Microdot()
|
||||
Response.default_content_type = 'text/html'
|
||||
|
||||
@app.route('/')
|
||||
async def index_handler(request):
|
||||
return Template('/index.html').render(settings=settings)
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def static_handler(request, path):
|
||||
if '..' in path:
|
||||
# Directory traversal is not allowed
|
||||
return 'Not found', 404
|
||||
return send_file('static/' + path)
|
||||
|
||||
@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:
|
||||
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
|
||||
|
||||
print("WebSocket connection closed")
|
||||
|
||||
@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:
|
||||
# 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()
|
||||
38
src/wifi.py
38
src/wifi.py
@@ -1,38 +0,0 @@
|
||||
import network
|
||||
from time import sleep
|
||||
|
||||
def connect(ssid, password, ip, gateway):
|
||||
if ssid is None or password is None:
|
||||
print("Missing ssid or password")
|
||||
return None
|
||||
try:
|
||||
sta_if = network.WLAN(network.STA_IF)
|
||||
if ip is not None and gateway is not None:
|
||||
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
|
||||
if not sta_if.isconnected():
|
||||
print('connecting to network...')
|
||||
sta_if.active(True)
|
||||
sta_if.connect(ssid, password)
|
||||
sleep(0.1)
|
||||
if sta_if.isconnected():
|
||||
return sta_if.ifconfig()
|
||||
return None
|
||||
return sta_if.ifconfig()
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to wifi {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ap(ssid, password):
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
ap_mac = ap_if.config('mac')
|
||||
print(ssid)
|
||||
ap_if.active(True)
|
||||
ap_if.config(essid=ssid, password=password)
|
||||
ap_if.active(False)
|
||||
ap_if.active(True)
|
||||
print(ap_if.ifconfig())
|
||||
|
||||
def get_mac():
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
return ap_if.config('mac')
|
||||
@@ -1,14 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>RGB Slider Tabs</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="tabs"></div>
|
||||
<div class="tab-content"></div>
|
||||
|
||||
<script type="module" src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +0,0 @@
|
||||
import "./rgb-slider.js";
|
||||
|
||||
const ws = new WebSocket("ws://localhost:8000/ws");
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connection established");
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("WebSocket connection closed");
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
|
||||
// Number of sliders (tabs) you want to create
|
||||
const numTabs = 3;
|
||||
|
||||
// Select the container for tabs and content
|
||||
const tabsContainer = document.querySelector(".tabs");
|
||||
const tabContentContainer = document.querySelector(".tab-content");
|
||||
|
||||
// Create tabs dynamically
|
||||
for (let i = 1; i <= numTabs; i++) {
|
||||
// Create the tab button
|
||||
const tabButton = document.createElement("button");
|
||||
tabButton.classList.add("tab");
|
||||
tabButton.id = `tab${i}`;
|
||||
tabButton.textContent = `Tab ${i}`;
|
||||
|
||||
// Add the tab button to the container
|
||||
tabsContainer.appendChild(tabButton);
|
||||
|
||||
// Create the corresponding tab content (RGB slider)
|
||||
const tabContent = document.createElement("div");
|
||||
tabContent.classList.add("tab-pane");
|
||||
tabContent.id = `content${i}`;
|
||||
const slider = document.createElement("rgb-slider");
|
||||
slider.id = i;
|
||||
tabContent.appendChild(slider);
|
||||
|
||||
// Add the tab content to the container
|
||||
tabContentContainer.appendChild(tabContent);
|
||||
|
||||
// Listen for color change on each RGB slider
|
||||
slider.addEventListener("color-change", (e) => {
|
||||
const { r, g, b } = e.detail;
|
||||
console.log(`Color changed in tab ${i}:`, e.detail);
|
||||
// Send RGB data to WebSocket server
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
const colorData = { r, g, b };
|
||||
ws.send(JSON.stringify(colorData));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to switch tabs
|
||||
function switchTab(tabId) {
|
||||
const tabs = document.querySelectorAll(".tab");
|
||||
const tabContents = document.querySelectorAll(".tab-pane");
|
||||
|
||||
tabs.forEach((tab) => tab.classList.remove("active"));
|
||||
tabContents.forEach((content) => content.classList.remove("active"));
|
||||
|
||||
// Activate the clicked tab and corresponding content
|
||||
document.getElementById(tabId).classList.add("active");
|
||||
document
|
||||
.getElementById("content" + tabId.replace("tab", ""))
|
||||
.classList.add("active");
|
||||
}
|
||||
|
||||
// Add event listeners to tabs
|
||||
tabsContainer.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("tab")) {
|
||||
switchTab(e.target.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Initially set the first tab as active
|
||||
switchTab("tab1");
|
||||
@@ -1,195 +0,0 @@
|
||||
// rgb-slider.js
|
||||
|
||||
export class RGBSlider extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadow.innerHTML = `
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
width: 50%;
|
||||
|
||||
font-family: sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 50%;
|
||||
height: 60px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #000;
|
||||
background-color: rgb(0, 0, 0);
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
gap: 50px;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.slider-group {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slider-group input[type="range"] {
|
||||
writing-mode: vertical-lr;
|
||||
direction: rtl;
|
||||
|
||||
width: 10px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.slider-group label {
|
||||
margin-top: 8px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.rgb-inputs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rgb-inputs input {
|
||||
width: 6ch;
|
||||
padding: 2px;
|
||||
font-family: monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgb-inputs label {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rgb-input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 600px) {
|
||||
.preview {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.slider-group input[type="range"] {
|
||||
height: 180px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.rgb-inputs input {
|
||||
font-size: 1em;
|
||||
padding: 4px;
|
||||
width: 7ch;
|
||||
}
|
||||
|
||||
.slider-group label,
|
||||
.rgb-inputs label {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="preview" id="preview"></div>
|
||||
|
||||
<div class="sliders">
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="r">
|
||||
<label>R</label>
|
||||
</div>
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="g">
|
||||
<label>G</label>
|
||||
</div>
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="b">
|
||||
<label>B</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rgb-inputs">
|
||||
<div class="rgb-input-group">
|
||||
<label for="rInput">R</label>
|
||||
<input type="number" min="0" max="255" id="rInput" value="0">
|
||||
</div>
|
||||
<div class="rgb-input-group">
|
||||
<label for="gInput">G</label>
|
||||
<input type="number" min="0" max="255" id="gInput" value="0">
|
||||
</div>
|
||||
<div class="rgb-input-group">
|
||||
<label for="bInput">B</label>
|
||||
<input type="number" min="0" max="255" id="bInput" value="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const get = (id) => shadow.querySelector(id);
|
||||
this.r = get("#r");
|
||||
this.g = get("#g");
|
||||
this.b = get("#b");
|
||||
this.rInput = get("#rInput");
|
||||
this.gInput = get("#gInput");
|
||||
this.bInput = get("#bInput");
|
||||
this.preview = get("#preview");
|
||||
|
||||
const updateColor = (r, g, b) => {
|
||||
this.preview.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
|
||||
this.rInput.value = r;
|
||||
this.gInput.value = g;
|
||||
this.bInput.value = b;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("color-change", {
|
||||
detail: { r, g, b },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const syncFromSliders = () => {
|
||||
const r = +this.r.value;
|
||||
const g = +this.g.value;
|
||||
const b = +this.b.value;
|
||||
updateColor(r, g, b);
|
||||
};
|
||||
|
||||
const syncFromInputs = () => {
|
||||
const r = Math.min(255, Math.max(0, +this.rInput.value));
|
||||
const g = Math.min(255, Math.max(0, +this.gInput.value));
|
||||
const b = Math.min(255, Math.max(0, +this.bInput.value));
|
||||
this.r.value = r;
|
||||
this.g.value = g;
|
||||
this.b.value = b;
|
||||
updateColor(r, g, b);
|
||||
};
|
||||
|
||||
this.r.addEventListener("input", syncFromSliders);
|
||||
this.g.addEventListener("input", syncFromSliders);
|
||||
this.b.addEventListener("input", syncFromSliders);
|
||||
|
||||
this.rInput.addEventListener("change", syncFromInputs);
|
||||
this.gInput.addEventListener("change", syncFromInputs);
|
||||
this.bInput.addEventListener("change", syncFromInputs);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("rgb-slider", RGBSlider);
|
||||
@@ -1,37 +0,0 @@
|
||||
/* General tab styles */
|
||||
.tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-pane.active {
|
||||
display: block;
|
||||
}
|
||||
Reference in New Issue
Block a user