Now tty parser is faster a lot. Data is processed online
This commit is contained in:
@ -50,9 +50,14 @@ class FDReader:
|
|||||||
|
|
||||||
def __init__(self, fd: int):
|
def __init__(self, fd: int):
|
||||||
# Отдельно буферизуем для корректной readline()
|
# Отдельно буферизуем для корректной readline()
|
||||||
|
self._fd = fd
|
||||||
raw = os.fdopen(fd, "rb", closefd=False)
|
raw = os.fdopen(fd, "rb", closefd=False)
|
||||||
|
self._file = raw
|
||||||
self._buf = io.BufferedReader(raw, buffer_size=65536)
|
self._buf = io.BufferedReader(raw, buffer_size=65536)
|
||||||
|
|
||||||
|
def fileno(self) -> int:
|
||||||
|
return self._fd
|
||||||
|
|
||||||
def readline(self) -> bytes:
|
def readline(self) -> bytes:
|
||||||
return self._buf.readline()
|
return self._buf.readline()
|
||||||
|
|
||||||
@ -152,6 +157,60 @@ class SerialLineSource:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SerialChunkReader:
|
||||||
|
"""Быстрое неблокирующее чтение чанков из serial/raw TTY для максимального дренажа буфера."""
|
||||||
|
|
||||||
|
def __init__(self, src: SerialLineSource):
|
||||||
|
self._src = src
|
||||||
|
self._ser = src._pyserial
|
||||||
|
self._fd: Optional[int] = None
|
||||||
|
if self._ser is not None:
|
||||||
|
# Неблокирующий режим для быстрой откачки
|
||||||
|
try:
|
||||||
|
self._ser.timeout = 0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self._fd = src._fdreader.fileno() # type: ignore[union-attr]
|
||||||
|
try:
|
||||||
|
os.set_blocking(self._fd, False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
self._fd = None
|
||||||
|
|
||||||
|
def read_available(self) -> bytes:
|
||||||
|
"""Вернёт доступные байты (b"" если данных нет)."""
|
||||||
|
if self._ser is not None:
|
||||||
|
try:
|
||||||
|
n = int(getattr(self._ser, "in_waiting", 0))
|
||||||
|
except Exception:
|
||||||
|
n = 0
|
||||||
|
if n > 0:
|
||||||
|
try:
|
||||||
|
return self._ser.read(n)
|
||||||
|
except Exception:
|
||||||
|
return b""
|
||||||
|
return b""
|
||||||
|
if self._fd is None:
|
||||||
|
return b""
|
||||||
|
out = bytearray()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
chunk = os.read(self._fd, 65536)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
out += chunk
|
||||||
|
if len(chunk) < 65536:
|
||||||
|
break
|
||||||
|
except BlockingIOError:
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
class SweepReader(threading.Thread):
|
class SweepReader(threading.Thread):
|
||||||
"""Фоновый поток: читает строки, формирует завершённые свипы и кладёт в очередь."""
|
"""Фоновый поток: читает строки, формирует завершённые свипы и кладёт в очередь."""
|
||||||
|
|
||||||
@ -166,16 +225,19 @@ class SweepReader(threading.Thread):
|
|||||||
def _finalize_current(self, xs, ys):
|
def _finalize_current(self, xs, ys):
|
||||||
if not xs:
|
if not xs:
|
||||||
return
|
return
|
||||||
try:
|
max_x = max(xs)
|
||||||
max_x = max(xs)
|
|
||||||
except ValueError:
|
|
||||||
return
|
|
||||||
width = max_x + 1
|
width = max_x + 1
|
||||||
|
# Быстрый векторизованный путь
|
||||||
sweep = np.full((width,), np.nan, dtype=np.float32)
|
sweep = np.full((width,), np.nan, dtype=np.float32)
|
||||||
# Заполнение известными точками
|
try:
|
||||||
for x, y in zip(xs, ys):
|
idx = np.asarray(xs, dtype=np.int64)
|
||||||
if 0 <= x < width:
|
vals = np.asarray(ys, dtype=np.float32)
|
||||||
sweep[x] = float(y)
|
sweep[idx] = vals
|
||||||
|
except Exception:
|
||||||
|
# Запасной путь
|
||||||
|
for x, y in zip(xs, ys):
|
||||||
|
if 0 <= x < width:
|
||||||
|
sweep[x] = float(y)
|
||||||
# Кладём готовый свип (если очередь полна — выбрасываем самый старый)
|
# Кладём готовый свип (если очередь полна — выбрасываем самый старый)
|
||||||
try:
|
try:
|
||||||
self._q.put_nowait(sweep)
|
self._q.put_nowait(sweep)
|
||||||
@ -202,38 +264,59 @@ class SweepReader(threading.Thread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Быстрый неблокирующий дренаж порта с разбором по байтам
|
||||||
|
chunk_reader = SerialChunkReader(self._src)
|
||||||
|
buf = bytearray()
|
||||||
while not self._stop.is_set():
|
while not self._stop.is_set():
|
||||||
raw = self._src.readline()
|
data = chunk_reader.read_available()
|
||||||
if not raw:
|
if data:
|
||||||
# timeout/ошибка/EOF — небольшой сон, чтобы не крутить CPU
|
buf += data
|
||||||
time.sleep(0.001)
|
else:
|
||||||
continue
|
# Короткая уступка CPU, если нет новых данных
|
||||||
try:
|
time.sleep(0.0005)
|
||||||
line = raw.decode("ascii", errors="ignore").strip()
|
|
||||||
except Exception:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not line:
|
# Обрабатываем все полные строки
|
||||||
continue
|
while True:
|
||||||
|
nl = buf.find(b"\n")
|
||||||
if line.startswith("Sweep_start"):
|
if nl == -1:
|
||||||
# Завершаем предыдущий, начинаем новый
|
break
|
||||||
self._finalize_current(xs, ys)
|
line = bytes(buf[:nl])
|
||||||
xs.clear()
|
del buf[: nl + 1]
|
||||||
ys.clear()
|
if line.endswith(b"\r"):
|
||||||
continue
|
line = line[:-1]
|
||||||
|
if not line:
|
||||||
# stp X Y
|
|
||||||
# Разрешим как с пробелами, так и табами
|
|
||||||
parts = line.split()
|
|
||||||
if len(parts) >= 3 and parts[0].lower() == "stp":
|
|
||||||
try:
|
|
||||||
x = int(parts[1], 10)
|
|
||||||
y = int(parts[2], 10)
|
|
||||||
except Exception:
|
|
||||||
continue
|
continue
|
||||||
xs.append(x)
|
|
||||||
ys.append(y)
|
if line.startswith(b"Sweep_start"):
|
||||||
|
self._finalize_current(xs, ys)
|
||||||
|
xs.clear()
|
||||||
|
ys.clear()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# stp X Y
|
||||||
|
if len(line) >= 5 and (line[:3] == b"stp" or line[:3] == b"STP"):
|
||||||
|
sp1 = line.find(b" ", 3)
|
||||||
|
if sp1 == -1:
|
||||||
|
sp1 = line.find(b"\t", 3)
|
||||||
|
if sp1 == -1:
|
||||||
|
continue
|
||||||
|
sp2 = line.find(b" ", sp1 + 1)
|
||||||
|
if sp2 == -1:
|
||||||
|
sp2 = line.find(b"\t", sp1 + 1)
|
||||||
|
if sp2 == -1:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
x = int(line[sp1 + 1 : sp2])
|
||||||
|
y = int(line[sp2 + 1 :])
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
xs.append(x)
|
||||||
|
ys.append(y)
|
||||||
|
|
||||||
|
# Защита от переполнения буфера при отсутствии переводов строки
|
||||||
|
if len(buf) > 1_000_000:
|
||||||
|
del buf[:-262144]
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
# Завершаем оставшийся свип
|
# Завершаем оставшийся свип
|
||||||
|
|||||||
Reference in New Issue
Block a user