fix(core): SerialConsole.read через in_waiting (не select) — как minicom

pyserial select-based read(4096) на RPi+PL2303 кидает 'readiness to read but no data'
и теряет байты в потоке (эхо ввода превращалось в кракозябры). Чтение по in_waiting
собирает доступные байты и отдаёт, когда поток утих — стабильно как minicom.
This commit is contained in:
toros
2026-09-08 23:52:21 +05:00
parent 6ad9df81e6
commit 93b4be9574
+23 -9
View File
@@ -9,6 +9,7 @@
""" """
import os import os
import subprocess import subprocess
import time
DRIVER = os.environ.get("ATXPI_DRIVER", "real") DRIVER = os.environ.get("ATXPI_DRIVER", "real")
@@ -77,13 +78,20 @@ class ConsoleBackend:
class SerialConsole(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: def __init__(self, port: str = SERIAL_PORT, baud: int = SERIAL_BAUD) -> None:
self.port, self.baud = port, baud self.port, self.baud = port, baud
self._ser = None self._ser = None
def open(self) -> None: def open(self) -> None:
import serial # noqa: требуется только для реального железа 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: def close(self) -> None:
if self._ser: if self._ser:
self._ser.close() self._ser.close()
@@ -93,15 +101,21 @@ class SerialConsole(ConsoleBackend):
raise RuntimeError("консоль не открыта") raise RuntimeError("консоль не открыта")
self._ser.write(data) self._ser.write(data)
def read(self, timeout: float = 0.5) -> bytes: def read(self, timeout: float = 0.5) -> bytes:
"""Собрать доступные байты за `timeout`, отдать, когда поток утих."""
if not self._ser: if not self._ser:
raise RuntimeError("консоль не открыта") raise RuntimeError("консоль не открыта")
self._ser.timeout = timeout collected = b""
try: end = time.time() + timeout
return self._ser.read(4096) while time.time() < end:
except Exception: waiting = self._ser.in_waiting
# Raspberry Pi + USB-serial: на простоях порт может сообщать if waiting > 0:
# "readiness to read but no data" — это не фатально, читаем дальше. collected += self._ser.read(waiting)
return b"" time.sleep(0.015) # собрать межбайтовые хвосты
else:
time.sleep(0.015)
if collected:
break # поток данных утих — отдаём как есть
return collected
def make_power() -> PowerBackend: def make_power() -> PowerBackend: