Update tool: add all RGB color order options and debug mode setting

This commit is contained in:
2025-11-30 22:41:06 +13:00
parent d45846ec74
commit 43bd90f137
2 changed files with 0 additions and 387 deletions

View File

@@ -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

View File

@@ -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(
"<Configure>",
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()