Этап 3c — гибрид GF+ARQ, пересборка NACK, приёмка. Техдолг #6 закрыт

ARQ включён и для group-FEC. Паритет и ретрансмит перестали быть альтернативами:
GF гасит одиночные потери без задержки, ARQ добирает то, что паритету не по силам
(>2 стираний/группу). Дыру, закрытую паритетом, RX снимает с NACK сам — лишнего
трафика нет.

receiver.c:
- group_route: кадр ложится в кольцо ДО любого bypass. Кольцо — единственный
  источник порядка и живёт независимо от сборки групп; иначе ретрансмит закрытой
  группы не занял бы свой слот и next_out встал бы на нём навсегда. Паритет
  получает слот нулевой длины: тратит seq, но в файл не идёт.
- group_route: bypass для кадра уже закрытой группы (base < g.base, а также
  base == g.base при !active — группа финализирована по END). Без него ретрансмит
  преждевременно flush-ал активную группу, сбрасывая её стирания, либо «воскрешал»
  закрытую с одним элементом.
- group_flush: под FDD пишет в кольцо ТОЛЬКО восстановленные паритетом кадры
  (g.fixed) и снимает их seq с NACK — принятые уже в кольце. Симплекс сохраняет
  прямой вывод группы: ретрансмитов там нет, переупорядочивать нечего.
- process_end: досрочный group_flush последней группы. Штатно группа закрывается
  первым кадром следующей — для последней его не будет, и её восстановление
  случилось бы только при выходе потока C, то есть уже после COMPLETE.
- process_end/miss_rebuild: NACK пересобирается из кольца на каждом END. Находка
  стресса 3b: miss_add при переполнении MISS_CAP молча терял seq, и вернуть его
  было некому (детектор дыр по нему второй раз не срабатывает) — приём заклинивало
  насмерть. Кольцо знает точно, каких кадров нет.
- arq_check_complete: критерий единый (next_out == total_seq), временный gf_seen
  убран.

transmitter.c: g_arq_on = 1 безусловно при -F; arq_service в GF-цикле как в legacy.
This commit is contained in:
Maxim
2026-07-16 15:29:25 +03:00
parent 6294a472cd
commit ab1e87a17e
6 changed files with 224 additions and 49 deletions

View File

@@ -277,7 +277,7 @@ ssh root@192.168.2.1 '/tmp/ofdm_bench 1.92'
| 3 | `fec_decode()` liquid не сообщает о неисправимых RS-блоках → счётчик `rs_saved` фиктивен. Достоверный критерий — только CRC/md5 поверх данных | ✅ исправлено: CRC32 поверх данных до RS на TX, сверка после декода на RX; `rs_saved`/`rs_fail` достоверны (подтверждено loopback'ом, §12.2) |
| 4 | Внешний NCO-CFO в receiver: `set_phase(0)` на границах буферов рвёт фазу; ofdmflexframesync и так компенсирует CFO сам | ✅ исправлено: внешний NCO удалён, CFO оставлен только как телеметрия |
| 5 | TX пушит весь буфер 16384 сэмпла при кадре ~10300 → ~35% эфира впустую + паузы `-p` | ✅ исправлено: `iio_buffer_push_partial(idx)` + дренаж хвоста DMA перед закрытием |
| 6 | Нет ARQ: потерянный кадр = молчаливая дыра в файле. seq в заголовке есть, но RX его игнорирует | ⚙️ частично: детектор пропусков seq (лог `[ПОТЕРЯ]` + счётчик) + **групповой стирающий FEC 16+2 закрывает ≤2 потери/группу на симплексе без обратного канала (§12.7, п.6)**; полный ARQ — на дуплексе (п.7) |
| 6 | Нет ARQ: потерянный кадр = молчаливая дыра в файле. seq в заголовке есть, но RX его игнорирует | ✅ исправлено на FDD (§12.9, §10 п.7): детектор пропусков seq → NACK в STATUS → селективный ретрансмит из окна TX 2048 кадров + кольцо переупорядочивания на RX + хендшейк END/COMPLETE (хвостовые потери). 10 МБ байт-в-байт, A возвращает 0 только по подтверждённому COMPLETE. **На симплексе (без `-F`) ARQ невозможен** — там остаются детектор `[ПОТЕРЯ]` и групповой FEC 16+2 (≤2 потери/группу, §12.7, п.6) |
## 10. Дорожная карта
@@ -303,7 +303,15 @@ ssh root@192.168.2.1 '/tmp/ofdm_bench 1.92'
байт-в-байт на рабочей точке `-p 4000`. Защита информации (лёгкий AEAD,
+16 Б/кадр, <1% CPU) — отложена, требования ОКР «R-Link» нет; формат
с резервом под неё.
7. FDD 868/915 двусторонняя + ARQ с обратным каналом + меры из docs/NOTES.md
7. ✅ **FDD 868/915 + обратный канал + ARQ** (§12.8, §12.9) — техдолг #6 закрыт.
Этап 1: две несущие, A TX915/RX868, B зеркально. Этап 2: обратный канал
STATUS (кумулятивный снимок RX: highest_seq + NACK-список), gate пройден —
PER форварда не деградирует от обратного TX. Этап 3: EOF-хендшейк END/COMPLETE
(хвостовые потери молчаливы — END несёт итог передачи) + селективный
ретрансмит по NACK (окно TX 2048 кадров, повтор не чаще 400 мс) + кольцо
переупорядочивания на RX (1024 кадра). **Побайтовый лосслесс на 10 МБ:
44 потери вытянуты 45 ретрансмитами, md5 ==.** ARQ и group-FEC работают
вместе: паритет гасит одиночные потери без задержки, ARQ добирает остальное.
8. UDP-туннель поверх линка; затем web-настройка (libmicrohttpd)
9. Видео (raw UDP, пакеты ≤1472 Б) — при устойчивом PER
@@ -580,6 +588,50 @@ TX-бёрстов (duty 57% при `-p 4000`), STATUS выживает в пау
И PER₁≈PER₀), fallback TDD не требуется. Разбор — NOTES §2.2. Дальше — этап 3:
селективный ретрансмит по NACK + END/COMPLETE-хендшейк.
### 12.9 ARQ: END/COMPLETE + селективный ретрансмит (§10 п.7, этап 3) — 2026-07-16
Закрытие техдолга #6. Три подэтапа, каждый принят на железе отдельно.
**Механизм.** (а) **END** (кадр `F0 E7`, payload 12 Б: total_seq + total_bytes):
хвостовые потери молчаливы — у последних кадров нет следующего seq, по которому
детектор увидел бы дыру. A шлёт END каждые 100 мс после EOF, пока не услышит
COMPLETE (таймаут 10 с → **exit 2**). (б) **Окно TX** `txwin[2048]` на A хранит
сырой payload: `phy_tx_frame` детерминирован, повтор уходит байт-в-байт.
(в) **NACK** едут в кумулятивном STATUS (50 старейших seq); ретрансмит
per-seq не чаще **400 мс** — расчёт петли потеря→STATUS→повтор→подтверждение при
50% доставки STATUS (§12.8). (г) **Кольцо переупорядочивания** `rb[1024]` на B:
ретрансмит приезжает вне очереди, в stdout уходит только непрерывный префикс
(курсор `next_out`). COMPLETE ⇔ `next_out == total_seq`.
**Результаты** (кабель+аттенюатор, `TXGAIN 30`, `FBGAIN 15`, `-p 4000`):
| Прогон | Потери seq | Ретрансмитов | Итог |
|---|---|---|---|
| 1 МБ, GROUPFEC=0 | 4 / 1024 | 4 | md5 ==, COMPLETE за 88 мс, END × 1 |
| 10 МБ, GROUPFEC=0 | 44 / 10240 | 45 | md5 ==, `NACK вне окна: 0`, STATUS 93% |
| негатив, FBGAIN 89 | — | — | A честно **exit 2** за 10 с (раньше рапортовал успех) |
Окно TX на 10 МБ провернулось ~5 раз без единого «NACK вне окна», кольцо RX не
переполнялось ни разу — оба расчётных риска закрыты замером. Доставка STATUS в
END-фазе растёт до ~93% (эфир пуст, самоглушение A прекращается) — плато §12.8
на хендшейк не влияет, как и предполагалось.
**Клифф канала.** Стресс `TXGAIN 38/40/45` бесполезен: канала там нет вовсе —
`Заголовков 3322, CRC 0` (заголовок BPSK живёт, payload QPSK+RS падает первым).
Переход 30 (PER 0.4%) → 38 (100%) резкий, промежуточной точки на кабеле почти
нет. Поведение в обрыве корректное: A упёрся в расчётный потолок rate-limit
(2246 ретрансмитов ≈ 50 × 18 с / 0.4 с — эфир не залит дублями), B не поднял
COMPLETE, A вышел с 2. Настоящий PER-стресс — свипом в §12.4, не здесь.
**Симплекс идёт мимо кольца.** Без `-F` ретрансмитов нет, seq только растёт —
переупорядочивать нечего, а дыру закрыть некому: кольцо лишь задержало бы вывод
до EOF. Отсюда ветка `g_fb_fg ? rb : stdout` в `process_frame`/`group_flush`.
**Гибрид с group-FEC.** Паритет гасит одиночные потери без задержки (RX снимает
восстановленные seq с NACK — ретрансмит не нужен), ARQ добирает то, что паритету
не по силам (>2 стираний/группу). `grp_failed > 0` при целом md5 — норма: группа
не собралась, но ARQ её дозакрыл.
---
*Ввод в строй: 1415-07-2026. Замеры CPU и базовые решения — 10-07-2026.
Прошивка v0.38, liquid-dsp 1.8.0.*