feat(bridge): add wifi/serial bridge runtime and UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"""Transport to LED drivers via ESP-NOW bridge WebSocket."""
|
||||
"""Transport to LED drivers via ESP-NOW bridge WebSocket or USB serial."""
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from models.bridge_serial_client import get_bridge_serial_client
|
||||
from models.bridge_ws_client import get_bridge_client
|
||||
from util.bridge_envelope import (
|
||||
BROADCAST_HEX,
|
||||
@@ -15,14 +16,14 @@ from util.bridge_envelope import (
|
||||
from util.espnow_wire import WIRE_MAGIC
|
||||
|
||||
|
||||
class NullSender:
|
||||
class NullBridge:
|
||||
"""No bridge configured."""
|
||||
|
||||
async def send(self, data, addr=None):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class BridgeWsSender:
|
||||
class BridgeWsTransport:
|
||||
"""Send v1 JSON or devices envelope via bridge WebSocket."""
|
||||
|
||||
async def send(self, data: Union[bytes, str, Dict[str, Any]], addr=None) -> bool:
|
||||
@@ -73,6 +74,57 @@ class BridgeWsSender:
|
||||
return await client.send_packet(envelope)
|
||||
|
||||
|
||||
class BridgeSerialTransport:
|
||||
"""Send v1 JSON or devices envelope via bridge USB/serial."""
|
||||
|
||||
async def send(self, data: Union[bytes, str, Dict[str, Any]], addr=None) -> bool:
|
||||
client = get_bridge_serial_client()
|
||||
if client is None:
|
||||
return False
|
||||
|
||||
if isinstance(data, dict):
|
||||
if data.get("v") == "1" and ("devices" in data or "dv" in data):
|
||||
from util.v1_wire import compact_envelope
|
||||
|
||||
return await client.send_packet(compact_envelope(data))
|
||||
packet = json.dumps(data, separators=(",", ":")).encode("utf-8")
|
||||
elif isinstance(data, str):
|
||||
packet = data.encode("utf-8")
|
||||
elif isinstance(data, (bytes, bytearray)):
|
||||
packet = bytes(data)
|
||||
else:
|
||||
return False
|
||||
|
||||
if not packet:
|
||||
return False
|
||||
|
||||
if packet[0] == WIRE_MAGIC:
|
||||
return await client.send_packet(packet)
|
||||
|
||||
if packet[0:1] != b"{":
|
||||
return False
|
||||
|
||||
mac_key = _addr_to_envelope_key(addr)
|
||||
if mac_key is None:
|
||||
return await client.send_packet(packet)
|
||||
|
||||
try:
|
||||
body = json.loads(packet.decode("utf-8"))
|
||||
except (UnicodeError, ValueError, TypeError):
|
||||
return False
|
||||
if not isinstance(body, dict) or body.get("v") != "1":
|
||||
return False
|
||||
|
||||
envelope = build_devices_envelope({mac_key: body})
|
||||
return await client.send_packet(envelope)
|
||||
|
||||
async def send_envelope(self, envelope: Dict[str, Any]) -> bool:
|
||||
client = get_bridge_serial_client()
|
||||
if client is None:
|
||||
return False
|
||||
return await client.send_packet(envelope)
|
||||
|
||||
|
||||
def _addr_to_envelope_key(addr) -> Optional[str]:
|
||||
if addr is None:
|
||||
return BROADCAST_MAC
|
||||
@@ -88,24 +140,32 @@ def _addr_to_envelope_key(addr) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
_current_sender = None
|
||||
_current_bridge = None
|
||||
|
||||
|
||||
def set_sender(sender):
|
||||
global _current_sender
|
||||
_current_sender = sender
|
||||
def set_bridge(bridge):
|
||||
global _current_bridge
|
||||
_current_bridge = bridge
|
||||
|
||||
|
||||
def get_current_sender():
|
||||
return _current_sender
|
||||
def get_current_bridge():
|
||||
return _current_bridge
|
||||
|
||||
|
||||
def get_sender(settings):
|
||||
url = str(settings.get("bridge_ws_url") or "").strip()
|
||||
if not url:
|
||||
def get_bridge(settings):
|
||||
mode = str(settings.get("bridge_transport") or "wifi").strip().lower()
|
||||
if mode == "wifi":
|
||||
url = str(settings.get("bridge_ws_url") or "").strip()
|
||||
if not url:
|
||||
print("[startup] bridge Wi‑Fi disabled (set bridge_ws_url in settings.json)")
|
||||
return NullBridge()
|
||||
print(f"[startup] ESP-NOW via bridge WebSocket {url!r}")
|
||||
return BridgeWsTransport()
|
||||
port = str(settings.get("bridge_serial_port") or "").strip()
|
||||
if not port:
|
||||
print(
|
||||
"[startup] bridge disabled (set bridge_ws_url in settings.json, e.g. ws://192.168.4.1/ws)"
|
||||
"[startup] bridge serial disabled (set bridge_serial_port in settings.json)"
|
||||
)
|
||||
return NullSender()
|
||||
print(f"[startup] ESP-NOW via bridge WebSocket {url!r} (devices envelope)")
|
||||
return BridgeWsSender()
|
||||
return NullBridge()
|
||||
print(f"[startup] ESP-NOW via bridge USB serial {port!r}")
|
||||
return BridgeSerialTransport()
|
||||
|
||||
Reference in New Issue
Block a user