59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
"""Continuos serial frame reader (Live-mode source)"""
|
|
from __future__ import annotations
|
|
import logging
|
|
import threading
|
|
from typing import Callable
|
|
|
|
from pygui.backend.models.frame import Frame
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
ParseLine = Callable[[str, int], Frame]
|
|
|
|
class StreamReader:
|
|
|
|
def __init__(self, transport, parse_line: ParseLine,
|
|
on_frame: Callable[[Frame], None],
|
|
on_error: Callable[[Exception], None] | None = None ) -> None:
|
|
self._transport = transport
|
|
self._parse = parse_line
|
|
self._on_frame = on_frame
|
|
self._on_error = on_error or (lambda e: None)
|
|
self._thread: threading.Thread | None = None
|
|
self._stop = threading.Event()
|
|
self._seq = 0
|
|
self.error_count = 0
|
|
|
|
def start(self) -> None:
|
|
self._stop.clear()
|
|
self._thread = threading.Thread(target=self._run, daemon = True)
|
|
self._thread.start()
|
|
|
|
def _run(self) -> None:
|
|
try:
|
|
while not self._stop.is_set():
|
|
raw = self._transport.readline()
|
|
if not raw:
|
|
continue
|
|
text = raw.decode(errors="ignore").strip()
|
|
if not text:
|
|
continue
|
|
try:
|
|
frame= self._parse(text, self._seq)
|
|
self._seq += 1
|
|
self._on_frame(frame)
|
|
except Exception as exc:
|
|
self.error_count += 1
|
|
self._on_error(exc)
|
|
finally:
|
|
try:
|
|
self._transport.close()
|
|
except Exception:
|
|
pass
|
|
|
|
def stop(self) -> None:
|
|
self._stop.set()
|
|
if self._thread is not None and self._thread.is_alive():
|
|
self._thread.join(timeout = 2.0)
|
|
self._thread = None
|