Done a heap
This commit is contained in:
parent
65774837c7
commit
c77fd30f8f
|
@ -1,23 +1,68 @@
|
|||
def adjust_brightness(color, brightness):
|
||||
"""Adjust brightness of an RGB color."""
|
||||
r, g, b = color
|
||||
return (int(r * brightness/255), int(g * brightness/255), int(b * brightness/255))
|
||||
def adjust_brightness(rgb_color, brightness):
|
||||
r, g, b = rgb_color
|
||||
# Convert 0-255 brightness to a scale of 0-1
|
||||
scale_factor = brightness / 255.0
|
||||
|
||||
def rgb_to_hex(color):
|
||||
"""Convert an RGB color to hex format."""
|
||||
return '#{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2])
|
||||
adjusted_r = int(r * scale_factor)
|
||||
adjusted_g = int(g * scale_factor)
|
||||
adjusted_b = int(b * scale_factor)
|
||||
|
||||
# Ensure values are within 0-255
|
||||
adjusted_r = max(0, min(255, adjusted_r))
|
||||
adjusted_g = max(0, min(255, adjusted_g))
|
||||
adjusted_b = max(0, min(255, adjusted_b))
|
||||
|
||||
return (adjusted_r, adjusted_g, adjusted_b)
|
||||
|
||||
|
||||
def generate_color_transition(start_color, end_color, steps):
|
||||
"""Generate a list of colors transitioning from start_color to end_color."""
|
||||
r1, g1, b1 = start_color
|
||||
r2, g2, b2 = end_color
|
||||
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
|
||||
"""Converts a hex color string (e.g., "#RRGGBB") to an RGB tuple."""
|
||||
hex_color = hex_color.lstrip('#')
|
||||
return int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
||||
|
||||
transition_colors = []
|
||||
for i in range(steps):
|
||||
r = r1 + (r2 - r1) * i // (steps - 1)
|
||||
g = g1 + (g2 - g1) * i // (steps - 1)
|
||||
b = b1 + (b2 - b1) * i // (steps - 1)
|
||||
transition_colors.append((r, g, b))
|
||||
|
||||
return transition_colors
|
||||
def rgb_to_hex(r: int, g: int, b: int) -> str:
|
||||
"""Converts an RGB tuple to a hex color string (e.g., "#RRGGBB")."""
|
||||
return f"#{r:02x}{g:02x}{b:02x}"
|
||||
|
||||
def get_contrast_text_color(background_hex_color: str) -> str:
|
||||
"""
|
||||
Determines whether black or white text is more readable on a given background color.
|
||||
Uses the WCAG 2.0 contrast recommendations (Luminosity calculation).
|
||||
"""
|
||||
r, g, b = hex_to_rgb(background_hex_color)
|
||||
|
||||
# Convert RGB to sRGB (0-1 range)
|
||||
# The linear RGB values are normalized by dividing by 255
|
||||
r_linear = r / 255.0
|
||||
g_linear = g / 255.0
|
||||
b_linear = b / 255.0
|
||||
|
||||
# Apply the sRGB to linear conversion for gamma correction
|
||||
# This is a simplified approximation for readability, a more accurate one involves if/else for values <= 0.03928
|
||||
# For a general "light vs dark" determination, this simplified approach is often sufficient.
|
||||
# The formula used here is often simplified as (R*0.299 + G*0.587 + B*0.114) for quick luminance.
|
||||
# A more precise relative luminance (L) calculation:
|
||||
def srgb_to_linear(c):
|
||||
if c <= 0.03928:
|
||||
return c / 12.92
|
||||
else:
|
||||
return ((c + 0.055) / 1.055) ** 2.4
|
||||
|
||||
L = (0.2126 * srgb_to_linear(r_linear) +
|
||||
0.7152 * srgb_to_linear(g_linear) +
|
||||
0.0722 * srgb_to_linear(b_linear))
|
||||
|
||||
# For general UI elements, a luminance threshold around 0.179 (sqrt(0.032)) is often used
|
||||
# or simply checking if (R*0.299 + G*0.587 + B*0.114) is > 186 for light background / dark text
|
||||
# A common rule of thumb for perceived brightness (closer to the one used in many UIs):
|
||||
# (R*299 + G*587 + B*114) / 1000
|
||||
# Let's use a simpler luminance check based on the first example's intention:
|
||||
# If the perceived brightness is above a certain threshold, use black text. Otherwise, use white.
|
||||
|
||||
# A simpler luminance check often used for text contrast:
|
||||
luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
||||
|
||||
if luminance > 0.5: # Adjust this threshold as needed for perceived contrast
|
||||
return "black"
|
||||
else:
|
||||
return "white"
|
||||
|
|
49
config.py
49
config.py
|
@ -1,49 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
default_config = {
|
||||
'color': (255, 0, 0),
|
||||
'brightness': 1.0,
|
||||
'color_format': 'RGB',
|
||||
'servers': [('192.168.0.201', 80)]
|
||||
}
|
||||
|
||||
config_file = 'config.json'
|
||||
|
||||
class ConfigHandler:
|
||||
def load_config(self):
|
||||
"""Load color, brightness, format, and server settings from config file."""
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return default_config
|
||||
|
||||
def save_config(self, color, brightness, color_format, servers):
|
||||
"""Save color, brightness, format, and server settings to config file."""
|
||||
config = {
|
||||
'color': color,
|
||||
'brightness': brightness,
|
||||
'color_format': color_format,
|
||||
'servers': servers
|
||||
}
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config, f)
|
||||
print("Configuration saved.")
|
||||
|
||||
def add_server(self, ip, port):
|
||||
"""Add a new server IP and port."""
|
||||
config = self.load_config()
|
||||
config['servers'].append((ip, port))
|
||||
self.save_config(config['color'], config['brightness'], config['color_format'], config['servers'])
|
||||
print(f"Server {ip}:{port} added.")
|
||||
|
||||
def remove_server(self, ip, port):
|
||||
"""Remove an existing server IP and port."""
|
||||
config = self.load_config()
|
||||
if (ip, port) in config['servers']:
|
||||
config['servers'].remove((ip, port))
|
||||
self.save_config(config['color'], config['brightness'], config['color_format'], config['servers'])
|
||||
print(f"Server {ip}:{port} removed.")
|
||||
else:
|
||||
print(f"Server {ip}:{port} not found.")
|
748
main.py
748
main.py
|
@ -1,118 +1,726 @@
|
|||
import asyncio
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import ttk, messagebox # Import messagebox for confirmations
|
||||
import json
|
||||
from async_tkinter_loop import async_handler, async_mainloop
|
||||
from networking import send_to_server
|
||||
from networking import WebSocketClient
|
||||
import color_utils
|
||||
from settings import Settings
|
||||
import time
|
||||
|
||||
# Dark theme colors (unchanged)
|
||||
bg_color = "#2e2e2e"
|
||||
fg_color = "white"
|
||||
trough_color_red = "#4a0000"
|
||||
trough_color_green = "#004a00"
|
||||
trough_color_blue = "#00004a"
|
||||
trough_color_brightness = "#4a4a4a"
|
||||
trough_color_delay = "#4a4a4a"
|
||||
active_bg_color = "#4a4a4a"
|
||||
highlight_pattern_color = "#6a5acd"
|
||||
# New color for active color in palette
|
||||
active_palette_color_border = "#FFD700" # Gold color
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self) -> None:
|
||||
self.settings = Settings()
|
||||
self.root = tk.Tk()
|
||||
self.root.attributes('-fullscreen', True)
|
||||
self.root.configure(bg=bg_color) # Set background color
|
||||
self.root.attributes("-fullscreen", True)
|
||||
self.root.configure(bg=bg_color)
|
||||
|
||||
# List of servers (IP, Port)
|
||||
self.lightgroups = {
|
||||
"light1": {"ids": [0], "settings": {"color": "#00ff00"}},
|
||||
"light2": {"ids": [0], "settings": {"color": "#ff0000"}}
|
||||
}
|
||||
# Debouncing variables (remain the same)
|
||||
self.last_rgb_update_time = 0
|
||||
self.rgb_update_interval_ms = 100
|
||||
self.pending_rgb_update_id = None
|
||||
|
||||
# Create Notebook for tabs
|
||||
self.last_brightness_update_time = 0
|
||||
self.brightness_update_interval_ms = 100
|
||||
self.pending_brightness_update_id = None
|
||||
|
||||
self.last_delay_update_time = 0
|
||||
self.delay_update_interval_ms = 100
|
||||
self.pending_delay_update_id = None
|
||||
|
||||
# --- WebSocketClient ---
|
||||
self.websocket_client = WebSocketClient("ws://192.168.4.1:80/ws")
|
||||
self.root.after(100, async_handler(self.websocket_client.connect))
|
||||
|
||||
# Configure ttk style (unchanged)
|
||||
style = ttk.Style()
|
||||
style.theme_use("alt")
|
||||
style.configure(".", background=bg_color, foreground=fg_color, font=("Arial", 14))
|
||||
style.configure("TNotebook", background=bg_color, borderwidth=0)
|
||||
style.configure(
|
||||
"TNotebook.Tab", background=bg_color, foreground=fg_color, font=("Arial", 30), padding=[10, 5]
|
||||
)
|
||||
style.map("TNotebook.Tab", background=[("selected", active_bg_color)], foreground=[("selected", fg_color)])
|
||||
style.configure("TFrame", background=bg_color)
|
||||
|
||||
# Create Notebook for tabs (unchanged)
|
||||
self.notebook = ttk.Notebook(self.root)
|
||||
self.notebook.pack(expand=1, fill="both")
|
||||
|
||||
# Create a tab for each server
|
||||
self.tabs = {}
|
||||
for key, value in self.lightgroups.items():
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text=key)
|
||||
self.create_sliders(tab)
|
||||
self.tabs[key] = tab
|
||||
self.create_tabs()
|
||||
|
||||
self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_change)
|
||||
|
||||
# Add Reload Config Button (unchanged)
|
||||
reload_button = tk.Button(
|
||||
self.root,
|
||||
text="Reload Config",
|
||||
command=self.reload_config,
|
||||
bg=active_bg_color,
|
||||
fg=fg_color,
|
||||
font=("Arial", 20),
|
||||
padx=20,
|
||||
pady=10,
|
||||
relief=tk.FLAT,
|
||||
)
|
||||
reload_button.pack(side=tk.BOTTOM, pady=20)
|
||||
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
|
||||
async_mainloop(self.root)
|
||||
|
||||
def create_sliders(self, tab):
|
||||
"""Create sliders for each tab."""
|
||||
def on_closing(self):
|
||||
print("Closing application...")
|
||||
asyncio.create_task(self.websocket_client.close())
|
||||
self.root.destroy()
|
||||
|
||||
def create_tabs(self):
|
||||
for tab_name in list(self.tabs.keys()):
|
||||
self.notebook.forget(self.tabs[tab_name])
|
||||
del self.tabs[tab_name]
|
||||
|
||||
for key, value in self.settings["lights"].items():
|
||||
tab = ttk.Frame(self.notebook)
|
||||
self.notebook.add(tab, text=key)
|
||||
self.create_light_control_widgets(tab, key, value["names"], value["settings"])
|
||||
self.tabs[key] = tab
|
||||
|
||||
def create_light_control_widgets(self, tab, tab_name, ids, initial_settings):
|
||||
slider_length = 800
|
||||
slider_width = 50
|
||||
|
||||
# Red Slider
|
||||
red_slider = tk.Scale(tab, from_=255, to=0, orient=tk.VERTICAL, length=slider_length, width=slider_width, label="Red")
|
||||
red_slider.set(0)
|
||||
red_slider.pack(side=tk.LEFT, padx=10)
|
||||
red_slider.bind("<ButtonRelease-1>", lambda _: self.update_colour(tab))
|
||||
# Extract initial color, brightness, and delay
|
||||
# The 'colors' entry can now be a list. We'll pick the first one for initial display.
|
||||
initial_colors = initial_settings.get("colors", ["#000000"])
|
||||
initial_hex_color = initial_colors[0] if initial_colors else "#000000"
|
||||
initial_brightness = initial_settings.get("brightness", 127)
|
||||
initial_delay = initial_settings.get("delay", 0)
|
||||
initial_pattern = initial_settings.get("pattern", "on")
|
||||
|
||||
# Green Slider
|
||||
green_slider = tk.Scale(tab, from_=255, to=0, orient=tk.VERTICAL, length=slider_length, width=slider_width, label="Green")
|
||||
green_slider.set(0)
|
||||
green_slider.pack(side=tk.LEFT, padx=10)
|
||||
green_slider.bind("<ButtonRelease-1>", lambda _: self.update_colour(tab))
|
||||
initial_r, initial_g, initial_b = color_utils.hex_to_rgb(initial_hex_color)
|
||||
|
||||
# Blue Slider
|
||||
blue_slider = tk.Scale(tab, from_=255, to=0, orient=tk.VERTICAL, length=slider_length, width=slider_width, label="Blue")
|
||||
blue_slider.set(0)
|
||||
blue_slider.pack(side=tk.LEFT, padx=10)
|
||||
blue_slider.bind("<ButtonRelease-1>", lambda _: self.update_colour(tab))
|
||||
# Main frame to hold everything within the tab
|
||||
main_tab_frame = tk.Frame(tab, bg=bg_color)
|
||||
main_tab_frame.pack(expand=True, fill="both", padx=10, pady=10)
|
||||
|
||||
# Brightness Slider
|
||||
brightness_slider = tk.Scale(tab, from_=255, to=0, orient=tk.VERTICAL, length=slider_length, width=slider_width, label="Brightness")
|
||||
brightness_slider.set(127)
|
||||
brightness_slider.pack(side=tk.LEFT, padx=10)
|
||||
brightness_slider.bind("<ButtonRelease-1>", lambda _: self.update_colour(tab))
|
||||
# Left panel for sliders
|
||||
slider_panel_frame = tk.Frame(main_tab_frame, bg=bg_color)
|
||||
slider_panel_frame.pack(side=tk.LEFT, padx=10, pady=10)
|
||||
|
||||
tab.widgets = {
|
||||
'red_slider': red_slider,
|
||||
'green_slider': green_slider,
|
||||
'blue_slider': blue_slider,
|
||||
'brightness_slider': brightness_slider,
|
||||
# Common slider configuration
|
||||
slider_config = {
|
||||
"from_": 255,
|
||||
"to": 0,
|
||||
"orient": tk.VERTICAL,
|
||||
"length": slider_length,
|
||||
"width": slider_width,
|
||||
"bg": bg_color,
|
||||
"fg": fg_color,
|
||||
"highlightbackground": bg_color,
|
||||
"activebackground": active_bg_color,
|
||||
"resolution": 1,
|
||||
"sliderlength": 70,
|
||||
}
|
||||
|
||||
# Red Slider
|
||||
red_slider = tk.Scale(slider_panel_frame, label="Red", troughcolor=trough_color_red, **slider_config)
|
||||
red_slider.set(initial_r)
|
||||
red_slider.pack(side=tk.LEFT, padx=10)
|
||||
red_slider.bind("<B1-Motion>", lambda _: self.schedule_update_rgb(tab))
|
||||
red_slider.bind("<ButtonRelease-1>", lambda _: self.schedule_update_rgb(tab, force_send=True))
|
||||
|
||||
# Green Slider
|
||||
green_slider = tk.Scale(slider_panel_frame, label="Green", troughcolor=trough_color_green, **slider_config)
|
||||
green_slider.set(initial_g)
|
||||
green_slider.pack(side=tk.LEFT, padx=10)
|
||||
green_slider.bind("<B1-Motion>", lambda _: self.schedule_update_rgb(tab))
|
||||
green_slider.bind("<ButtonRelease-1>", lambda _: self.schedule_update_rgb(tab, force_send=True))
|
||||
|
||||
# Blue Slider
|
||||
blue_slider = tk.Scale(slider_panel_frame, label="Blue", troughcolor=trough_color_blue, **slider_config)
|
||||
blue_slider.set(initial_b)
|
||||
blue_slider.pack(side=tk.LEFT, padx=10)
|
||||
blue_slider.bind("<B1-Motion>", lambda _: self.schedule_update_rgb(tab))
|
||||
blue_slider.bind("<ButtonRelease-1>", lambda _: self.schedule_update_rgb(tab, force_send=True))
|
||||
|
||||
# Brightness Slider
|
||||
brightness_slider = tk.Scale(
|
||||
slider_panel_frame, label="Brightness", troughcolor=trough_color_brightness, **slider_config
|
||||
)
|
||||
brightness_slider.set(initial_brightness)
|
||||
brightness_slider.pack(side=tk.LEFT, padx=10)
|
||||
brightness_slider.bind("<B1-Motion>", lambda _: self.schedule_update_brightness(tab))
|
||||
brightness_slider.bind("<ButtonRelease-1>", lambda _: self.schedule_update_brightness(tab, force_send=True))
|
||||
|
||||
# Delay Slider
|
||||
delay_slider_config = slider_config.copy()
|
||||
delay_slider_config.update(
|
||||
{
|
||||
"from_": 1000,
|
||||
"to": 0,
|
||||
"resolution": 10,
|
||||
"label": "Delay (ms)",
|
||||
"troughcolor": trough_color_delay,
|
||||
}
|
||||
)
|
||||
delay_slider = tk.Scale(slider_panel_frame, **delay_slider_config)
|
||||
delay_slider.set(initial_delay)
|
||||
delay_slider.pack(side=tk.LEFT, padx=10)
|
||||
delay_slider.bind("<B1-Motion>", lambda _: self.schedule_update_delay(tab))
|
||||
delay_slider.bind("<ButtonRelease-1>", lambda _: self.schedule_update_delay(tab, force_send=True))
|
||||
|
||||
# Store references to widgets for this tab
|
||||
tab.widgets = {
|
||||
"red_slider": red_slider,
|
||||
"green_slider": green_slider,
|
||||
"blue_slider": blue_slider,
|
||||
"brightness_slider": brightness_slider,
|
||||
"delay_slider": delay_slider,
|
||||
"selected_color_index": 0, # Default to the first color
|
||||
}
|
||||
tab.colors_in_palette = initial_colors.copy() # Store the list of hex colors for this tab
|
||||
tab.color_swatch_frames = [] # To hold references to the color swatches
|
||||
|
||||
# Right panel for IDs, Patterns, and NEW Color Palette
|
||||
right_panel_frame = tk.Frame(main_tab_frame, bg=bg_color)
|
||||
right_panel_frame.pack(side=tk.LEFT, padx=20, pady=10, anchor="n", expand=True, fill="both")
|
||||
|
||||
# IDs section (unchanged)
|
||||
ids_frame = tk.Frame(right_panel_frame, bg=bg_color)
|
||||
ids_frame.pack(pady=10, fill=tk.X)
|
||||
tk.Label(ids_frame, text="Associated Names:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(pady=10)
|
||||
for light_id in ids:
|
||||
tk.Label(ids_frame, text=str(light_id), font=("Arial", 18), bg=bg_color, fg=fg_color).pack(pady=2)
|
||||
|
||||
# --- New Frame to hold Patterns and Color Palette side-by-side ---
|
||||
patterns_and_palette_frame = tk.Frame(right_panel_frame, bg=bg_color)
|
||||
patterns_and_palette_frame.pack(pady=20, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Patterns section
|
||||
patterns_frame = tk.Frame(patterns_and_palette_frame, bg=bg_color, bd=2, relief=tk.GROOVE)
|
||||
patterns_frame.pack(side=tk.LEFT, padx=10, pady=5, fill=tk.BOTH, expand=True) # Pack to the left
|
||||
tk.Label(patterns_frame, text="Patterns:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(pady=10)
|
||||
|
||||
tab.pattern_buttons = {}
|
||||
patterns = self.settings.get("patterns", [])
|
||||
for pattern_name in patterns:
|
||||
button = tk.Button(
|
||||
patterns_frame,
|
||||
text=pattern_name,
|
||||
command=lambda p=pattern_name: self.send_pattern(tab_name, p),
|
||||
bg=active_bg_color,
|
||||
fg=fg_color,
|
||||
font=("Arial", 18),
|
||||
padx=15,
|
||||
pady=5,
|
||||
relief=tk.FLAT,
|
||||
)
|
||||
button.pack(pady=5, fill=tk.X)
|
||||
tab.pattern_buttons[pattern_name] = button
|
||||
|
||||
self.highlight_pattern_button(tab, initial_pattern)
|
||||
|
||||
# --- Color Palette Editor Section ---
|
||||
color_palette_editor_frame = tk.Frame(patterns_and_palette_frame, bg=bg_color, bd=2, relief=tk.GROOVE)
|
||||
color_palette_editor_frame.pack(side=tk.LEFT, padx=10, pady=5, fill=tk.BOTH, expand=True) # Pack to the left
|
||||
tab.color_palette_editor_frame = color_palette_editor_frame # Store reference for update_ui_for_pattern
|
||||
|
||||
tk.Label(color_palette_editor_frame, text="Color Palette:", font=("Arial", 20), bg=bg_color, fg=fg_color).pack(
|
||||
pady=10
|
||||
)
|
||||
|
||||
# Frame to hold color swatches (will be dynamic)
|
||||
tab.color_swatches_container = tk.Frame(color_palette_editor_frame, bg=bg_color)
|
||||
tab.color_swatches_container.pack(pady=5, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Buttons for Add/Remove Color
|
||||
palette_buttons_frame = tk.Frame(color_palette_editor_frame, bg=bg_color)
|
||||
palette_buttons_frame.pack(pady=10, fill=tk.X)
|
||||
|
||||
add_color_button = tk.Button(
|
||||
palette_buttons_frame,
|
||||
text="Add Color",
|
||||
command=lambda t=tab: self.add_color_to_palette(t),
|
||||
bg=active_bg_color,
|
||||
fg=fg_color,
|
||||
font=("Arial", 16),
|
||||
padx=10,
|
||||
pady=5,
|
||||
relief=tk.FLAT,
|
||||
)
|
||||
add_color_button.pack(side=tk.LEFT, expand=True, padx=5)
|
||||
|
||||
remove_color_button = tk.Button(
|
||||
palette_buttons_frame,
|
||||
text="Remove Selected",
|
||||
command=lambda t=tab: self.remove_selected_color_from_palette(t),
|
||||
bg=active_bg_color,
|
||||
fg=fg_color,
|
||||
font=("Arial", 16),
|
||||
padx=10,
|
||||
pady=5,
|
||||
relief=tk.FLAT,
|
||||
)
|
||||
remove_color_button.pack(side=tk.RIGHT, expand=True, padx=5)
|
||||
|
||||
# Initial population of the color palette
|
||||
self.refresh_color_palette_display(tab)
|
||||
|
||||
# The initial call to update_ui_for_pattern now only sets slider values and highlights
|
||||
self.update_ui_for_pattern(tab, initial_pattern)
|
||||
|
||||
def refresh_color_palette_display(self, tab):
|
||||
"""Clears and repopulates the color swatches in the palette display."""
|
||||
# Clear existing swatches
|
||||
for frame in tab.color_swatch_frames:
|
||||
frame.destroy()
|
||||
tab.color_swatch_frames.clear()
|
||||
|
||||
for i, hex_color in enumerate(tab.colors_in_palette):
|
||||
swatch_frame = tk.Frame(
|
||||
tab.color_swatches_container, bg=hex_color, width=100, height=50, bd=2, relief=tk.SOLID
|
||||
)
|
||||
swatch_frame.pack(pady=2, padx=5, fill=tk.X)
|
||||
# Bind click to select this color for editing
|
||||
swatch_frame.bind("<Button-1>", lambda event, idx=i, t=tab: self.select_color_in_palette(t, idx))
|
||||
|
||||
# Add a label inside to make it clickable too
|
||||
swatch_label = tk.Label(
|
||||
swatch_frame,
|
||||
text=f"Color {i+1}",
|
||||
bg=hex_color,
|
||||
fg=color_utils.get_contrast_text_color(hex_color),
|
||||
font=("Arial", 14),
|
||||
width=10,
|
||||
height=2,
|
||||
)
|
||||
swatch_label.pack(expand=True, fill=tk.BOTH)
|
||||
swatch_label.bind("<Button-1>", lambda event, idx=i, t=tab: self.select_color_in_palette(t, idx))
|
||||
|
||||
tab.color_swatch_frames.append(swatch_frame)
|
||||
|
||||
# Re-highlight the currently selected color
|
||||
self._highlight_selected_color_swatch(tab)
|
||||
|
||||
def _highlight_selected_color_swatch(self, tab):
|
||||
"""Applies/removes highlight border to the selected color swatch."""
|
||||
current_index = tab.widgets["selected_color_index"]
|
||||
for i, swatch_frame in enumerate(tab.color_swatch_frames):
|
||||
if i == current_index:
|
||||
swatch_frame.config(highlightbackground=active_palette_color_border, highlightthickness=3)
|
||||
else:
|
||||
swatch_frame.config(highlightbackground=swatch_frame.cget("bg"), highlightthickness=0) # Reset to no highlight
|
||||
|
||||
def select_color_in_palette(self, tab, index: int):
|
||||
"""Selects a color in the palette, updates sliders, and highlights swatch."""
|
||||
if not (0 <= index < len(tab.colors_in_palette)):
|
||||
return
|
||||
|
||||
tab.widgets["selected_color_index"] = index
|
||||
self._highlight_selected_color_swatch(tab)
|
||||
|
||||
# Update RGB sliders with the selected color
|
||||
hex_color = tab.colors_in_palette[index]
|
||||
r, g, b = color_utils.hex_to_rgb(hex_color)
|
||||
tab.widgets["red_slider"].set(r)
|
||||
tab.widgets["green_slider"].set(g)
|
||||
tab.widgets["blue_slider"].set(b)
|
||||
|
||||
print(f"Selected color index {index}: {hex_color}")
|
||||
|
||||
def add_color_to_palette(self, tab):
|
||||
"""Adds a new black color to the palette and selects it, with a limit of 10 colors."""
|
||||
MAX_COLORS = 10 # Define the maximum number of colors allowed
|
||||
|
||||
if len(tab.colors_in_palette) >= MAX_COLORS:
|
||||
messagebox.showwarning("Color Limit Reached", f"You can add a maximum of {MAX_COLORS} colors to the palette.")
|
||||
return
|
||||
|
||||
tab.colors_in_palette.append("#000000") # Add black as default
|
||||
self.refresh_color_palette_display(tab)
|
||||
# Select the newly added color
|
||||
self.select_color_in_palette(tab, len(tab.colors_in_palette) - 1)
|
||||
self.save_current_tab_settings() # Save changes to settings.json
|
||||
|
||||
def remove_selected_color_from_palette(self, tab):
|
||||
"""Removes the currently selected color from the palette."""
|
||||
current_index = tab.widgets["selected_color_index"]
|
||||
if len(tab.colors_in_palette) <= 1:
|
||||
messagebox.showwarning("Cannot Remove", "There must be at least one color in the palette.")
|
||||
return
|
||||
|
||||
if messagebox.askyesno("Confirm Delete", f"Are you sure you want to remove Color {current_index + 1}?"):
|
||||
del tab.colors_in_palette[current_index]
|
||||
# Adjust selected index if the removed color was the last one
|
||||
if current_index >= len(tab.colors_in_palette):
|
||||
tab.widgets["selected_color_index"] = len(tab.colors_in_palette) - 1
|
||||
if tab.widgets["selected_color_index"] < 0: # Should not happen if check above works
|
||||
tab.widgets["selected_color_index"] = 0
|
||||
|
||||
self.refresh_color_palette_display(tab)
|
||||
# Update sliders with the new selected color (if any)
|
||||
if tab.colors_in_palette:
|
||||
self.select_color_in_palette(tab, tab.widgets["selected_color_index"])
|
||||
else: # If palette became empty (shouldn't happen with 1-color check)
|
||||
tab.widgets["red_slider"].set(0)
|
||||
tab.widgets["green_slider"].set(0)
|
||||
tab.widgets["blue_slider"].set(0)
|
||||
|
||||
self.save_current_tab_settings() # Save changes to settings.json
|
||||
|
||||
def update_ui_for_pattern(self, tab, current_pattern: str):
|
||||
"""
|
||||
Manages the state of the UI elements based on the selected pattern.
|
||||
The Color Palette Editor is now always visible when 'transition' is selected,
|
||||
and RGB sliders update based on the selected color in the palette.
|
||||
When not 'transition', RGB sliders revert to the first color in settings.
|
||||
"""
|
||||
# The color_palette_editor_frame is now *always* packed in create_light_control_widgets
|
||||
# when patterns_and_palette_frame is created with side-by-side packing.
|
||||
# So we no longer need to pack/pack_forget it here.
|
||||
# Its visibility is handled by its initial creation and packing alongside the patterns.
|
||||
|
||||
# If the pattern is "transition", select the current color in the palette
|
||||
# and ensure the RGB sliders reflect that color.
|
||||
if current_pattern == "transition":
|
||||
# This handles refreshing the display and setting sliders to the selected color
|
||||
self.refresh_color_palette_display(tab)
|
||||
self.select_color_in_palette(tab, tab.widgets["selected_color_index"])
|
||||
else:
|
||||
# When switching away from transition, ensure RGB sliders show the first color from settings
|
||||
# and reset selected_color_index.
|
||||
initial_colors = self.settings["lights"][self.notebook.tab(self.notebook.select(), "text")]["settings"].get(
|
||||
"colors", ["#000000"]
|
||||
)
|
||||
initial_hex_color = initial_colors[0] if initial_colors else "#000000"
|
||||
r, g, b = color_utils.hex_to_rgb(initial_hex_color)
|
||||
tab.widgets["red_slider"].set(r)
|
||||
tab.widgets["green_slider"].set(g)
|
||||
tab.widgets["blue_slider"].set(b)
|
||||
tab.widgets["selected_color_index"] = 0 # Reset selected color index to the first (default)
|
||||
self._highlight_selected_color_swatch(
|
||||
tab
|
||||
) # Remove highlight if active color is no longer relevant for editing
|
||||
# (or just highlight the 0th if you want)
|
||||
|
||||
# Brightness and Delay sliders are always visible.
|
||||
|
||||
def highlight_pattern_button(self, tab_widget, active_pattern_name):
|
||||
if hasattr(tab_widget, "pattern_buttons"):
|
||||
for pattern_name, button in tab_widget.pattern_buttons.items():
|
||||
if pattern_name == active_pattern_name:
|
||||
button.config(bg=highlight_pattern_color)
|
||||
else:
|
||||
button.config(bg=active_bg_color)
|
||||
|
||||
def on_tab_change(self, event):
|
||||
selected_tab_name = self.notebook.tab(self.notebook.select(), "text")
|
||||
current_tab_widget = self.notebook.nametowidget(self.notebook.select())
|
||||
|
||||
initial_settings = self.settings["lights"][selected_tab_name]["settings"]
|
||||
|
||||
# Ensure current_tab_widget has the necessary attributes
|
||||
if not hasattr(current_tab_widget, "colors_in_palette"):
|
||||
# This tab might not have been fully initialized yet, or recreated
|
||||
# In a full reload, create_tabs ensures it is.
|
||||
return
|
||||
|
||||
# Update the local colors_in_palette list for the tab
|
||||
current_tab_widget.colors_in_palette = initial_settings.get("colors", ["#000000"]).copy()
|
||||
current_tab_widget.widgets["selected_color_index"] = 0 # Default to first color
|
||||
|
||||
# Refresh the color palette display and select the first color
|
||||
self.refresh_color_palette_display(current_tab_widget)
|
||||
if current_tab_widget.colors_in_palette:
|
||||
self.select_color_in_palette(current_tab_widget, 0)
|
||||
else: # If palette became empty (shouldn't happen with default ["#000000"])
|
||||
current_tab_widget.widgets["red_slider"].set(0)
|
||||
current_tab_widget.widgets["green_slider"].set(0)
|
||||
current_tab_widget.widgets["blue_slider"].set(0)
|
||||
|
||||
# Update brightness and delay sliders
|
||||
current_tab_widget.widgets["brightness_slider"].set(initial_settings.get("brightness", 127))
|
||||
current_tab_widget.widgets["delay_slider"].set(initial_settings.get("delay", 0))
|
||||
|
||||
# Highlight the active pattern button
|
||||
initial_pattern = initial_settings.get("pattern", "on")
|
||||
self.highlight_pattern_button(current_tab_widget, initial_pattern)
|
||||
|
||||
# Update UI visibility based on the current pattern
|
||||
self.update_ui_for_pattern(current_tab_widget, initial_pattern)
|
||||
|
||||
def reload_config(self):
|
||||
print("Reloading configuration...")
|
||||
self.settings = Settings()
|
||||
self.create_tabs()
|
||||
# After recreating, ensure the currently selected tab's sliders are updated
|
||||
# Trigger on_tab_change manually for the currently selected tab
|
||||
self.on_tab_change(None)
|
||||
|
||||
# --- Debouncing functions (no change to core logic, just how they call update_rgb) ---
|
||||
def schedule_update_rgb(self, tab, force_send=False):
|
||||
current_time = time.time() * 1000
|
||||
if force_send:
|
||||
if self.pending_rgb_update_id:
|
||||
self.root.after_cancel(self.pending_rgb_update_id)
|
||||
self.pending_rgb_update_id = None
|
||||
self.update_rgb(tab)
|
||||
self.last_rgb_update_time = current_time
|
||||
elif current_time - self.last_rgb_update_time >= self.rgb_update_interval_ms:
|
||||
if self.pending_rgb_update_id:
|
||||
self.root.after_cancel(self.pending_rgb_update_id)
|
||||
self.pending_rgb_update_id = None
|
||||
self.update_rgb(tab)
|
||||
self.last_rgb_update_time = current_time
|
||||
else:
|
||||
if self.pending_rgb_update_id:
|
||||
self.root.after_cancel(self.pending_rgb_update_id)
|
||||
time_to_wait = int(self.rgb_update_interval_ms - (current_time - self.last_rgb_update_time))
|
||||
self.pending_rgb_update_id = self.root.after(time_to_wait, lambda: self.update_rgb(tab))
|
||||
|
||||
def schedule_update_brightness(self, tab, force_send=False):
|
||||
current_time = time.time() * 1000
|
||||
if force_send:
|
||||
if self.pending_brightness_update_id:
|
||||
self.root.after_cancel(self.pending_brightness_update_id)
|
||||
self.pending_brightness_update_id = None
|
||||
self.update_brightness(tab)
|
||||
self.last_brightness_update_time = current_time
|
||||
elif current_time - self.last_brightness_update_time >= self.brightness_update_interval_ms:
|
||||
if self.pending_brightness_update_id:
|
||||
self.root.after_cancel(self.pending_brightness_update_id)
|
||||
self.pending_brightness_update_id = None
|
||||
self.update_brightness(tab)
|
||||
self.last_brightness_update_time = current_time
|
||||
else:
|
||||
if self.pending_brightness_update_id:
|
||||
self.root.after_cancel(self.pending_brightness_update_id)
|
||||
time_to_wait = int(self.brightness_update_interval_ms - (current_time - self.last_brightness_update_time))
|
||||
self.pending_brightness_update_id = self.root.after(time_to_wait, lambda: self.update_brightness(tab))
|
||||
|
||||
def schedule_update_delay(self, tab, force_send=False):
|
||||
current_time = time.time() * 1000
|
||||
if force_send:
|
||||
if self.pending_delay_update_id:
|
||||
self.root.after_cancel(self.pending_delay_update_id)
|
||||
self.pending_delay_update_id = None
|
||||
self.update_delay(tab)
|
||||
self.last_delay_update_time = current_time
|
||||
elif current_time - self.last_delay_update_time >= self.delay_update_interval_ms:
|
||||
if self.pending_delay_update_id:
|
||||
self.root.after_cancel(self.pending_delay_update_id)
|
||||
self.pending_delay_update_id = None
|
||||
self.update_delay(tab)
|
||||
self.last_delay_update_time = current_time
|
||||
else:
|
||||
if self.pending_delay_update_id:
|
||||
self.root.after_cancel(self.pending_delay_update_id)
|
||||
time_to_wait = int(self.delay_update_interval_ms - (current_time - self.last_delay_update_time))
|
||||
self.pending_delay_update_id = self.root.after(time_to_wait, lambda: self.update_delay(tab))
|
||||
|
||||
# --- Asynchronous Update Functions ---
|
||||
@async_handler
|
||||
async def update_colour(self, tab):
|
||||
"""Update color based on the slider values and send to the selected server."""
|
||||
async def update_rgb(self, tab):
|
||||
"""Update the currently selected color in the palette and send to the server."""
|
||||
try:
|
||||
# Retrieve slider values
|
||||
red_slider = tab.widgets['red_slider']
|
||||
green_slider = tab.widgets['green_slider']
|
||||
blue_slider = tab.widgets['blue_slider']
|
||||
brightness_slider = tab.widgets['brightness_slider']
|
||||
red_slider = tab.widgets["red_slider"]
|
||||
green_slider = tab.widgets["green_slider"]
|
||||
blue_slider = tab.widgets["blue_slider"]
|
||||
|
||||
r = red_slider.get()
|
||||
g = green_slider.get()
|
||||
b = blue_slider.get()
|
||||
brightness = brightness_slider.get()
|
||||
|
||||
# Adjust brightness
|
||||
color = color_utils.adjust_brightness((r, g, b), brightness)
|
||||
print(f"Adjusted color: {color}, Brightness: {brightness}")
|
||||
hex_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||
print(f"Updating selected color to: {hex_color}")
|
||||
|
||||
# Convert RGB to hex color
|
||||
hex_color = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
|
||||
selected_color_index = tab.widgets["selected_color_index"]
|
||||
if 0 <= selected_color_index < len(tab.colors_in_palette):
|
||||
tab.colors_in_palette[selected_color_index] = hex_color
|
||||
self.refresh_color_palette_display(tab) # Update swatch immediately
|
||||
|
||||
# Get the selected server
|
||||
selected_server = self.notebook.tab(self.notebook.select(), "text")
|
||||
ids = self.lightgroups[selected_server]["ids"]
|
||||
names = self.settings["lights"][selected_server]["names"]
|
||||
|
||||
# ALWAYS send the full current palette, or at least the first color,
|
||||
# along with other relevant settings, when an RGB slider is moved.
|
||||
# The device firmware will interpret 'colors' based on its current pattern.
|
||||
|
||||
# Determine which colors to send. It's generally safest to send the
|
||||
# full current palette, as the device might need all of them.
|
||||
colors_to_send = tab.colors_in_palette.copy() # Send a copy to be safe
|
||||
|
||||
# Construct WebSocket payload
|
||||
payload = {
|
||||
"save": True,
|
||||
"ids": [0],
|
||||
"save": True, # Always save this change to config
|
||||
"names": names,
|
||||
"settings": {
|
||||
"colors": [hex_color], # Use the dynamically calculated hex color
|
||||
"brightness": brightness, # Use the brightness slider value
|
||||
"pattern": "on"
|
||||
}
|
||||
"colors": colors_to_send,
|
||||
# We might also want to send the current brightness and delay
|
||||
# to ensure the device has the complete state, or at least
|
||||
# ensures these don't get 'unset' if they weren't explicitly changed.
|
||||
# This depends on your firmware's expected payload.
|
||||
"brightness": tab.widgets["brightness_slider"].get(),
|
||||
"delay": tab.widgets["delay_slider"].get(),
|
||||
# Also include the current pattern, so the device knows how to apply colors
|
||||
"pattern": self.settings["lights"][selected_server]["settings"].get("pattern", "on"),
|
||||
},
|
||||
}
|
||||
|
||||
# Send the payload to the server
|
||||
await send_to_server(payload)
|
||||
print(f"Sent payload: {payload}")
|
||||
# Update the settings object with the new color list (and potentially other synced values)
|
||||
self.settings["lights"][selected_server]["settings"]["colors"] = tab.colors_in_palette.copy()
|
||||
# Also ensure brightness, delay, and pattern are up-to-date in settings before saving
|
||||
self.settings["lights"][selected_server]["settings"]["brightness"] = tab.widgets["brightness_slider"].get()
|
||||
self.settings["lights"][selected_server]["settings"]["delay"] = tab.widgets["delay_slider"].get()
|
||||
# Pattern is generally set by the pattern buttons, but including it in the save here
|
||||
# for completeness might be useful depending on your app's state management.
|
||||
# self.settings["lights"][selected_server]["settings"]["pattern"] = current_pattern # Already updated by send_pattern
|
||||
self.settings.save()
|
||||
|
||||
await self.websocket_client.send_data(payload)
|
||||
print(f"Sent RGB payload: {payload}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating color: {e}")
|
||||
# Optionally, display the error in the GUI
|
||||
print(f"Error updating RGB: {e}")
|
||||
|
||||
@async_handler
|
||||
async def update_brightness(self, tab):
|
||||
try:
|
||||
brightness_slider = tab.widgets["brightness_slider"]
|
||||
brightness = brightness_slider.get()
|
||||
print(f"Brightness: {brightness}")
|
||||
|
||||
selected_server = self.notebook.tab(self.notebook.select(), "text")
|
||||
names = self.settings["lights"][selected_server]["names"]
|
||||
|
||||
payload = {
|
||||
"save": True,
|
||||
"names": names,
|
||||
"settings": {
|
||||
"brightness": brightness,
|
||||
},
|
||||
}
|
||||
# Update the settings object with the new brightness
|
||||
self.settings["lights"][selected_server]["settings"]["brightness"] = brightness
|
||||
self.settings.save()
|
||||
await self.websocket_client.send_data(payload)
|
||||
print(f"Sent brightness payload: {payload}")
|
||||
except Exception as e:
|
||||
print(f"Error updating brightness: {e}")
|
||||
|
||||
@async_handler
|
||||
async def update_delay(self, tab):
|
||||
try:
|
||||
delay_slider = tab.widgets["delay_slider"]
|
||||
delay = delay_slider.get()
|
||||
print(f"Delay: {delay}")
|
||||
|
||||
selected_server = self.notebook.tab(self.notebook.select(), "text")
|
||||
names = self.settings["lights"][selected_server]["names"]
|
||||
payload = {
|
||||
"save": True,
|
||||
"names": names,
|
||||
"settings": {
|
||||
"delay": delay,
|
||||
},
|
||||
}
|
||||
# Update the settings object with the new delay
|
||||
self.settings["lights"][selected_server]["settings"]["delay"] = delay
|
||||
self.settings.save()
|
||||
await self.websocket_client.send_data(payload)
|
||||
print(f"Sent delay payload: {payload}")
|
||||
except Exception as e:
|
||||
print(f"Error updating delay: {e}")
|
||||
|
||||
@async_handler
|
||||
async def send_pattern(self, tab_name: str, pattern_name: str):
|
||||
try:
|
||||
names = self.settings["lights"][tab_name]["names"]
|
||||
# Get the actual tab widget to access its `colors_in_palette` and other attributes
|
||||
current_tab_widget = None
|
||||
for key, tab_widget in self.tabs.items():
|
||||
if key == tab_name:
|
||||
current_tab_widget = tab_widget
|
||||
break
|
||||
|
||||
if not current_tab_widget:
|
||||
print(f"Error: Could not find tab widget for {tab_name}")
|
||||
return
|
||||
|
||||
current_settings_for_tab = self.settings["lights"][tab_name]["settings"]
|
||||
|
||||
payload_settings = {
|
||||
"pattern": pattern_name,
|
||||
"brightness": current_settings_for_tab.get("brightness", 127),
|
||||
"delay": current_settings_for_tab.get("delay", 0),
|
||||
}
|
||||
|
||||
# Only include "colors" in the payload if the pattern specifically uses them
|
||||
# For "transition", send the entire palette
|
||||
# For "on", "off", "blink", usually just the first color from the palette is relevant
|
||||
if pattern_name == "transition":
|
||||
# Ensure we send the *current state* of the palette
|
||||
payload_settings["colors"] = current_tab_widget.colors_in_palette
|
||||
elif pattern_name in ["on", "blink"]: # Add other patterns that use a single color here
|
||||
if current_tab_widget.colors_in_palette:
|
||||
payload_settings["colors"] = [current_tab_widget.colors_in_palette[0]]
|
||||
else:
|
||||
payload_settings["colors"] = ["#000000"] # Default if palette is empty
|
||||
# For patterns like "off" or "rainbow", "colors" might not be needed or handled differently
|
||||
|
||||
payload = {
|
||||
"save": True,
|
||||
"names": names,
|
||||
"settings": payload_settings,
|
||||
}
|
||||
|
||||
# Update the settings object with the new pattern and current colors/brightness/delay
|
||||
# It's important to save the state that *was active* when the pattern was set,
|
||||
# or the state that should *persist* with the pattern.
|
||||
self.settings["lights"][tab_name]["settings"]["pattern"] = pattern_name
|
||||
# Ensure the saved colors are always the palette's current state
|
||||
self.settings["lights"][tab_name]["settings"]["colors"] = current_tab_widget.colors_in_palette
|
||||
self.settings.save()
|
||||
|
||||
self.highlight_pattern_button(current_tab_widget, pattern_name)
|
||||
self.update_ui_for_pattern(current_tab_widget, pattern_name) # Update visibility
|
||||
|
||||
await self.websocket_client.send_data(payload)
|
||||
print(f"Sent pattern payload: {payload}")
|
||||
except Exception as e:
|
||||
print(f"Error sending pattern: {e}")
|
||||
|
||||
def save_current_tab_settings(self):
|
||||
"""Saves the current state of the active tab's settings (colors, brightness, delay, pattern) to config."""
|
||||
selected_server = self.notebook.tab(self.notebook.select(), "text")
|
||||
current_tab_widget = self.notebook.nametowidget(self.notebook.select())
|
||||
|
||||
if not hasattr(current_tab_widget, "colors_in_palette"):
|
||||
return # Tab not fully initialized yet
|
||||
|
||||
# Update settings for the current tab in the self.settings object
|
||||
self.settings["lights"][selected_server]["settings"]["colors"] = current_tab_widget.colors_in_palette
|
||||
self.settings["lights"][selected_server]["settings"]["brightness"] = current_tab_widget.widgets["brightness_slider"].get()
|
||||
self.settings["lights"][selected_server]["settings"]["delay"] = current_tab_widget.widgets["delay_slider"].get()
|
||||
# The pattern is updated in send_pattern already, but ensure consistency
|
||||
# For simplicity, we assume send_pattern is the primary way to change pattern.
|
||||
|
||||
self.settings.save()
|
||||
print(f"Saved settings for {selected_server}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -2,28 +2,52 @@ import asyncio
|
|||
import websockets
|
||||
import json
|
||||
|
||||
async def send_to_server(data):
|
||||
"""
|
||||
Send WebSocket data to the server.
|
||||
"""
|
||||
try:
|
||||
# Connect to the WebSocket server
|
||||
async with websockets.connect("ws://192.168.4.1:80/ws") as websocket:
|
||||
# Serialize data to JSON and send it
|
||||
await websocket.send(json.dumps(data))
|
||||
except (ConnectionError, websockets.exceptions.ConnectionClosed) as e:
|
||||
print(f"Error sending to {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Define the data to be sent
|
||||
data = {
|
||||
"settings": {
|
||||
"color": "#00ff00"
|
||||
}
|
||||
}
|
||||
class WebSocketClient:
|
||||
def __init__(self, uri):
|
||||
self.uri = uri
|
||||
self.websocket = None
|
||||
self.is_connected = False
|
||||
|
||||
# Server details
|
||||
server = ("192.168.4.1", 80) # Example WebSocket server port
|
||||
async def connect(self):
|
||||
"""Establishes the WebSocket connection."""
|
||||
if self.is_connected and self.websocket:
|
||||
print("Already connected.")
|
||||
return
|
||||
|
||||
# Run the asynchronous function using asyncio.run
|
||||
asyncio.run(send_to_server(data, server))
|
||||
try:
|
||||
print(f"Connecting to {self.uri}...")
|
||||
self.websocket = await websockets.connect(self.uri)
|
||||
self.is_connected = True
|
||||
print("WebSocket connected.")
|
||||
except (ConnectionError, websockets.exceptions.ConnectionClosedOK) as e:
|
||||
print(f"Error connecting: {e}")
|
||||
self.is_connected = False
|
||||
self.websocket = None
|
||||
|
||||
async def send_data(self, data):
|
||||
print(data)
|
||||
"""Sends data over the open WebSocket connection."""
|
||||
if not self.is_connected or not self.websocket:
|
||||
print("WebSocket not connected. Attempting to reconnect...")
|
||||
await self.connect()
|
||||
if not self.is_connected:
|
||||
print("Failed to reconnect. Cannot send data.")
|
||||
return
|
||||
|
||||
try:
|
||||
await self.websocket.send(json.dumps(data))
|
||||
print(f"Sent: {data}")
|
||||
except (ConnectionError, websockets.exceptions.ConnectionClosed) as e:
|
||||
print(f"Error sending data: {e}")
|
||||
self.is_connected = False
|
||||
self.websocket = None # Reset connection on error
|
||||
await self.connect() # Attempt to reconnect
|
||||
|
||||
async def close(self):
|
||||
"""Closes the WebSocket connection."""
|
||||
if self.websocket and self.is_connected:
|
||||
await self.websocket.close()
|
||||
self.is_connected = False
|
||||
self.websocket = None
|
||||
print("WebSocket closed.")
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"lights": {
|
||||
"light1": {
|
||||
"names": [
|
||||
"test"
|
||||
],
|
||||
"settings": {
|
||||
"colors": [
|
||||
"#ff0000",
|
||||
"#0000ff",
|
||||
"#00ff00"
|
||||
],
|
||||
"brightness": 255,
|
||||
"pattern": "blink",
|
||||
"delay": 40
|
||||
}
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
"on",
|
||||
"off",
|
||||
"blink",
|
||||
"rainbow_cycle",
|
||||
"color_transition"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import json
|
||||
|
||||
class Settings(dict):
|
||||
SETTINGS_FILE = "settings.json"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.load() # Load settings from file during initialization
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
j = json.dumps(self, indent=4)
|
||||
with open(self.SETTINGS_FILE, 'w') as file:
|
||||
file.write(j)
|
||||
print("Settings saved successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error saving settings: {e}")
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
with open(self.SETTINGS_FILE, 'r') as file:
|
||||
loaded_settings = json.load(file)
|
||||
self.update(loaded_settings)
|
||||
print("Settings loaded successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error loading settings {e}")
|
||||
self.save()
|
Loading…
Reference in New Issue