initial Commit

This commit is contained in:
2026-05-10 12:46:33 +02:00
commit 108f08645c
36 changed files with 8688 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
from __future__ import annotations
import asyncio
from typing import Any
import serial
import serial.tools.list_ports
from backend.services.pico import parse_pico_line
DEFAULT_BAUD = 115200
def find_pico_port() -> str | None:
for port in serial.tools.list_ports.comports():
vid = port.vid
manufacturer = (port.manufacturer or "").lower()
description = (port.description or "").lower()
if vid == 0x2E8A or "pico" in description or "raspberry" in manufacturer:
return port.device
return None
class SerialService:
def __init__(self, app: Any):
self.app = app
self.task: asyncio.Task[None] | None = None
self.stop_event = asyncio.Event()
self.connected_port: str | None = None
def start(self) -> None:
if self.task is None or self.task.done():
self.stop_event.clear()
self.task = asyncio.create_task(self._run())
async def stop(self) -> None:
self.stop_event.set()
if self.task:
await asyncio.wait([self.task], timeout=2)
async def restart(self) -> None:
await self.stop()
self.task = None
self.start()
async def _run(self) -> None:
while not self.stop_event.is_set():
port = self.app.db.get_setting("serial_port") or find_pico_port()
if not port:
self.connected_port = None
await self.app.ws.broadcast("serial.disconnected", {"reason": "Pico not found"})
await asyncio.sleep(2)
continue
try:
await self._read_port(port)
except Exception as exc:
self.connected_port = None
await self.app.ws.broadcast("serial.disconnected", {"port": port, "error": str(exc)})
await asyncio.sleep(2)
async def _read_port(self, port: str) -> None:
with serial.Serial(port, DEFAULT_BAUD, timeout=1) as ser:
self.connected_port = port
await self.app.ws.broadcast("serial.connected", {"port": port, "baud": DEFAULT_BAUD})
while not self.stop_event.is_set():
raw = await asyncio.to_thread(ser.readline)
line = raw.decode("utf-8", errors="replace").strip()
if not line:
continue
event = parse_pico_line(line)
if event is None:
await self.app.ws.broadcast("serial.diagnostic", {"line": line})
continue
payload = event.to_dict()
self.app.db.add_event(f"button.{payload['event']}", payload)
await self.app.actions.handle_button_event(payload)