feat(bridge): add wifi/serial bridge runtime and UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,7 +27,7 @@ from microdot.session import Session # noqa: E402
|
||||
from microdot.websocket import with_websocket # noqa: E402
|
||||
|
||||
|
||||
class DummySender:
|
||||
class DummyBridge:
|
||||
def __init__(self):
|
||||
self.sent: list[tuple[str, Optional[str]]] = []
|
||||
|
||||
@@ -166,7 +166,7 @@ def server(monkeypatch, tmp_path_factory):
|
||||
old_cwd = os.getcwd()
|
||||
os.chdir(str(SRC_PATH))
|
||||
|
||||
dummy_sender = DummySender()
|
||||
dummy_bridge = DummyBridge()
|
||||
|
||||
try:
|
||||
# Ensure controllers are imported fresh after our patching.
|
||||
@@ -196,10 +196,10 @@ def server(monkeypatch, tmp_path_factory):
|
||||
import controllers.settings as settings_ctl # noqa: E402
|
||||
import controllers.device as device_ctl # noqa: E402
|
||||
|
||||
# Configure transport sender used by /presets/send.
|
||||
from models.transport import set_sender # noqa: E402
|
||||
# Configure transport bridge used by /presets/send.
|
||||
from models.transport import set_bridge # noqa: E402
|
||||
|
||||
set_sender(dummy_sender)
|
||||
set_bridge(dummy_bridge)
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@@ -244,7 +244,7 @@ def server(monkeypatch, tmp_path_factory):
|
||||
@app.route("/ws")
|
||||
@with_websocket
|
||||
async def ws(request, ws):
|
||||
# Minimal websocket handler: forward raw JSON/text payloads to dummy sender.
|
||||
# Minimal websocket handler: forward raw JSON/text payloads to dummy bridge.
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
if not data:
|
||||
@@ -253,9 +253,9 @@ def server(monkeypatch, tmp_path_factory):
|
||||
parsed = json.loads(data)
|
||||
addr = parsed.pop("to", None)
|
||||
payload = json.dumps(parsed) if parsed else data
|
||||
await dummy_sender.send(payload, addr=addr)
|
||||
await dummy_bridge.send(payload, addr=addr)
|
||||
except Exception:
|
||||
await dummy_sender.send(data)
|
||||
await dummy_bridge.send(data)
|
||||
|
||||
thread, chosen_port = _start_microdot_server(app, host="127.0.0.1", port=0)
|
||||
base_url = f"http://127.0.0.1:{chosen_port}"
|
||||
@@ -271,7 +271,7 @@ def server(monkeypatch, tmp_path_factory):
|
||||
yield {
|
||||
"base_url": base_url,
|
||||
"client": client,
|
||||
"sender": dummy_sender,
|
||||
"bridge": dummy_bridge,
|
||||
"thread": thread,
|
||||
"app": app,
|
||||
}
|
||||
@@ -379,7 +379,7 @@ def test_settings_controller(server):
|
||||
def test_profiles_presets_zones_endpoints(server, monkeypatch):
|
||||
c: requests.Session = server["client"]
|
||||
base_url: str = server["base_url"]
|
||||
sender: DummySender = server["sender"]
|
||||
bridge: DummyBridge = server["bridge"]
|
||||
|
||||
import controllers.device as device_ctl
|
||||
|
||||
@@ -436,7 +436,7 @@ def test_profiles_presets_zones_endpoints(server, monkeypatch):
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["brightness"] == 77
|
||||
|
||||
sender.sent.clear()
|
||||
bridge.sent.clear()
|
||||
resp = c.post(
|
||||
f"{base_url}/presets/send",
|
||||
json={"preset_ids": [new_preset_id], "save": False},
|
||||
@@ -444,7 +444,7 @@ def test_profiles_presets_zones_endpoints(server, monkeypatch):
|
||||
assert resp.status_code == 200
|
||||
sent_result = resp.json()
|
||||
assert sent_result["presets_sent"] >= 1
|
||||
assert len(sender.sent) >= 1
|
||||
assert len(bridge.sent) >= 1
|
||||
|
||||
resp = c.delete(f"{base_url}/presets/{new_preset_id}")
|
||||
assert resp.status_code == 200
|
||||
@@ -555,7 +555,7 @@ def test_profiles_presets_zones_endpoints(server, monkeypatch):
|
||||
def test_groups_sequences_scenes_palettes_patterns_endpoints(server):
|
||||
c: requests.Session = server["client"]
|
||||
base_url: str = server["base_url"]
|
||||
sender: DummySender = server["sender"]
|
||||
bridge: DummyBridge = server["bridge"]
|
||||
|
||||
_create_and_apply_profile(c, base_url)
|
||||
|
||||
@@ -667,21 +667,21 @@ def test_groups_sequences_scenes_palettes_patterns_endpoints(server):
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()[dev_id].get("connected") is None
|
||||
|
||||
sender.sent.clear()
|
||||
bridge.sent.clear()
|
||||
resp = c.post(f"{base_url}/devices/{dev_id}/identify")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json().get("message")
|
||||
assert len(sender.sent) >= 1
|
||||
first = json.loads(sender.sent[0][0])
|
||||
assert len(bridge.sent) >= 1
|
||||
first = json.loads(bridge.sent[0][0])
|
||||
assert "presets" in first and "select" in first
|
||||
assert first["presets"]["__identify"]["p"] == "blink"
|
||||
assert first["presets"]["__identify"]["d"] == 50
|
||||
assert first["select"] == ["__identify"]
|
||||
deadline = time.monotonic() + 2.0
|
||||
while len(sender.sent) < 2 and time.monotonic() < deadline:
|
||||
while len(bridge.sent) < 2 and time.monotonic() < deadline:
|
||||
time.sleep(0.02)
|
||||
assert len(sender.sent) >= 2
|
||||
second = json.loads(sender.sent[1][0])
|
||||
assert len(bridge.sent) >= 2
|
||||
second = json.loads(bridge.sent[1][0])
|
||||
assert second.get("select") == ["off"]
|
||||
|
||||
resp = c.post(
|
||||
|
||||
Reference in New Issue
Block a user