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:
+23
-9
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user