initial Commit
This commit is contained in:
78
backend/services/serial_service.py
Normal file
78
backend/services/serial_service.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user