refactor: implement thread-safe hardware operations, port caching, and refined state management in the device controller
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user