diff --git a/atxpi/core/hardware.py b/atxpi/core/hardware.py index bb6cd41..bb0c777 100644 --- a/atxpi/core/hardware.py +++ b/atxpi/core/hardware.py @@ -9,6 +9,7 @@ """ import os import subprocess +import time DRIVER = os.environ.get("ATXPI_DRIVER", "real") @@ -77,13 +78,20 @@ class ConsoleBackend: class SerialConsole(ConsoleBackend): - """Реальный драйвер консоли — pyserial (ленивый импорт).""" + """Реальный драйвер консоли — pyserial (ленивый импорт). + + Чтение через `in_waiting` (без select-based read(4096)), т.к. pyserial-чтение + с select'ом на Raspberry Pi + PL2303 кидает 'readiness to read but no data' + и теряет байты в потоке. Так читает и minicom. + """ def __init__(self, port: str = SERIAL_PORT, baud: int = SERIAL_BAUD) -> None: self.port, self.baud = port, baud self._ser = None def open(self) -> None: import serial # noqa: требуется только для реального железа - self._ser = serial.Serial(self.port, self.baud, timeout=0) + self._ser = serial.Serial( + self.port, self.baud, timeout=0.05, write_timeout=2, exclusive=True, + ) def close(self) -> None: if self._ser: self._ser.close() @@ -93,15 +101,21 @@ class SerialConsole(ConsoleBackend): raise RuntimeError("консоль не открыта") self._ser.write(data) def read(self, timeout: float = 0.5) -> bytes: + """Собрать доступные байты за `timeout`, отдать, когда поток утих.""" if not self._ser: raise RuntimeError("консоль не открыта") - self._ser.timeout = timeout - try: - return self._ser.read(4096) - except Exception: - # Raspberry Pi + USB-serial: на простоях порт может сообщать - # "readiness to read but no data" — это не фатально, читаем дальше. - return b"" + collected = b"" + end = time.time() + timeout + while time.time() < end: + waiting = self._ser.in_waiting + if waiting > 0: + collected += self._ser.read(waiting) + time.sleep(0.015) # собрать межбайтовые хвосты + else: + time.sleep(0.015) + if collected: + break # поток данных утих — отдаём как есть + return collected def make_power() -> PowerBackend: