feat(patterns,api): pattern OTA, graceful shutdown, driver delivery updates

- Pattern controller/UI and presets patterns tab for OTA to Wi-Fi drivers
- Device controller extensions; driver_delivery chunk handling
- main: SIGINT/SIGTERM shutdown, TCP/UDP server close coordination
- Submodule led-driver: Wi-Fi default transport, lazy espnow import, dynamic patterns

Made-with: Cursor
This commit is contained in:
pi
2026-04-11 15:10:23 +12:00
parent 7179b6531e
commit e67de6215a
9 changed files with 1031 additions and 67 deletions

View File

@@ -18,6 +18,28 @@ def _split_serial_envelope(inner_json_str, peer_hex_list):
return json.dumps(env, separators=(",", ":"))
def _wifi_message_for_device(msg, device_name):
"""
For Wi-Fi TCP fanout, narrow a v1 select map to a single device name.
Returns the original message when no narrowing applies.
"""
if not device_name:
return msg
try:
body = json.loads(msg)
except Exception:
return msg
if not isinstance(body, dict):
return msg
select = body.get("select")
if not isinstance(select, dict):
return msg
if device_name not in select:
return msg
body["select"] = {device_name: select[device_name]}
return json.dumps(body, separators=(",", ":"))
async def deliver_preset_broadcast_then_per_device(
sender,
chunk_messages,
@@ -129,7 +151,9 @@ async def deliver_json_messages(sender, messages, target_macs, devices_model, de
if doc and doc.get("transport") == "wifi":
ip = doc.get("address")
if ip:
wifi_tasks.append(send_json_line_to_ip(ip, msg))
name = str(doc.get("name") or "").strip()
wifi_msg = _wifi_message_for_device(msg, name)
wifi_tasks.append(send_json_line_to_ip(ip, wifi_msg))
else:
espnow_hex.append(mac)