From 43bd90f1374232ec01b7904e502e08f40b30ec71 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sun, 30 Nov 2025 22:41:06 +1300 Subject: [PATCH] Update tool: add all RGB color order options and debug mode setting --- tool/README.md | 58 -------- tool/led_config.py | 329 --------------------------------------------- 2 files changed, 387 deletions(-) delete mode 100644 tool/README.md delete mode 100755 tool/led_config.py diff --git a/tool/README.md b/tool/README.md deleted file mode 100644 index f268b94..0000000 --- a/tool/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# LED Bar Configuration Tool - -A tkinter GUI tool for configuring LED bar settings via mpremote. - -## Features - -- Download `settings.json` from MicroPython device using mpremote -- Edit LED configuration settings -- Upload modified `settings.json` back to device -- Load/save settings from/to local files - -## Requirements - -- Python 3.x with tkinter (usually included) -- mpremote: `pip install mpremote` - -## Usage - -```bash -python3 tool/led_config.py -``` - -Or make it executable: -```bash -chmod +x tool/led_config.py -./tool/led_config.py -``` - -## Configuration Fields - -- **LED Pin**: GPIO pin number for LED strip -- **Number of LEDs**: Total number of LEDs in the strip -- **Color Order**: RGB or RBG color order -- **Device Name**: Name identifier for the device -- **Pattern**: Current LED pattern -- **Color 1/Color 2**: Primary colors (hex format, e.g., #ff0000) -- **Delay**: Pattern delay in milliseconds -- **Brightness**: LED brightness level -- **N1-N6**: Pattern-specific parameters -- **AP Password**: WiFi access point password -- **ID**: Device ID - -## Device Connection - -Default device is `/dev/ttyUSB0`. Change it in the "Device" field if your device is on a different port (e.g., `/dev/ttyACM0`, `COM3` on Windows). - -## Workflow - -1. Enter your device path (e.g., `/dev/ttyUSB0`) -2. Click "Download Settings" to fetch current settings from device -3. Edit any settings as needed -4. Click "Upload Settings" to save changes back to device - -You can also: -- Load settings from a local JSON file -- Save current settings to a local JSON file - - diff --git a/tool/led_config.py b/tool/led_config.py deleted file mode 100755 index ad6574d..0000000 --- a/tool/led_config.py +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/env python3 -""" -LED Bar Configuration Tool -A tkinter GUI for downloading, editing, and uploading settings.json to/from MicroPython devices via mpremote. -""" - -import tkinter as tk -from tkinter import ttk, messagebox, filedialog -import json -import subprocess -import os -import tempfile -import serial -from pathlib import Path - - -class LEDConfigTool: - def __init__(self, root): - self.root = root - self.root.title("LED Bar Configuration Tool") - self.root.geometry("600x700") - - self.settings = {} - self.temp_file = None - - # Create main frame - main_frame = ttk.Frame(root, padding="10") - main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - - # Title - title_label = ttk.Label(main_frame, text="LED Bar Configuration", font=("Arial", 16, "bold")) - title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20)) - - # Device connection section - device_frame = ttk.LabelFrame(main_frame, text="Device Connection", padding="10") - device_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10)) - - ttk.Label(device_frame, text="Device:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) - self.device_entry = ttk.Entry(device_frame, width=30) - self.device_entry.insert(0, "/dev/ttyACM0") # Default device - self.device_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10)) - - ttk.Button(device_frame, text="Download Settings", command=self.download_settings).grid(row=0, column=2) - - # Settings section - settings_frame = ttk.LabelFrame(main_frame, text="Settings", padding="10") - settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10)) - - # Create scrollable frame for settings - canvas = tk.Canvas(settings_frame, height=400) - scrollbar = ttk.Scrollbar(settings_frame, orient="vertical", command=canvas.yview) - scrollable_frame = ttk.Frame(canvas) - - scrollable_frame.bind( - "", - lambda e: canvas.configure(scrollregion=canvas.bbox("all")) - ) - - canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") - canvas.configure(yscrollcommand=scrollbar.set) - - # Settings fields - self.setting_widgets = {} - settings_config = [ - ("led_pin", "LED Pin", "number"), - ("num_leds", "Number of LEDs", "number"), - ("color_order", "Color Order", "choice", ["rgb", "rbg"]), - ("name", "Device Name", "text"), - ("pattern", "Pattern", "text"), - ("color1", "Color 1", "color"), - ("color2", "Color 2", "color"), - ("delay", "Delay (ms)", "number"), - ("brightness", "Brightness", "number"), - ("n1", "N1", "number"), - ("n2", "N2", "number"), - ("n3", "N3", "number"), - ("n4", "N4", "number"), - ("n5", "N5", "number"), - ("n6", "N6", "number"), - ("ap_password", "AP Password", "text"), - ("id", "ID", "number"), - ] - - for idx, config in enumerate(settings_config): - key = config[0] - label_text = config[1] - field_type = config[2] - - ttk.Label(scrollable_frame, text=f"{label_text}:").grid(row=idx, column=0, sticky=tk.W, padx=(0, 10), pady=5) - - if field_type == "number": - widget = ttk.Entry(scrollable_frame, width=20) - elif field_type == "choice": - widget = ttk.Combobox(scrollable_frame, width=17, values=config[3], state="readonly") - elif field_type == "color": - widget = ttk.Entry(scrollable_frame, width=20) - else: # text - widget = ttk.Entry(scrollable_frame, width=20) - - widget.grid(row=idx, column=1, sticky=(tk.W, tk.E), pady=5) - self.setting_widgets[key] = widget - - canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) - scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) - settings_frame.grid_rowconfigure(0, weight=1) - settings_frame.grid_columnconfigure(0, weight=1) - - # Buttons section - button_frame = ttk.Frame(main_frame) - button_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0)) - - ttk.Button(button_frame, text="Load from File", command=self.load_from_file).grid(row=0, column=0, padx=5) - ttk.Button(button_frame, text="Save to File", command=self.save_to_file).grid(row=0, column=1, padx=5) - ttk.Button(button_frame, text="Upload Settings", command=self.upload_settings).grid(row=0, column=2, padx=5) - - # Status bar - self.status_label = ttk.Label(main_frame, text="Ready", relief=tk.SUNKEN, anchor=tk.W) - self.status_label.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0)) - - # Configure grid weights - root.columnconfigure(0, weight=1) - root.rowconfigure(0, weight=1) - main_frame.columnconfigure(0, weight=1) - main_frame.rowconfigure(2, weight=1) - device_frame.columnconfigure(1, weight=1) - - def update_status(self, message): - """Update the status bar message.""" - self.status_label.config(text=message) - self.root.update_idletasks() - - def download_settings(self): - """Download settings.json from the device using mpremote.""" - device = self.device_entry.get().strip() - if not device: - messagebox.showerror("Error", "Please specify a device") - return - - self.update_status("Downloading settings...") - - try: - # Create temporary file - self.temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) - temp_path = self.temp_file.name - self.temp_file.close() - - # Download file using mpremote - cmd = ["mpremote", "connect", device, "cp", ":/settings.json", temp_path] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) - - if result.returncode != 0: - raise Exception(f"mpremote error: {result.stderr}") - - # Load the downloaded file - with open(temp_path, 'r') as f: - self.settings = json.load(f) - - # Update UI with loaded settings - self.update_ui_from_settings() - - self.update_status(f"Settings downloaded successfully from {device}") - messagebox.showinfo("Success", "Settings downloaded successfully!") - - except subprocess.TimeoutExpired: - self.update_status("Error: Connection timeout") - messagebox.showerror("Error", "Connection timeout. Check device connection.") - except FileNotFoundError: - self.update_status("Error: mpremote not found") - messagebox.showerror("Error", "mpremote not found. Please install it:\npip install mpremote") - except Exception as e: - self.update_status(f"Error: {str(e)}") - messagebox.showerror("Error", f"Failed to download settings:\n{str(e)}") - finally: - # Clean up temp file - if self.temp_file and os.path.exists(temp_path): - try: - os.unlink(temp_path) - except: - pass - - def upload_settings(self): - """Upload settings.json to the device using mpremote.""" - device = self.device_entry.get().strip() - if not device: - messagebox.showerror("Error", "Please specify a device") - return - - if not self.settings: - messagebox.showerror("Error", "No settings to upload. Please download or load settings first.") - return - - self.update_status("Uploading settings...") - - try: - # Get current settings from UI - self.update_settings_from_ui() - - # Create temporary file with current settings - temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) - temp_path = temp_file.name - json.dump(self.settings, temp_file, indent=2) - temp_file.close() - - # Upload file using mpremote - cmd = ["mpremote", "connect", device, "cp", temp_path, ":/settings.json"] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) - - if result.returncode != 0: - raise Exception(f"mpremote error: {result.stderr}") - - # Reset the device - self.update_status("Resetting device...") - try: - with serial.Serial(device, baudrate=115200) as ser: - ser.write(b'\x03\x03\x04') - except Exception as e: - # If serial reset fails, try mpremote method as fallback - reset_cmd = ["mpremote", "connect", device, "exec", "import machine; machine.reset()"] - subprocess.run(reset_cmd, capture_output=True, text=True, timeout=5) - - self.update_status(f"Settings uploaded and device reset on {device}") - messagebox.showinfo("Success", "Settings uploaded successfully and device reset!") - - except subprocess.TimeoutExpired: - self.update_status("Error: Connection timeout") - messagebox.showerror("Error", "Connection timeout. Check device connection.") - except FileNotFoundError: - self.update_status("Error: mpremote not found") - messagebox.showerror("Error", "mpremote not found. Please install it:\npip install mpremote") - except Exception as e: - self.update_status(f"Error: {str(e)}") - messagebox.showerror("Error", f"Failed to upload settings:\n{str(e)}") - finally: - # Clean up temp file - if os.path.exists(temp_path): - try: - os.unlink(temp_path) - except: - pass - - def load_from_file(self): - """Load settings from a local JSON file.""" - file_path = filedialog.askopenfilename( - title="Load Settings", - filetypes=[("JSON files", "*.json"), ("All files", "*.*")] - ) - - if not file_path: - return - - try: - with open(file_path, 'r') as f: - self.settings = json.load(f) - - self.update_ui_from_settings() - self.update_status(f"Settings loaded from {os.path.basename(file_path)}") - messagebox.showinfo("Success", "Settings loaded successfully!") - - except Exception as e: - self.update_status(f"Error: {str(e)}") - messagebox.showerror("Error", f"Failed to load settings:\n{str(e)}") - - def save_to_file(self): - """Save current settings to a local JSON file.""" - if not self.settings: - messagebox.showerror("Error", "No settings to save. Please download or load settings first.") - return - - file_path = filedialog.asksaveasfilename( - title="Save Settings", - defaultextension=".json", - filetypes=[("JSON files", "*.json"), ("All files", "*.*")] - ) - - if not file_path: - return - - try: - # Get current settings from UI - self.update_settings_from_ui() - - with open(file_path, 'w') as f: - json.dump(self.settings, f, indent=2) - - self.update_status(f"Settings saved to {os.path.basename(file_path)}") - messagebox.showinfo("Success", "Settings saved successfully!") - - except Exception as e: - self.update_status(f"Error: {str(e)}") - messagebox.showerror("Error", f"Failed to save settings:\n{str(e)}") - - def update_ui_from_settings(self): - """Update UI widgets with current settings values.""" - for key, widget in self.setting_widgets.items(): - if key in self.settings: - value = self.settings[key] - if isinstance(widget, ttk.Combobox): - widget.set(str(value)) - else: - widget.delete(0, tk.END) - widget.insert(0, str(value)) - - def update_settings_from_ui(self): - """Update settings dictionary from UI widget values.""" - for key, widget in self.setting_widgets.items(): - value = widget.get().strip() - if value: - # Try to convert to appropriate type - if key in ["led_pin", "num_leds", "delay", "brightness", "id", "n1", "n2", "n3", "n4", "n5", "n6"]: - try: - self.settings[key] = int(value) - except ValueError: - pass # Keep as string if conversion fails - else: - self.settings[key] = value - elif key in self.settings: - # Keep existing value if widget is empty - pass - - -def main(): - root = tk.Tk() - app = LEDConfigTool(root) - root.mainloop() - - -if __name__ == "__main__": - main() -