6 Commits

8 changed files with 175 additions and 115 deletions

View File

@@ -15,32 +15,37 @@ async def main():
settings = Settings() settings = Settings()
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"]) patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
if settings["color_order"] == "rbg":
color_order = (1, 5, 3)
print("RBG")
if settings["color_order"] == "grb":
color_order = (3, 1, 5)
else: color_order = (1, 3, 5)
patterns.colors = [(8,0,0)] patterns.colors = [(8,0,0)]
# Initialize WDT only if debug is disabled
wdt = None
if not settings.get("debug", False):
wdt = machine.WDT(timeout=10000)
wdt.feed()
print("Watchdog timer enabled")
else:
print("Debug mode: Watchdog timer disabled")
async def system(): async def system():
while True: while True:
gc.collect() gc.collect()
for i in range(60): if wdt is not None:
wdt.feed() for i in range(60):
await asyncio.sleep(1) wdt.feed()
await asyncio.sleep(1)
else:
# If WDT is disabled, just sleep
await asyncio.sleep(60)
w = web(settings, patterns) w = web(settings, patterns)
print(settings) print(settings)
# start the server in a bacakground task # start the server in a bacakground task
print("Starting") print("Starting")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80)) server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
wdt = machine.WDT(timeout=10000)
wdt.feed()
asyncio.create_task(p2p(settings, patterns)) asyncio.create_task(p2p(settings, patterns))
asyncio.create_task(system()) asyncio.create_task(system())
patterns.select(settings["pattern"])
await patterns.run() await patterns.run()

View File

@@ -9,21 +9,19 @@ class Settings(dict):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.load() # Load settings from file during initialization self.load() # Load settings from file during initialization
if self["color_order"] == "rbg": self.color_order = (1, 5, 3) self.color_order = self.get_color_order(self["color_order"])
else: self.color_order = (1, 3, 5)
def set_defaults(self): def set_defaults(self):
self["led_pin"] = 10 self["led_pin"] = 10
self["num_leds"] = 50 self["num_leds"] = 50
self["pattern"] = "on" self["pattern"] = "on"
self["color1"] = "#00ff00"
self["color2"] = "#ff0000"
self["delay"] = 100 self["delay"] = 100
self["brightness"] = 10 self["brightness"] = 10
self["color_order"] = "rgb" self["color_order"] = "rgb"
self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}" self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}"
self["ap_password"] = "" self["ap_password"] = ""
self["id"] = 0 self["id"] = 0
self["debug"] = False
def save(self): def save(self):
try: try:
@@ -84,8 +82,8 @@ class Settings(dict):
self.save() self.save()
machine.reset() machine.reset()
elif key == "color_order": elif key == "color_order":
if value == "rbg": self.color_order = (1, 5, 3) self["color_order"] = value
else: self.color_order = (1, 3, 5) self.color_order = self.get_color_order(value)
pass pass
elif key == "id": elif key == "id":
pass pass
@@ -102,6 +100,18 @@ class Settings(dict):
except (KeyError, ValueError): except (KeyError, ValueError):
return "Bad request", 400 return "Bad request", 400
def get_color_order(self, color_order):
"""Convert color order string to tuple of hex string indices."""
color_orders = {
"rgb": (1, 3, 5),
"rbg": (1, 5, 3),
"grb": (3, 1, 5),
"gbr": (3, 5, 1),
"brg": (5, 1, 3),
"bgr": (5, 3, 1)
}
return color_orders.get(color_order.lower(), (1, 3, 5)) # Default to RGB
# Example usage # Example usage
def main(): def main():
settings = Settings() settings = Settings()

View File

@@ -107,3 +107,57 @@ input[type="range"]::-moz-range-thumb {
margin-right: 10px; margin-right: 10px;
vertical-align: middle; /* Aligns them nicely if heights vary */ vertical-align: middle; /* Aligns them nicely if heights vary */
} }
#colors_palette {
margin-bottom: 20px;
}
#colors_container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.color-item {
display: flex;
align-items: center;
gap: 5px;
}
.color-input {
width: 60px !important;
height: 40px;
border: 2px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.remove-color-btn {
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
width: 30px;
height: 30px;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
.remove-color-btn:hover {
background-color: #da190b;
}
#add_color_btn {
background-color: #4caf50;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
#add_color_btn:hover {
background-color: #45a049;
}

View File

@@ -1,7 +1,6 @@
let delayTimeout; let delayTimeout;
let brightnessTimeout; let brightnessTimeout;
let colorTimeout; let colorsTimeout;
let color2Timeout;
let ws; // Variable to hold the WebSocket connection let ws; // Variable to hold the WebSocket connection
let connectionStatusElement; // Variable to hold the connection status element let connectionStatusElement; // Variable to hold the connection status element
@@ -98,22 +97,60 @@ async function get(path) {
} }
} }
async function updateColor(event) { function updateColors() {
event.preventDefault(); clearTimeout(colorsTimeout);
clearTimeout(colorTimeout); colorsTimeout = setTimeout(function () {
colorTimeout = setTimeout(function () { const colorInputs = document.querySelectorAll(".color-input");
const color = document.getElementById("color").value; const colors = Array.from(colorInputs).map(input => input.value);
sendWebSocketData({ color1: color }); sendWebSocketData({ colors: colors });
}, 500); }, 500);
} }
async function updateColor2(event) { function addColorInput(color = "#ff0000") {
event.preventDefault(); const container = document.getElementById("colors_container");
clearTimeout(color2Timeout); const colorDiv = document.createElement("div");
color2Timeout = setTimeout(function () { colorDiv.className = "color-item";
const color = document.getElementById("color2").value;
sendWebSocketData({ color2: color }); const colorInput = document.createElement("input");
}, 500); colorInput.type = "color";
colorInput.className = "color-input";
colorInput.value = color;
colorInput.addEventListener("input", updateColors);
const removeBtn = document.createElement("button");
removeBtn.type = "button";
removeBtn.textContent = "×";
removeBtn.className = "remove-color-btn";
removeBtn.addEventListener("click", function() {
colorDiv.remove();
updateColors();
});
colorDiv.appendChild(colorInput);
colorDiv.appendChild(removeBtn);
container.appendChild(colorDiv);
}
function initializeColors(initialColors = null) {
const container = document.getElementById("colors_container");
container.innerHTML = "";
// Get initial colors from data attribute or use defaults
if (initialColors === null) {
const colorsData = document.getElementById("colors_container").dataset.colors;
if (colorsData) {
try {
initialColors = JSON.parse(colorsData);
} catch (e) {
initialColors = ["#ff0000", "#00ff00"];
}
} else {
initialColors = ["#ff0000", "#00ff00"];
}
}
if (initialColors.length === 0) {
initialColors = ["#ff0000"];
}
initialColors.forEach(color => addColorInput(color));
} }
async function updatePattern(pattern) { async function updatePattern(pattern) {
@@ -198,8 +235,11 @@ document.addEventListener("DOMContentLoaded", async function () {
// Establish WebSocket connection on page load // Establish WebSocket connection on page load
connectWebSocket(); connectWebSocket();
document.getElementById("color").addEventListener("input", updateColor); // Initialize colors palette
document.getElementById("color2").addEventListener("input", updateColor2); initializeColors();
document.getElementById("add_color_btn").addEventListener("click", function() {
addColorInput();
});
document.getElementById("delay").addEventListener("input", updateDelay); document.getElementById("delay").addEventListener("input", updateDelay);
document document
.getElementById("brightness") .getElementById("brightness")

View File

@@ -1,4 +1,4 @@
{% args settings, patterns, mac %} {% args settings, patterns, colors_json, mac %}
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -46,22 +46,13 @@
step="1" step="1"
/> />
</form> </form>
<form id="color_form" method="post" action="/color"> <div id="colors_palette">
<input <label>Colors:</label>
type="color" <div id="colors_container" data-colors='{{colors_json}}'>
id="color" <!-- Color inputs will be added here dynamically -->
name="color" </div>
value="{{settings['color1']}}" <button type="button" id="add_color_btn">+ Add Color</button>
/> </div>
</form>
<form id="color2_form" method="post" action="/color2">
<input
type="color"
id="color2"
name="color2"
value="{{settings['color2']}}"
/>
</form>
</div> </div>
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password --> <!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->

View File

@@ -12,7 +12,19 @@ def web(settings, patterns):
@app.route('/') @app.route('/')
async def index_hnadler(request): async def index_hnadler(request):
mac = wifi.get_mac().hex() mac = wifi.get_mac().hex()
return Template('index.html').render(settings=settings, patterns=patterns.patterns.keys()) # Convert colors from RGB tuples to hex strings for display
colors_hex = []
for color in patterns.colors:
# Convert (R, G, B) tuple to #RRGGBB hex string
colors_hex.append(f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}")
# Convert to JSON string for data attribute
colors_json = json.dumps(colors_hex)
return Template('index.html').render(
settings,
patterns.patterns.keys(),
colors_json,
mac
)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def static_handler(request, path): def static_handler(request, path):

View File

@@ -64,11 +64,9 @@ class LEDConfigTool:
settings_config = [ settings_config = [
("led_pin", "LED Pin", "number"), ("led_pin", "LED Pin", "number"),
("num_leds", "Number of LEDs", "number"), ("num_leds", "Number of LEDs", "number"),
("color_order", "Color Order", "choice", ["rgb", "rbg"]), ("color_order", "Color Order", "choice", ["rgb", "rbg", "grb", "gbr", "brg", "bgr"]),
("name", "Device Name", "text"), ("name", "Device Name", "text"),
("pattern", "Pattern", "text"), ("pattern", "Pattern", "text"),
("color1", "Color 1", "color"),
("color2", "Color 2", "color"),
("delay", "Delay (ms)", "number"), ("delay", "Delay (ms)", "number"),
("brightness", "Brightness", "number"), ("brightness", "Brightness", "number"),
("n1", "N1", "number"), ("n1", "N1", "number"),
@@ -79,6 +77,7 @@ class LEDConfigTool:
("n6", "N6", "number"), ("n6", "N6", "number"),
("ap_password", "AP Password", "text"), ("ap_password", "AP Password", "text"),
("id", "ID", "number"), ("id", "ID", "number"),
("debug", "Debug Mode", "choice", ["True", "False"]),
] ]
for idx, config in enumerate(settings_config): for idx, config in enumerate(settings_config):
@@ -295,7 +294,11 @@ class LEDConfigTool:
if key in self.settings: if key in self.settings:
value = self.settings[key] value = self.settings[key]
if isinstance(widget, ttk.Combobox): if isinstance(widget, ttk.Combobox):
widget.set(str(value)) # For debug, convert boolean to string
if key == "debug":
widget.set(str(value))
else:
widget.set(str(value))
else: else:
widget.delete(0, tk.END) widget.delete(0, tk.END)
widget.insert(0, str(value)) widget.insert(0, str(value))
@@ -311,6 +314,9 @@ class LEDConfigTool:
self.settings[key] = int(value) self.settings[key] = int(value)
except ValueError: except ValueError:
pass # Keep as string if conversion fails pass # Keep as string if conversion fails
elif key == "debug":
# Convert string "True"/"False" to boolean
self.settings[key] = value == "True"
else: else:
self.settings[key] = value self.settings[key] = value
elif key in self.settings: elif key in self.settings:

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