refactor: implement thread-safe hardware operations, port caching, and refined state management in the device controller

This commit is contained in:
jack
2026-07-02 22:33:44 -07:00
parent da43aeb1f6
commit 9e28a25695
7 changed files with 193 additions and 68 deletions
+1 -1
View File
@@ -239,7 +239,7 @@ def remove_trailing_zeros(data: list[list[str]]) -> None:
fval = float(val)
except ValueError:
fval = 99.9
if fval >= 0.01:
if abs(fval) >= 0.01:
is_all_zero = False
break
if is_all_zero:
+13
View File
@@ -29,6 +29,12 @@ class DeviceService:
def __init__(self, settings: LocalSettings) -> None:
self._settings = settings
self._cached_ports: list[str] = []
self._last_port_scan_time = 0.0
def clear_port_scan_cache(self) -> None:
"""Force the next scan to bypass the cache."""
self._last_port_scan_time = 0.0
# ------------------------------------------------------------------
# Public API
@@ -36,6 +42,11 @@ class DeviceService:
def enumerate_ports(self) -> list[str]:
"""Return list of available serial port names."""
import time
now = time.monotonic()
if now - self._last_port_scan_time < 2.0:
return self._cached_ports
hardware_ports = [p.device for p in serial.tools.list_ports.comports()]
virtual_ports = []
@@ -86,6 +97,8 @@ class DeviceService:
if p not in ordered_ports:
ordered_ports.append(p)
self._cached_ports = ordered_ports
self._last_port_scan_time = time.monotonic()
return ordered_ports
+11 -9
View File
@@ -36,17 +36,13 @@ class StreamReader:
def _read_chunk(self, min_bytes: int = 1) -> bytes:
"""Read a chunk of available bytes from the transport.
Reads up to 256 bytes in one shot instead of byte-at-a-time,
falling back to single-byte reads with a yield when nothing is
immediately available."""
Reads up to 256 bytes in one shot. If nothing is immediately
available, yields briefly to avoid a busy spin.
"""
chunk = self._transport.read(max(min_bytes, 256))
if chunk:
return chunk
# Nothing available right now yield briefly to avoid busy-spin.
b = self._transport.read(1)
if not b:
if not chunk:
time.sleep(0.005)
return b
return chunk
def _accumulate(self, needed: int) -> bytearray:
"""Accumulate *needed* bytes using buffered reads."""
@@ -92,6 +88,12 @@ class StreamReader:
# Default: ASCII hex dump stream.
self._run_ascii(raw)
except Exception as exc:
log.exception("StreamReader thread encountered error: %s", exc)
try:
self._on_error(exc)
except Exception:
pass
finally:
try:
self._transport.close()