Files
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
..

Разбор всплесков на передаче по трафику радио (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.3–23.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 обязан быть вдвое больше моно-отсчётов до интерполяции.