mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
Всплесков на передаче нет, инструмент своё отработал и больше не нужен в горячем пути. Снято ровно то, что ставилd209697, и ничего сверх: * удалён юнит TxTrace.pas (кольцо + сброс дампа + TraceInit); * TCIAdapter: точки съёма маркеров и TX-аудио, поля FTxTraceLast/FTxTraceAudio; * HPSDRNetwork: DUC-DRY/DUC-RUN и переменная DryFrom (жила только для них), вместе с ними ушли из uses TxTrace и PlatformUtils — их добавлял тот же коммит, MonotonicUs в этом файле больше нигде не встречается; * RadioController: PTT-метка в SetMOX и сброс дампа на снятии PTT; * TraceInit из ewsdr.lpr и ewsdrd.lpr. Каждая удалённая строка стояла под `if TraceOn` — вычислений, от которых зависит тракт, в них не было. ★PlatformUtils.MonotonicUs НЕ тронут: на нём абсолютные дедлайны планировщика TX, это не отладка. Скрипт разбора test/hpsdr/txtrace_scan.py оставлен, формат дампа не менялся; в README стенда записан рецепт возврата зондов изd209697. Проверено: lazbuild --ws=qt6 и build-ewsdrd.sh собираются, test/tci 260/260, test/cat 50/50 — те же числа, что и с зондами. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
157 lines
13 KiB
Markdown
157 lines
13 KiB
Markdown
# Разбор всплесков на передаче по трафику радио (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-тракта
|
||
|
||
★**Сняты из кода 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`
|
||
обязан быть вдвое больше моно-отсчётов до интерполяции.
|