diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ec0ee21 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/home/awe/Documents/radar_system/data_acq_and_processing/gpr_cpp" +} \ No newline at end of file diff --git a/device_firmware/cart_firmware/.vscode/extensions.json b/device_firmware/cart_firmware/.vscode/extensions.json index 080e70d..a91ca8f 100644 --- a/device_firmware/cart_firmware/.vscode/extensions.json +++ b/device_firmware/cart_firmware/.vscode/extensions.json @@ -5,6 +5,7 @@ "platformio.platformio-ide" ], "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" + "ms-vscode.cpptools-extension-pack", + "pioarduino.pioarduino-ide" ] } diff --git a/device_firmware/cart_firmware/tools/bridge.py b/device_firmware/cart_firmware/tools/bridge.py index 00e6f66..6e5e959 100644 --- a/device_firmware/cart_firmware/tools/bridge.py +++ b/device_firmware/cart_firmware/tools/bridge.py @@ -21,6 +21,7 @@ handshake-кадром IDENT (роль + версия протокола). Дл import argparse import hmac import logging +import select import socket import sys import time @@ -128,7 +129,13 @@ def run_tx(args, key): log.info("tx: %s -> UDP %s:%d", ser.port, *peer) sent = 0 while True: - for ftype, payload in parser.feed(ser.read(256)): + # read(1) блокирует до первого байта, затем забираем всё доступное — + # кадр уходит в сеть сразу, без ожидания таймаута чтения (иначе каждый + # кадр задерживался бы до таймаута порта). + data = ser.read(1) + if ser.in_waiting: + data += ser.read(ser.in_waiting) + for ftype, payload in parser.feed(data): if ftype == lp.CONTROL: sock.sendto(seal(payload, key), peer) sent += 1 @@ -149,7 +156,7 @@ def run_rx(args, key): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(bind) - sock.settimeout(lp.FRAME_PERIOD_MS / 1000.0) + sock.setblocking(False) parser = lp.FrameParser() log.info("rx: UDP %s:%d -> %s", bind[0], bind[1], ser.port) @@ -160,37 +167,55 @@ def run_rx(args, key): in_failsafe = False rejects = 0 # подряд отброшенных по seq -> признак перезапуска источника + def accept(datagram, addr): + """Проверить пакет и обновить состояние; вернуть payload принятого + свежего кадра либо None.""" + nonlocal last_seq, last_net, in_failsafe, rejects + if allowed_host and addr[0] != allowed_host: + log.warning("пакет с неожиданного адреса %s отброшен", addr[0]) + return None + payload = unseal(datagram, key) + if not payload or len(payload) != lp.CONTROL_LEN: + log.warning("битый/неаутентичный пакет отброшен") + return None + c = lp.unpack_control(payload) + # Принять свежий seq ИЛИ новую сессию: источник (STM-TX) после ребута + # начинает seq заново с 0, и без сброса last_seq поток завис бы в + # failsafe навсегда. Новую сессию распознаём по пачке подряд «несвежих» + # пакетов (устойчиво к одиночным reorder/dup). + fresh = last_seq is None or seq_newer(c["seq"], last_seq) + if not fresh: + rejects += 1 + if rejects >= SEQ_RESET_AFTER: + log.warning("seq откатился (%d -> %d): новая сессия", last_seq, c["seq"]) + fresh = True + if not fresh: + return None # устаревший/повторный — anti-replay + rejects = 0 + last_seq = c["seq"] + last_net = time.monotonic() + if in_failsafe: + log.info("связь восстановлена") + in_failsafe = False + return payload + while True: - try: - datagram, addr = sock.recvfrom(2048) - if allowed_host and addr[0] != allowed_host: - log.warning("пакет с неожиданного адреса %s отброшен", addr[0]) - datagram = None - payload = unseal(datagram, key) if datagram else None - if payload and len(payload) == lp.CONTROL_LEN: - c = lp.unpack_control(payload) - # Принять свежий seq ИЛИ новую сессию: источник (STM-TX) после - # ребута начинает seq заново с 0, и без сброса last_seq поток - # завис бы в failsafe навсегда. Новую сессию распознаём по пачке - # подряд «несвежих» пакетов (устойчиво к одиночным reorder/dup). - fresh = last_seq is None or seq_newer(c["seq"], last_seq) - if not fresh: - rejects += 1 - if rejects >= SEQ_RESET_AFTER: - log.warning("seq откатился (%d -> %d): новая сессия", last_seq, c["seq"]) - fresh = True - if fresh: - rejects = 0 - last_seq = c["seq"] - last_net = time.monotonic() - if in_failsafe: - log.info("связь восстановлена") - in_failsafe = False - ser.write(lp.build_frame(lp.CONTROL, payload)) - elif datagram is not None: - log.warning("битый/неаутентичный пакет отброшен") - except socket.timeout: - pass + # Ждём пакет до 20 мс (пауза цикла), затем БЕЗ блокировки выгребаем всю + # накопившуюся очередь сокета и шлём на STM только самый свежий кадр + # (latest-wins): так задержка не растёт, даже если пакеты пришли пачкой. + newest = None + ready = select.select([sock], [], [], lp.FRAME_PERIOD_MS / 2000.0)[0] + while ready: + try: + datagram, addr = sock.recvfrom(2048) + except BlockingIOError: + break + p = accept(datagram, addr) + if p is not None: + newest = p + ready = select.select([sock], [], [], 0)[0] + if newest is not None: + ser.write(lp.build_frame(lp.CONTROL, newest)) # сетевой failsafe: тишина -> активная нейтраль на STM в темпе 20 Гц now = time.monotonic() @@ -203,10 +228,11 @@ def run_rx(args, key): fs_seq = (fs_seq + 1) & 0xFFFFFFFF last_fs = now - # дренируем входящие LOG/IDENT от STM-RX - for ftype, payload in parser.feed(ser.read(256)): - if ftype == lp.LOG: - log.info("STM-RX: %s", payload.decode("ascii", "replace")) + # неблокирующий дренаж входящих LOG от STM-RX + if ser.in_waiting: + for ftype, payload in parser.feed(ser.read(ser.in_waiting)): + if ftype == lp.LOG: + log.info("STM-RX: %s", payload.decode("ascii", "replace")) # ---------------- общее ----------------