Files
ewsdr/doc/PLUTO_INTEGRATION_PLAN.md
ew8bakandClaude Opus 4.8 a885488243 feat: web RF-AGC + QO-100 RX MUTE controls (Pluto)
Прокинул в web-интерфейс управление, которого там не хватало (раньше
только отображение):

- RF-AGC (Pluto hw-gain): дропдаун режима FAST/SLOW/HYB/MAN + слайдер
  GAIN 0–73 дБ (активен только в MAN, драг переводит в manual). Виден
  по BackendCaps.HasHWGain (= Pluto подключён). Команды set_rx_gain_mode
  / set_rx_gain → SetRxGainMode / SetRxGain.
- RX MUTE (QO-100 self-monitor): toggle-кнопка рядом с BCN, видна по
  InQO100, подсветка по состоянию. Команда set_rx_mute → SetRxMuteOnTx.

Гейтинг зеркалит десктоп. Работает в GUI-web-сервере и headless-демоне.
Проверено на железе пользователем.

WebServer: команды + state-поля (rf_agc_visible/rx_gain_mode/rx_gain_db,
rx_mute_visible/rx_mute_on) + SetRxGainStatus/SetRxMuteStatus.
WebAdapter: колбэки OnRxGain*/OnRxMute (Invoke на поток контроллера) +
публикация в PushState. MainForm + ewsdrd.lpr: привязка колбэков.
WebPageHtml: виджеты, JS-обработчики, updRfAgc/updRxMute, PEND_MAP.

Также актуализирован doc/PLUTO_INTEGRATION_PLAN.md: TX-тракт и режим
QO-100 помечены проверенными на железе; раскрыт TODO «XO ppm-калибровка»
(ручной TxLOOffset как текущий обходной путь, xo_correction/GPSDO как
постоянное решение).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:02:34 +03:00

421 lines
30 KiB
Markdown
Raw Permalink 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.
# План интеграции ADALM-PLUTO (PlutoSDR) в EWSDR
Ветка: `feature/pluto-sdr-integration` (от `feature/radio-controller-refactor`).
Документ — результат анализа текущей кодовой базы и предлагаемый поэтапный план.
Цель: добавить второй бэкенд железа (Pluto, libiio/AD936x) рядом с openHPSDR так,
чтобы оба находились в одном окне discovery, а различия возможностей корректно
отражались в UI (включая оверлей samplerate с другими значениями для Pluto).
**Ключевые решения по продукту:**
- Pluto — **только VHF/UHF**, HF исключён. Свой band-план: 2 м, 70 см, 23 см,
13 см (и т.д.).
- Отдельный **режим QO-100** (геостационарный транспондер): RX через LNB,
TX напрямую на 2400 МГц, раздельные RX/TX LO, full-duplex.
---
## 1. Анализ текущей архитектуры
### 1.1 Слои и владение
```
TMainForm (GUI) ─┐
ewsdrd.lpr (демон)─┤── владеют ─→ TRadioController (backend-агностичное ядро)
WebServer/Adapter ─┘ │
├─ FNetwork: THPSDRNetwork ← ЕДИНСТВЕННАЯ точка железа
├─ FDSPEngine: TWDSPEngine
├─ FAudioOut / FAudioIn
├─ FSettings: TSettingsManager (per-device по MAC)
└─ FDeviceStore: TDeviceStore (saved + discovered)
```
`TRadioController` уже спроектирован как **backend-агностичное ядро** — в коде есть
прямые комментарии-намёки на Pluto (RadioController.pas:541, :603). Но
`FNetwork` объявлен конкретным типом `THPSDRNetwork`, а не интерфейсом, и
используется в **52 местах** контроллера.
### 1.2 Путь данных (ключевое для бэкенда)
**RX (вход IQ):**
```
THPSDRNetwork (RX-поток, UDP 1035) → OnDDCIQ(DDCIndex, TDDCIQPacket)
→ FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame)
```
IQ — **24-битные** интерливнутые пары I/Q (6 байт/пара), формат зашит в
`PushDDCPacket` (WDSPEngine.pas:1136) и в декодере DSP-потока.
**TX (выход IQ):**
```
WDSP TXA → OnTXIQ(Buf, Count) → накопление 240 пар (24-бит, clamp)
→ FNetwork.SendDUCIQ(I[], Q[])
```
**Телеметрия:** `OnHPStatus(THighPriorityStatus)` → fwd power / SWR / supply V/A /
PLL lock / ADC overload. S-meter считается отдельно в WDSP (`FLastSMeter`).
### 1.3 Discovery
```
FController.Discover → TDiscoverThread → FNetwork.Discover(2000)
(UDP broadcast :1024, CMD_DISCOVERY)
→ per device: OnDeviceFound(THPSDRDevice)
→ FDeviceStore.AddDiscovered(...) → Changed(rfDeviceList)
→ DeviceForm.RefreshFound (один список «DISCOVERED DEVICES»)
```
- `THPSDRDevice`: IP, Port, MAC[6], BoardType, ProtocolVersion, NumDDCs…
- `TDeviceStore`: `TSavedDevice` (Name/IP/BoardType/AutoStart) и
`TDiscoveredDevice` (IP/DisplayName/BoardType/MAC[6]).
- **Per-device настройки** (`Settings.pas`) ключуются по MAC через
`MacToStr(MAC)` → строковая секция JSON. `LoadDevice/LoadTX/LoadAlex/LoadXvtr`
все принимают `MAC: array of Byte`.
### 1.4 Sample rate
- `SampleRateOverlay.pas`**жёстко зашитый** массив
`SPAN_RATES = (48000,96000,192000,384000,768000,1536000)` и `SPAN_NAMES`.
- `SetSampleRate(Hz)` (RadioController.pas:1791): span ≡ sample rate;
`FDSPEngine.ChangeSampleRate(Hz)` + `FNetwork.ConfigureDDCs(1, Hz div 1000, …)`.
- Дискретные значения HPSDR кратны DSP-clock 122.88 МГц.
### 1.5 Частота / диапазоны / XVTR
- `FreqToPhaseWord` (HPSDRProtocol.pas): phase = 2³² · f / 122.88 МГц.
- `FreqToBandIdx` / band-план — **HF/6m** (1.8–54 МГц, 11 диапазонов).
- Есть полноценная **XVTR-подсистема** (8 слотов, LO offset/error, трансляция
видимой частоты в IF, `XvtrTranslate`) — но **один** LO-offset на слот (RX=TX).
Для QO-100 нужны **раздельные RX/TX LO** → отдельная модель (см. §4).
- `SetDuplex` (RadioController.pas:1851) — сейчас заглушка (`{TODO wiring}`).
Для QO-100 full-duplex это нужно довести.
---
## 2. Pluto: что отличается (capability-матрица)
| Возможность | openHPSDR | Pluto (AD936x) |
|--------------------------------|-----------------------|---------------------------------------|
| Транспорт | UDP/Ethernet (свой) | libiio: USB / IP / local |
| Discovery | UDP broadcast :1024 | `iio_scan_context` (USB/network/local)|
| Идентификатор | MAC[6] | serial / context URI |
| Кол-во приёмников (DDC) | до 80, sync | 1 RX (2×2 только с хаком прошивки) |
| Sample rate | дискрет 48k…1536k | **непрерывный, ≥ ~521 ksps**, до 61.44 MSPS (по USB2 практически ~56 MSPS) |
| RF-полоса (аналог. фильтр) | нет (фикс CIC) | отдельная настройка RX/TX LPF |
| Диапазон частот | 061.44 МГц (HF/6m) | 325 МГц–3.8 ГГц (сток), **70 МГц–6 ГГц с хаком**. HF не используем |
| Разрядность IQ | 24-бит | 12-бит ADC, по шине 16-бит |
| Усиление RX | step atten + dither/random | manual gain 073 дБ / AGC (slow/fast/hybrid/manual) |
| TX | DUC + PA + Alex T/R | 1 DAC, TX atten 89…0 дБ, **без PA/SWR** |
| Full-duplex | (зависит) | да (раздельные RX/TX LO) — нужно для QO-100 |
| S-meter | WDSP (есть) | WDSP (есть) + аппаратный RSSI |
| Fwd power / SWR / supply V/A | HP status | **нет** (нет PA) |
| PLL lock | HP status | нет (другой механизм) |
| ADC overload | HP status | косвенно (gain/RSSI) |
| Wideband ADC дисплей | есть | нет |
| Alex / антенный коммутатор | есть | нет |
| Mic over network / HW CW keyer / sidetone / open collector | есть | нет (TX-аудио через звуковую карту/web) |
| 10 МГц reference / PA config | есть | нет; есть XO ppm-калибровка, темп.сенсор |
**Только у Pluto (нужно в UI):** RF bandwidth, manual gain / режим AGC железа,
XO ppm-коррекция, выбор транспорта (USB/IP), buffer size/timeout, температура.
---
## 3. Band-план Pluto (VHF/UHF) и режим QO-100
### 3.1 VHF/UHF band-план
HF исключён. Для Pluto — **отдельная таблица диапазонов** (используется, когда
подключён бэкенд `bkPluto`); `FreqToBandIdx`/band-кнопки переключаются на неё:
| Band | Частоты | Примечание |
|--------|----------------------|---------------------------------------------|
| 2 м | 144146 МГц | требует freq-range хак Pluto (<325 МГц) |
| 70 см | 430440 МГц | требует freq-range хак Pluto |
| 23 см | 12401300 МГц | нативно |
| 13 см | 23002450 МГц | нативно (включает QO-100 uplink 2400) |
| (опц.) FM bcast / ADS-B / прочие — позже |
⚠️ 2 м и 70 см ниже стокового минимума 325 МГц → нужен AD9363→AD9361
frequency-range мод прошивки Pluto. В UI/доках это отметить.
### 3.2 Режим QO-100 (геостационар Es'hail-2 NB-транспондер)
Геометрия сигнала:
- **Downlink (RX):** 10489.5010489.99 МГц (10 ГГц). Принимается через LNB:
LNB LO (обычно 9750 МГц) → Pluto RX ≈ 739.5739.99 МГц. Нативный диапазон Pluto.
- **Uplink (TX):** 2400.052400.54 МГц. Pluto TX напрямую (нативно).
- **Связь частот:** uplink = downlink 8089.5 МГц (offset транспондера).
- **Full-duplex** — стандартный режим работы (слышишь себя через спутник).
Отсюда QO-100 — это **трансвертер с раздельными RX/TX LO**, чего нынешняя
XVTR-модель (один offset) не даёт. Предлагаемая модель — отдельная подсистема/слот:
```pascal
TQO100Settings = record
Enabled: Boolean;
DisplayBase: Double; // отображаем downlink (10489.x ГГц)
LnbLoHz: Double; // LO LNB, по умолч. 9750e6
LnbPpm: Double; // калибровка дрейфа LNB (по маяку)
TransponderOfs: Double; // 8089.5e6 (downlink - uplink)
TxAttenDb: Double; // -89..0
FullDuplex: Boolean; // RX во время TX
end;
```
Трансляция:
- RX: `PlutoRxLO = DisplayFreq LnbLoHz` (с учётом `LnbPpm`).
- TX: `PlutoTxLO = DisplayFreq TransponderOfs`.
- Дисплей/VFO/курсор работают в терминах downlink (10 ГГц).
- Калибровка по маяку: подстройка `LnbPpm`, чтобы наблюдаемый маяк совпал с
опорной частотой.
Требует довести `SetDuplex` (сейчас TODO) до реального full-duplex (одновременно
RX-поток refill и TX-поток push в libiio — Pluto это поддерживает аппаратно).
---
## 4. Предлагаемая архитектура интеграции
### 4.1 Абстракция бэкенда (ядро плана)
Базовый класс/интерфейс **`TRadioBackend`**, который реализуют оба:
`THPSDRNetwork` и новый `TPlutoBackend`. Контроллер держит `FBackend: TRadioBackend`
(сохранить имя поля `FNetwork`, сменив только тип на базовый — минимизирует дифф).
Методы, которых у Pluto нет, — пустые no-op (Alex, wideband, step atten, speaker
audio, DUC specific и т.д.).
Дескриптор возможностей:
```pascal
TBackendKind = (bkHPSDR, bkPluto);
TSampleRateMode = (srmDiscrete, srmContinuous);
TBackendCaps = record
Kind: TBackendKind;
HasTX, HasPA, HasAlex, HasWideband: Boolean;
HasDitherRandom, HasHWMic, HasPLLStatus: Boolean;
HasHWGain: Boolean; // manual gain / hw AGC (Pluto)
HasRFBandwidth: Boolean;
HasFullDuplex: Boolean;
MinSampleRate, MaxSampleRate: Integer;
SampleRateMode: TSampleRateMode;
RatePresets: array of Integer; // для оверлея
MinFreqHz, MaxFreqHz: Double;
end;
```
Контроллер по `FBackend.Caps`: гейтит UI, подставляет пресеты samplerate,
выбирает band-план (VHF/UHF), путь телеметрии.
### 4.2 libiio binding
- FPC-биндинг к `libiio` (и опц. `libad9361`) через **динамическую загрузку**
(`dynlibs`): `libiio.so` / `libiio.dll` / `libiio.dylib`. Приложение
запускается без libiio (Pluto просто не появляется в discovery).
- Новые юниты: `IIOBindings.pas` (типы/прототипы) + `PlutoBackend.pas`
(`TPlutoBackend`: scan/connect, RX/TX потоки, gain/freq/rate/bandwidth).
### 4.3 Discovery в одном окне
- Расширить записи устройств: `Kind: TBackendKind`, `URI: string`
(Pluto: `usb:1.5.5` / `ip:192.168.2.1`), `Serial: string`.
- `TDiscoverThread` запускает **оба** скана параллельно, сливает в один store:
HPSDR (`FNetwork.Discover`) + Pluto (`iio_create_scan_context`).
- `DeviceForm` структурно не меняется: тот же список, лишь метка типа в строке
(`PlutoSDR usb:1.5.5`). `BoardTypeName`/рендер дополнить веткой Pluto.
- `ConnectDevice` диспетчеризует по `Kind`: создаёт нужный бэкенд перед Connect.
### 4.4 Ключ per-device настроек
Минимальное изменение: для Pluto **синтезировать 6-байтовый ключ из serial**
(хэш serial → 6 байт) и подавать в существующие `LoadDevice/LoadTX/...`. Settings.pas
не трогаем. (Альтернатива — обобщить ключ до строки; дороже, отложить.)
### 4.5 Sample rate overlay
- `TSampleRateOverlay` получает список значений/имён от контроллера
(`SetRatePresets`). HPSDR — текущие 48k…1536k. Pluto — пресеты **> 560 кбит**,
напр. `768k / 1536k / 2304k / 3072k / 3840k / 5760k`.
- `SetSampleRate` для Pluto зовёт `FBackend` (set `sampling_frequency` +
`rf_bandwidth`), без `ConfigureDDCs`.
### 4.6 IQ-конвертация (формат WDSP не меняем)
- **RX:** `TPlutoBackend` читает int16 из `iio_buffer`, конвертирует 16→24-бит,
упаковывает в `TDDCIQPacket` (seq синтезируется), вызывает `OnDDCIQ`.
- **TX:** ✅ `SendDUCIQ` апсемплит 192k→device-rate **полифазным FIR-интерполятором**
×L (`BuildTxResampler`: оконный sinc-ФНЧ, окно Блэкмана ~−58 дБ, `TX_FIR_TAPS=16`
отводов/фазу, срез `TX_FIR_CUT=40 кГц`, нормировка на единичное DC-усиление фазы;
пресеты рейта кратны 192k → L целое), конвертирует 24→16-бит (`>>8`, full-scale
MSB-aligned), инвертирует Q (симметрично RX) и кладёт в кольцевой FIFO. Линия
задержки `FTxDlI/Q` живёт между вызовами, сбрасывается в `StartTX`/`ClearDUCIQQueue`.
Отдельный `TPlutoTXThread` льёт пары в tx-буфер libiio и `iio_buffer_push`
(блокирующий push = пейсинг на device-rate); при underrun — нули (непрерывный
full-duplex). Решение rate-mismatch (риск §6.2) — апсемпл в host (RX/TX делят
один `sampling_frequency`).
- **Размер TX-буфера** масштабируется от device-rate (модель SoapyPlutoSDR):
`round(rate/60)` → следующая степень 2, кламп `[4096..65536]` (576k→16384,
1536k→32768). Фикс 4096 был мал для full-duplex выше ~1 Msps → дропы/щелчки.
FIFO держит ~4 таких буфера запаса (`TxBufPairsForRate`, пересчёт в `StartTX`).
- Подтверждено по каноничному драйверу (SoapyPlutoSDR): TX full-scale = 32768,
MSB-aligned → наш `>>8` (24→16) верен; занижения уровня по дизайну нет.
### 4.7 Усиление / телеметрия
- ✅ RX gain: блок `RF AGC` (десктоп) — кнопки FAST/SLOW/HYB/MAN (4 режима AD9361,
подтверждены на железе `gain_control_mode_available`) + слайдер manual 0–73 дБ при
`Caps.HasHWGain`; персист per-device. WDSP AGC поверх.
-**RF-AGC в web** (управление): дропдаун режима RFagc-F/S/H/M + слайдер GAIN
0–73 дБ (активен только в MAN), виден при `BackendCaps.HasHWGain` (Pluto подключён).
Команды `set_rx_gain_mode`/`set_rx_gain``SetRxGainMode`/`SetRxGain`; state-поля
`rf_agc_visible`/`rx_gain_mode`/`rx_gain_db`. GUI-web-сервер + headless-демон. Собрано,
live-проверка на железе за пользователем.
- Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
`not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) →
`ServicePlutoTelemetry` (контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3
статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA). ✅ **Две температуры**:
трансивер AD9361 (`ad9361-phy temp0`) + Zynq SoC/FPGA (`xadc temp0`,
`(raw+offset)*scale/1000`) — формат `Temp 37/53C` в десктопе и web.
### 4.8 TX-мощность / drive (Pluto)
-**Мощность задаётся ОДИН раз — железной аттенюацией** (`hardwaregain`), а не
цифрой WDSP. `ApplyWDSPDrive` (контроллер): на Pluto `SetDriveLevel(1.0)` (полная
цифра), HPSDR — `Pct/100`. Раньше drive резался дважды (цифра × атт).
-**`PlutoTxAttForDrive` — power-linear:** `дБ = потолок + 20·log10(pct/100)`
(100% → потолок, 50% → −6, 10% → −20). Старый dB-линейный маппинг от −89.75 уводил
низкие % в −80 дБ — из-за этого TUN (TUNLevel 10%) давал −81.8 дБ → тон не слышен.
Потолок — `FPlutoTxMaxAttDb` (дефолт −10 дБ), на приёме −89.75 (≈выкл).
-**TX LO (`altvoltage1`) гасится по PTT** (`SetTxLOPowerdown`): на приёме
`powerdown=1` → нет утечки несущей TX на частоте VFO в RX-спектре и нет излучения в
простое; на передаче `powerdown=0`. `ApplyTxLO` ставит powerdown по `FTransmitting`
(заодно возвращает LO, если стороннее ПО его погасило). RX LO (`altvoltage0`) —
всегда вкл (приём непрерывен, QO-100 full-duplex не ломается). Цена: повторное
включение LO запускает калибровку синтезатора AD9361 (единицы–десятки мс) → первый
слог SSB может чуть подрезаться.
---
## 5. Поэтапный план (фазы)
**Фаза 0 — Абстракция (без поведения Pluto)**
- `TRadioBackend` (база) + `TBackendCaps`; `THPSDRNetwork` реализует, заполняет
caps. Сменить тип `FNetwork`/вызовы. Регрессия: HPSDR как раньше. ✅
**Фаза 1 — libiio binding + discovery**
- `IIOBindings.pas`, `PlutoBackend.pas` (skeleton: scan/connect/disconnect/caps).
- `Kind/URI/Serial` в store, объединённый discovery, диспетчер по `Kind`.
- UI: оба типа в одном окне. ✅ (видим Pluto, ещё без RX).
**Фаза 2 — RX-тракт Pluto + VHF/UHF band-план**
- RX-поток libiio → 16→24-бит → `OnDDCIQ` → WDSP. Set freq (Hz в LO), gain,
sample rate, RF bandwidth.
- VHF/UHF band-план (§3.1) при `bkPluto`; samplerate-оверлей с Pluto-пресетами.
- Гейтинг UI по caps. ✅ приём на Pluto (спектр/водопад/аудио на 2м/70см/23см/13см).
**Фаза 3 — Управление/калибровка**
- ✅ RX gain / hw-AGC, транспорт (USB/IP), persist per-device по synthetic-MAC.
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия в статус-бар: RSSI + **две
температуры** (трансивер AD9361 + Zynq SoC `xadc`, формат `Temp 37/53C`).
- TODO: **XO ppm-калибровка** — корень частотной ошибки опорника Pluto. Сейчас
обходится вручную через `TxLOOffset` QO-100-слота (подстройка под фактический
uplink, см. Фаза 5). Правильный фикс — `xo_correction` AD9361 (правит RX **и** TX
в ppm на всех частотах разом) или внешний 40 МГц GPSDO-реф (золотой стандарт для
QO-100). На 2.4 ГГц 1 ppm ≈ 2.4 кГц, поэтому TX без GPSDO плывёт с прогревом.
- TODO: buffer/timeout-контролы.
**Фаза 4 — TX-тракт Pluto**
-`SendDUCIQ` 24→16 + апсемпл 192k→device-rate + пейсинг (FIFO + push-поток, §4.6).
-**Полифазный FIR-интерполятор** вместо линейной интерполяции (§4.6) — образы на
кратных 192k давятся ~−58 дБ (раньше только sinc²), чище аплинк, громкость не меняется.
-**TX-буфер по sample-rate** (§4.6) — устранены щелчки/дропы на full-duplex >1 Msps.
- ✅ Управление мощностью через TX-аттенюацию (ADI-модель), маппинг **power-linear**
+ полная цифра WDSP (§4.8). Без PA → нет drive-байта/SWR/Alex/keyer.
-**TX LO powerdown по PTT** (§4.8) — нет утечки несущей TX в RX-спектре в простое.
-**Проверено на железе** (передача QO-100 через спутник, downlink виден в
full-duplex self-monitor; SSB-ориентация и уровни в норме).
- TODO: опц. headroom 6 дБ для FM (full-scale const-envelope → нелинейность ЦАП);
опц. зануление IQ на приёме. Опц. поддержка F5OEO-прошивки (PTT внешнего PA через
`hardwaregain=40`) — отложено, нужно только под конкретное железо.
**Фаза 5 — Режим QO-100**
- ✅ Калибровка LNB по маяку (beacon lock) — стабилизация дрейфа.
- ✅ Hardware full-duplex + ✅ display-duplex (`SetDuplex` доведён: живой
`KeepRXDuringTX`; авто-вкл при входе в QO-100 — слышим свой downlink).
- ✅ Раздельные RX/TX LO (§3.2): QO-100 = XVTR-слот с флагом `FullDuplexTx` +
`TxLOOffset`; `XvtrTranslateTX` даёт TX-LO = visibleTxLOOffset (без LOError).
RX-путь (LNB LO + beacon-дрейф) не тронут. Дисплей/VFO в downlink-терминах.
- ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset /
границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот
(`QO100_SLOT`), не засоряет таблицу трансвертеров openHPSDR.
-**TX↔RX связка с фиксированным офсетом**: TX-VFO в downlink-домене,
`XvtrTranslateTX` = `visible TxLOOffset` (≈8089.5 МГц) — крутишь приём, аплинк
едет следом, офсет держится сам (через общий VFO). Split — через VFO-B.
-**Аудио self-monitor** («слышу себя со спутника»): кнопка `RX MUTE` в TX-панели
рядом с DUP — видна только Pluto + full-duplex (QO-100). По умолчанию ON (RX-аудио
глушится на TX, как раньше); OFF → RX-аудио идёт на колонки и во время TX (свой
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
персист в глобале (`rx_mute_on_tx`). ✅ **В web**: toggle-кнопка `RX MUTE` рядом с
BCN, видна по `InQO100`, подсветка по `rx_mute_on`; команда `set_rx_mute`
`SetRxMuteOnTx`. GUI-web-сервер + headless-демон.
-**Bandplan-оверлей NB-транспондера** (`BandPlanOverlay.pas`): полоска внизу
спектра (над ruler) с сегментами CW ONLY / NB DIGI / DIGI / SSB / MIXED MODES +
маяки CW/PSK/EXP. Модель `VfoOverlay` (кэш-битмап + const-alpha), CPU и GL, виден
по `InQO100`. DPI-устойчив (высота и шрифт по `Screen.PixelsPerInch`).
- TODO (визуальный self-monitor / навигация по транспондеру):
* **Индикатор «не громче маяка»** — сравнение уровня своего downlink с маяком
(этикет QO-100); амплитуда маяка уже известна декодеру.
* RIT/XIT-клариффайер — опц., на геостационаре нужен редко.
-**Проверено на железе** (QSO через QO-100: TX uplink 2400 + RX downlink 739.5 МГц,
full-duplex self-monitor). Нюанс калибровки: `TxLOOffset` пришлось подстроить руками
под фактический uplink — это ошибка опорника Pluto (TCXO), а не геометрия (геом.
offset точно 8089.5 МГц). Постоянное решение — XO ppm / GPSDO, см. Фаза 3 TODO.
- ОТВЕРГНУТО: TX-маркер на RX-спектре в DUP — в обычной работе RX=TX (один VFO),
маркер ложится на RX-курсор, информации не даёт; полезен только в split (редко).
**Фаза 6 — Полировка** ✅ (в основном)
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
(discovery/connect Pluto headless, beacon-lock @10 Гц); кросс-платформенная сборка
с/без libiio.
- ✅ Проброс в web RF-AGC (gain/режимы) и QO-100 `RX MUTE` (self-monitor) — см.
§4.7 / Фаза 5. Доки актуализируются по ходу.
**Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):**
- ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-
загрузчик бэндов читал только freq/mode/filter/AGC/CTun/center/span и **пропускал
FM-поля** (`fmsq_on`/`fmsq_level`/`ctcss_*`/`fmstep_*`/`fmrpt_*`), хотя `SaveBand`
их пишет, а `LoadBand` читает. Итог: SQL/CTCSS/шаг/репитер-сдвиг сохранялись на
выходе, но сбрасывались в дефолт при следующем запуске (смена бэндов работала —
идёт через in-memory band-cache). Затрагивало **все бэкенды**, не только Pluto.
---
## 6. Риски и открытые вопросы
1. **2 м / 70 см ниже 325 МГц** → требуется freq-range мод прошивки Pluto.
Документировать; возможно гейтить эти кнопки при стоковой прошивке.
2.**TX sample-rate mismatch** (192k WDSP vs ≥521k Pluto) — решено host-апсемплом
192k→device-rate в `SendDUCIQ` (целочисленно, пресеты кратны 192k). ✅ Качество
образов — **полифазный FIR-интерполятор** (окно Блэкмана ~−58 дБ). ✅ Щелчки на
full-duplex >1 Msps — TX-буфер масштабируется от rate (модель SoapyPlutoSDR).
3. **QO-100 display-duplex** — hardware full-duplex есть (Фаза 4), остаётся довести
`SetDuplex` (водопад RX во время TX); проверить латентность/буферы libiio.
4. **LNB дрейф** — калибровка `LnbPpm` по маяку (ручная/полуавтомат).
5. **Ключ настроек** — synthetic-MAC из serial (быстро) vs обобщение до строки.
6. **libiio как зависимость** — динамическая загрузка, поведение при отсутствии.
7. **USB-латентность/пропускная** — реальный максимум sample-rate по USB2.
---
## 7. Затрагиваемые файлы (ориентир)
| Файл | Изменение |
|-------------------------|-------------------------------------------------------|
| `RadioController.pas` | тип `FBackend`, диспетчер по Kind, гейтинг по caps, QO-100 трансляция |
| `HPSDRNetwork.pas` | реализовать `TRadioBackend` + заполнить caps |
| `IIOBindings.pas` (нов) | биндинг libiio (dynamic) |
| `PlutoBackend.pas` (нов)| `TPlutoBackend` (scan/connect/RX/TX/gain/rate/bw) |
| `DeviceStore.pas` | `Kind/URI/Serial` в записях |
| `DeviceForm.pas` | метка типа в списке (структурно без изменений) |
| `SampleRateOverlay.pas` | пресеты из контроллера вместо констант |
| `BoardUtils.pas` | имя Pluto, VHF/UHF band-план |
| `Settings.pas` | без изменений (synthetic-MAC) / QO-100 persist |
| `MainForm.pas` / `ewsdrd.lpr` | гейтинг виджетов по caps, wiring, QO-100 UI |