Files
ewsdr/test/hpsdr/README.md
T
ew8bakandClaude Opus 5 867da5921e chore(tx): детектор голодания снят — всплесков не видно, вернём при нужде
Прибор из b173b7f отработал своё на этом заходе: всплески не появляются, и
держать его в тракте нет причин. Снято ровно то, что тот коммит поставил:
юнит TxHealth.pas, замеры простоев в отправителе DUC (обе ветки), публикация
контекста пейсинга из планировщика TCI, счёт бита HPS_FIFO_EMPTY, вызовы
TxHealthService в таймере UI и цикле демона, индикатор «·сухо N» в статусной
строке и секция G стенда.

Вернуть — реверсом этого коммита; опора детектора и правило его устройства
(в тракте передачи ничего, кроме записи в память) записаны в
test/hpsdr/README.md, чтобы не восстанавливать рассуждение заново.

Проверено: test/tci 262/262, test/cat 50/50, GUI (--ws=qt6) и демон собираются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
2026-08-24 19:25:57 +03:00

183 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Разбор всплесков на передаче по трафику радио (openHPSDR, протокол 2)
Всплеск на передаче в этом тракте может родиться в трёх разных местах, и
глазами их не отличить. Дамп трафика отличает, потому что в проводе лежит
ровно то, что мы отдали радио, и ровно то, что радио вернуло:
| где родился | что видно в дампе |
|---|---|
| наш DSP / очередь TX | разрыв **внутри** DUC I&Q: скачок на стыке пакетов, клиппинг, дыра по времени |
| сеть / провод | потеря `seq` DUC I&Q (наружу) или DDC I&Q (внутрь), дубли, переупорядочение |
| радио / прошивка | DUC I&Q ушёл ровным, а радио жалуется: FIFO overflow, пустой DUC FIFO |
| приёмный тракт ПК | на проводе всё цело, но растут `RcvbufErrors`/`drops` — мусор на водопаде, а не в эфире |
Последняя строка важнее, чем кажется: **всплеск, увиденный на своём водопаде,
может вообще не уйти в эфир** — это может быть дыра в приёмном потоке DDC.
Инструмент это разделяет.
## 1. Съём
```
sudo test/hpsdr/capture.sh # bridge0, радио 172.16.2.200
sudo test/hpsdr/capture.sh -i eth0 -h 172.16.2.99 -d /var/tmp/cap
```
Пишется кольцо `-W 24 -C 150` (24 файла по 150 МБ ≈ 10-15 мин полного дуплекса)
плюс `*.drops` — счётчики потерь ядра раз в секунду.
Явление редкое, поэтому работаем так: запустили съём, работаем в эфире как
обычно. **Увидели всплеск — засекли время по часам и дали ещё секунд десять,
потом Ctrl-C.** Кольцо гарантирует, что последние минуты на диске, а засечённое
время сразу показывает, в какую строку отчёта смотреть.
Место: полный дуплекс на 192 кГц TX + пара DDC ≈ 3-6 МБ/с. Кольцо не растёт.
## 2. Разбор
```
test/hpsdr/pcap_tx_scan.py /tmp/hpsdrcap/hpsdr-*.pcap \
--watch-ddc 3 --drops /tmp/hpsdrcap/hpsdr-*.drops
```
**`--watch-ddc N` — главный ключ, если вы смотрите свой сигнал на конкретном
приёмнике** (DDC3 = UDP-порт 1038). Тогда скан ищет всплески прямо в приёмном
потоке и сопоставляет каждый с тем, что в этот момент ушло в эфир. Без ключа
всплески ищутся на всех DDC.
Прочие ключи: `--tx-only` (события только внутри передачи), `--radio IP` (если
автоопределение промахнулось), `--corr СЕК` (окно сопоставления, по умолчанию
0.05), `--max N`, `--env env.csv` (профиль огибающей по пакетам — можно
построить график и увидеть форму всплеска).
## 3. Как читать отчёт
**`=== передачи ===`** — по каждому нажатию PTT: сколько DUC-пакетов реально
ушло против расчётных `длительность / 1.25 мс`. Минус десятки пакетов = очередь
TX голодала, в эфире дыры. Тут же диапазон глубины DUC FIFO радио: здоровая
передача держит его далеко от нуля.
**`=== сопоставление ===`** — ради этого всё и делалось. По каждому всплеску,
найденному в приёмном потоке, печатается всё, что случилось в окне ±50 мс, со
смещением в миллисекундах, и вердикт. Смещение важно: свой сигнал возвращается
в приёмник с задержкой тракта (ЦАП, эфир, конвейер DDC), поэтому причина обязана
стоять с **минусом** — раньше всплеска. Вердикты: `НАШ ТРАКТ` (в DUC I&Q рядом
`BREAK`/`CLIP` — сигнал ушёл в провод уже испорченным), `АРТЕФАКТ ПРИЁМА` (дыра
в самом приёмном потоке или `SOCK-DROP` — в эфир всплеск не уходил), `РАДИО`,
`ПРОВОД`, `КОМАНДА НА ХОДУ`, `ЖЕЛЕЗО` (рядом чисто в обе стороны).
**`=== конфигурация DDC ===`** — из перехваченного DDC Specific: источник
каждого включённого DDC (`ADC0` = эфир, `DAC` = петля PureSignal) и его частота
дискретизации. Источник решает, что вообще означает увиденный всплеск: с ADC —
это сигнал в антенне, с DAC — только то, что ушло в модулятор.
**События, по убыванию доказательности:**
* `RX-SPIKE` — выброс над фоном в приёмном потоке. Ищется **только на передаче**:
там свой сигнал держит ровный уровень, и всё, что торчит над ним, — событие.
На приёме эфир скачет сам, детектор был бы бессмысленным. Первые 250 мс после
фронта PTT пропускаются: уровень там меняется законно.
* `BREAK` — последний отсчёт пакета N и первый отсчёт N+1 разошлись больше чем
на 45% огибающей. На 192 ksps соседние отсчёты речевого сигнала так не прыгают:
это разрыв фазы/амплитуды, то есть щелчок или всплеск шириной во весь фильтр.
Родился до провода, значит у нас — в WDSP, в сборке пакетов или в очереди.
(Ровно эта подпись однажды уже ловилась: `bfo=0` в `OpenChannel(TXA)`.)
Ищется только в исходящем DUC: на приёме шум эфира прыгает от отсчёта к
отсчёту сам по себе, а разрыв от потерянного пакета и так виден по `seq`.
* `CLIP` — упор в полную шкалу DAC, печатается по фронту с длительностью.
* `TX-STARVE` / `FIFO-OVF` — жалоба самого радио: DUC FIFO пуст на передаче
либо переполнение. При пустом FIFO радио передаёт что попало.
* `SOCK-DROP` — на проводе пакеты были, ядро их выбросило. Тогда всплеск на
экране — артефакт приёма, в эфир он не уходил.
* `HOLE` — пауза между пакетами больше 2.5 периодов потока. Для DUC норма
1.25 мс, для DDC период выучивается по медиане первых 200 интервалов.
* `SEQ-LOSS` / `SEQ-DUP` — потери и дубли в проводе, отдельно наружу и внутрь.
* `HP-CHG` — High Priority сменился ПОСРЕДИ передачи, с расшифровкой поля
(частота DDC/DUC, drive, Alex, атт., OC). Смена частоты или drive на ходу —
это клик по построению.
* `DUCSPEC` / `DDCSPEC` — Specific-пакеты, ушедшие на передаче.
**Пусто во всех разделах при засечённом всплеске** — значит трактовка меняется:
цифровой поток чист от нас до радио и обратно, ищем дальше по железу (PA, ALC,
питание, реле), а не в софте.
## Файлы
* `capture.sh` — кольцевой съём tcpdump + лог потерь ядра.
* `pcap_tx_scan.py` — разбор pcap, чистый Python без зависимостей (~40 МБ/с).
## Тихий детектор голодания очереди TX (снят, вернуть из `b173b7f`)
Опора детектора: всплеск, рождённый НАШИМ трактом, невозможен без простоя
очереди DUC длиннее подушки отправителя (`DUC_FIFO_THROTTLE` = 2000 отсчётов при
192 кГц = **10.42 мс**) — съём 2026-08-21 дал шесть всплесков на шесть простоев
10.323.5 мс, а паузы 8 мс и короче не дали ни одного. Значит счётчик таких
простоев отвечает на вопрос «наш всплеск или не наш» полностью.
Прибор (`TxHealth.pas`) был включён всегда, без переменных окружения: поток
отправителя DUC мерил простои, планировщик TCI публиковал рядом контекст
пейсинга (аванс, долг, в полёте, квант, шаг, период пачек клиента), сетевой
поток считал бит `HPS_FIFO_EMPTY`. ★Журнал (`~/.config/ewsdr/txhealth.log`,
строка на передачу: «чисто» / «ОПАСНО (первое на N мс)» плюс подробности) писал
ТОЛЬКО потребитель — таймер UI и цикл демона; в тракте передачи диска не было по
построению. В статусной строке показывался счётчик `·сухо N` за сеанс.
Вернуть целиком:
git revert <коммит снятия> # он же и есть реверс b173b7f
★Правило, ради которого прибор так и построен: предыдущий инструмент
(`TxTrace`) сбрасывал дамп синхронно в хвосте `SetMOX` и этими десятками
миллисекунд закрывал гонку в пейсинге TCI — с зондами всплесков не было, без них
они возвращались (лечение гонки — `fa38fa7`). Инструмент не имеет права ничего
делать в тракте передачи.
## Приборы внутри процесса: кольцевая трассировка TX-тракта
★**Сняты из кода 2026-08-24** — пейсинг TX проверен на железе, всплесков нет, и
инструмент больше не нужен в горячем пути. Последняя редакция зондов лежит в
коммите `d209697`; вернуть их целиком:
git show d209697 -- TxTrace.pas | git apply # сам юнит
git show <коммит снятия> | git apply -R # точки съёма и TraceInit
Скрипт разбора (`txtrace_scan.py`) оставлен: формат дампа не менялся, и при
возврате зондов он работает как прежде. Ниже — описание прибора на случай
возврата.
Дамп на проводе видит ПАУЗУ в потоке DUC, но не видит, кто её сделал. Для этого
в код вписывалось кольцо трассировки (`TxTrace.pas`), выключенное по умолчанию.
Включение — переменной окружения, кольцо сбрасывается на диск в конце КАЖДОЙ
передачи (после снятия PTT, чтобы файл не задержал выход из эфира):
EWSDR_TXTRACE=1 ./bin/x86_64-linux/ewsdr
# файлы: ~/.config/ewsdr/txtrace-<дата>-tx.txt
Разбор: `./txtrace_scan.py ~/.config/ewsdr/txtrace-*-tx.txt`
Что пишется (только выбросы там, где событий тысячи в секунду):
| событие | A | B | C |
|---|---|---|---|
| `PTT` | 1 старт / 0 конец | источник модуляции: 0 радио, 1 звуковая карта, 2 внешняя подача (web/TCI) | |
| `RECV-GAP` | пауза между возвратами `recvfrom` >2 мс | порт | |
| `HANDLER` | длительность обработчика >2 мс | порт | длина пакета |
| `HP-SYNC` | длительность `Invoke(ApplyHPStatus)` = `TThread.Synchronize` >2 мс | | |
| `TX-TICK` | зазор между тиками TX-потока | уровень mic-ринга НА ВХОДЕ тика | блоков сделано |
| `TX-PHASE` | `fexchange0` | кормление TX-анализатора (стоит ПЕРЕД отдачей IQ) | отдача IQ в очередь DUC |
| `CHRONO` | сколько отсчётов просим у клиента TCI | остаток долга | пауза от прошлого маркера |
| `TX-AUDIO` | пауза от прошлого блока от клиента | `length` из шапки клиента | отсчётов после интерполяции |
| `PULL-MIC` | запрошено у звуковой карты | реально получено | пауза от прошлой тяги |
| `DUC-DRY` | виртуальный уровень FIFO радио | глубина очереди | |
| `DUC-RUN` | длительность простоя очереди | уровень FIFO на выходе | глубина очереди |
Ключевые чтения:
* `DUC-RUN` длиннее 10.42 мс (подушка `DUC_FIFO_THROTTLE`) = кандидат на всплеск;
скрипт печатает историю событий перед каждым таким простоем;
* `TX-TICK` с `B` меньше 512 = продюсер не успел, тракт тут ни при чём;
* `TX-AUDIO` с периодом около 40-60 мс = зернистость запроса TCI (тик 20 мс
против блока 2048 отсчётов = 42.7 мс), а не сбой сети;
* пара (`B`, `C`) у `TX-AUDIO` проверяет учёт каналов: при стерео `length`
обязан быть вдвое больше моно-отсчётов до интерполяции.