feat(patterns): align manual and auto behaviour
Unify manual/auto timing semantics for key patterns, add preset background support, and improve runtime observability while keeping the driver responsive under beat-triggered selects. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
42
src/main.py
42
src/main.py
@@ -15,30 +15,27 @@ try:
|
||||
except ImportError:
|
||||
import os
|
||||
|
||||
wdt = machine.WDT(timeout=10000)
|
||||
wdt.feed()
|
||||
|
||||
machine.freq(160000000)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
print(settings)
|
||||
|
||||
wdt = machine.WDT(timeout=10000)
|
||||
wdt.feed()
|
||||
|
||||
|
||||
gc.collect()
|
||||
print("mem before presets:", {"free": gc.mem_free(), "alloc": gc.mem_alloc()})
|
||||
|
||||
presets = Presets(settings["led_pin"], settings["num_leds"])
|
||||
presets.load(settings)
|
||||
presets.b = settings.get("brightness", 255)
|
||||
presets.debug = bool(settings.get("debug", False))
|
||||
gc.collect()
|
||||
print("mem after presets:", {"free": gc.mem_free(), "alloc": gc.mem_alloc()})
|
||||
|
||||
default_preset = settings.get("default", "")
|
||||
if default_preset and default_preset in presets.presets:
|
||||
if presets.select(default_preset):
|
||||
print(f"Selected startup preset: {default_preset}")
|
||||
else:
|
||||
if not presets.select(default_preset):
|
||||
print("Startup preset failed (invalid pattern?):", default_preset)
|
||||
|
||||
# On ESP32-C3, soft reboots can leave Wi-Fi driver state allocated.
|
||||
@@ -54,11 +51,22 @@ sta_if.active(True)
|
||||
sta_if.config(pm=network.WLAN.PM_NONE)
|
||||
sta_if.connect(settings["ssid"], settings["password"])
|
||||
while not sta_if.isconnected():
|
||||
print("Connecting")
|
||||
print("Waiting for network connection...")
|
||||
utime.sleep(1)
|
||||
wdt.feed()
|
||||
|
||||
print(sta_if.ifconfig())
|
||||
|
||||
def _print_network_ips(controller_ip=None):
|
||||
"""Always log STA address and led-controller (WS client) address when known."""
|
||||
try:
|
||||
led_ip = sta_if.ifconfig()[0]
|
||||
except Exception:
|
||||
led_ip = "?"
|
||||
ctrl = controller_ip if controller_ip else "(not connected)"
|
||||
print("led-driver IP:", led_ip, " led-controller IP:", ctrl)
|
||||
|
||||
|
||||
_print_network_ips()
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@@ -76,7 +84,6 @@ def _safe_pattern_filename(name):
|
||||
@app.route("/ws")
|
||||
@with_websocket
|
||||
async def ws_handler(request, ws):
|
||||
print("WS client connected")
|
||||
controller_ip = None
|
||||
try:
|
||||
client_addr = getattr(request, "client_addr", None)
|
||||
@@ -86,15 +93,12 @@ async def ws_handler(request, ws):
|
||||
controller_ip = client_addr
|
||||
except Exception:
|
||||
controller_ip = None
|
||||
print("WS controller_ip:", controller_ip)
|
||||
_print_network_ips(controller_ip)
|
||||
try:
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
if not data:
|
||||
print("WS client disconnected (closed)")
|
||||
break
|
||||
print("WS recv bytes:", len(data) if isinstance(data, (bytes, bytearray)) else len(str(data)))
|
||||
print(data)
|
||||
process_data(data, settings, presets, controller_ip=controller_ip)
|
||||
except WebSocketError as e:
|
||||
print("WS client disconnected:", e)
|
||||
@@ -108,7 +112,6 @@ async def upload_pattern(request):
|
||||
raw_name = request.args.get("name")
|
||||
reload_raw = request.args.get("reload", "1")
|
||||
reload_patterns = str(reload_raw).strip().lower() not in ("0", "false", "no", "off")
|
||||
print("patterns/upload request:", {"name": raw_name, "reload": reload_patterns})
|
||||
|
||||
if not isinstance(raw_name, str) or not raw_name.strip():
|
||||
return json.dumps({"error": "name is required"}), 400, {
|
||||
@@ -116,15 +119,12 @@ async def upload_pattern(request):
|
||||
}
|
||||
body = request.body
|
||||
if not isinstance(body, (bytes, bytearray)) or not body:
|
||||
print("patterns/upload rejected: empty body")
|
||||
return json.dumps({"error": "code is required"}), 400, {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
print("patterns/upload body_bytes:", len(body))
|
||||
try:
|
||||
code = body.decode("utf-8")
|
||||
except UnicodeError:
|
||||
print("patterns/upload rejected: body not utf-8")
|
||||
return json.dumps({"error": "body must be utf-8 text"}), 400, {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
@@ -148,18 +148,15 @@ async def upload_pattern(request):
|
||||
|
||||
path = "patterns/" + name
|
||||
try:
|
||||
print("patterns/upload writing:", path)
|
||||
with open(path, "w") as f:
|
||||
f.write(code)
|
||||
if reload_patterns:
|
||||
print("patterns/upload reloading patterns")
|
||||
presets.reload_patterns()
|
||||
except OSError as e:
|
||||
print("patterns/upload failed:", e)
|
||||
return json.dumps({"error": str(e)}), 500, {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
print("patterns/upload success:", {"name": name, "reloaded": reload_patterns})
|
||||
|
||||
return json.dumps({
|
||||
"message": "pattern uploaded",
|
||||
@@ -179,7 +176,6 @@ async def presets_loop():
|
||||
async def _udp_hello_after_http_ready():
|
||||
"""Hello must run after the HTTP server binds, or discovery clients time out on /ws."""
|
||||
await asyncio.sleep(1)
|
||||
print("UDP hello: broadcasting…")
|
||||
try:
|
||||
broadcast_hello_udp(
|
||||
sta_if,
|
||||
|
||||
Reference in New Issue
Block a user