QO-100 reuses the transverter path (VFO/band-stack/beacon-lock untouched) with a split LO for the geostationary transponder: - TXvtrEntry += FullDuplexTx + TxLOOffset (persisted). Reserve the last XVTR slot (QO100_SLOT) for QO-100; migration restores the template when the slot is unset or has empty offsets (fixes old configs showing zeros). - XvtrTranslateTX: TX LO = visible - TxLOOffset (no LNB LOError) for QO-100; identical to RX translate for normal transverters. Switched all TX-side translate calls (controller + MainForm). - SetDuplex wired (live KeepRXDuringTX); auto display-duplex on QO-100 entry. - New "QO-100" settings page (LNB LO / transponder offset / downlink edges / RX gain / TX ceiling + live RX-IF/TX readout); QO slot hidden from the transverter table. Reuses OnXvtrChange — no MainForm wiring needed. - doc/PLUTO_INTEGRATION_PLAN.md: phase 5 done. Builds clean (headless + qt6 GUI). Not yet verified on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22 KiB
План интеграции 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 практически ~5–6 MSPS) |
| RF-полоса (аналог. фильтр) | нет (фикс CIC) | отдельная настройка RX/TX LPF |
| Диапазон частот | 0–61.44 МГц (HF/6m) | 325 МГц–3.8 ГГц (сток), 70 МГц–6 ГГц с хаком. HF не используем |
| Разрядность IQ | 24-бит | 12-бит ADC, по шине 16-бит |
| Усиление RX | step atten + dither/random | manual gain 0–73 дБ / 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 м | 144–146 МГц | требует freq-range хак Pluto (<325 МГц) |
| 70 см | 430–440 МГц | требует freq-range хак Pluto |
| 23 см | 1240–1300 МГц | нативно |
| 13 см | 2300–2450 МГц | нативно (включает 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.50–10489.99 МГц (10 ГГц). Принимается через LNB: LNB LO (обычно 9750 МГц) → Pluto RX ≈ 739.5–739.99 МГц. Нативный диапазон Pluto.
- Uplink (TX): 2400.05–2400.54 МГц. Pluto TX напрямую (нативно).
- Связь частот: uplink = downlink − 8089.5 МГц (offset транспондера).
- Full-duplex — стандартный режим работы (слышишь себя через спутник).
Отсюда QO-100 — это трансвертер с раздельными RX/TX LO, чего нынешняя XVTR-модель (один offset) не даёт. Предлагаемая модель — отдельная подсистема/слот:
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 и т.д.).
Дескриптор возможностей:
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(setsampling_frequency+rf_bandwidth), безConfigureDDCs.
4.6 IQ-конвертация (формат WDSP не меняем)
- RX:
TPlutoBackendчитает int16 изiio_buffer, конвертирует 16→24-бит, упаковывает вTDDCIQPacket(seq синтезируется), вызываетOnDDCIQ. - TX: ✅
SendDUCIQапсемплит 192k→device-rate (целочисленно, линейная интерполяция; пресеты рейта кратны 192k), конвертирует 24→16-бит (>>8, full-scale MSB-aligned), инвертирует Q (симметрично RX) и кладёт в кольцевой FIFO. ОтдельныйTPlutoTXThreadльёт пары в tx-буфер libiio иiio_buffer_push(блокирующий push = пейсинг на device-rate); при underrun — нули (непрерывный full-duplex). Решение rate-mismatch (риск §6.2) — апсемпл в host (RX/TX делят одинsampling_frequency). TODO: полифазный FIR вместо линейной интерполяции для лучшего подавления образов на аплинке.
4.7 Усиление / телеметрия
- RX gain: контрол manual gain (0–73 дБ) / режим hw-AGC при
Caps.HasHWGain(можно расширить UI step-atten). WDSP AGC поверх. - Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
not Caps.HasPA. ✅ RSSI/температура Pluto:ReadTelemetry(backend) →ServicePlutoTelemetry(контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3 статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA).
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) в статус-бар.
- TODO: XO ppm-калибровка, buffer/timeout-контролы.
Фаза 4 — TX-тракт Pluto ✅
- ✅
SendDUCIQ24→16 + апсемпл 192k→device-rate + пейсинг (FIFO + push-поток, §4.6). - ✅ Управление мощностью через TX-аттенюацию (ADI-модель): непрерывный full-duplex
стрим, drive% →
hardwaregain(потолокPlutoTxMaxAttDb, дефолт −10 дБ, настраиваемый под внешний усилитель); на приёме −89.75 дБ (≈выкл). Маппинг/ политика — в контроллере (PlutoTxAttForDrive/ApplyPlutoTxAtten), бэкенд — тонкийSetTxAtten. Без PA → нет drive-байта/SWR/Alex/keyer. - TODO: проверка на железе (ориентация SSB по Q-инверсии, уровни); полифазный ресэмпл (см. §4.6); опц. зануление IQ на приёме.
Фаза 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 = visible−TxLOOffset (без LOError). RX-путь (LNB LO + beacon-дрейф) не тронут. Дисплей/VFO в downlink-терминах. - ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset /
границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот
(
QO100_SLOT), не засоряет таблицу трансвертеров openHPSDR. - TODO: проверка на железе.
Фаза 6 — Полировка
- Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон
ewsdrd.lpr(discovery/connect Pluto headless); кросс-платформенная сборка с/без libiio; доки.
6. Риски и открытые вопросы
- 2 м / 70 см ниже 325 МГц → требуется freq-range мод прошивки Pluto. Документировать; возможно гейтить эти кнопки при стоковой прошивке.
- ✅ TX sample-rate mismatch (192k WDSP vs ≥521k Pluto) — решено host-апсемплом
192k→device-rate в
SendDUCIQ(целочисленно, пресеты кратны 192k). Качество образов — линейная интерполяция; апгрейд до полифазного FIR в бэклоге. - QO-100 display-duplex — hardware full-duplex есть (Фаза 4), остаётся довести
SetDuplex(водопад RX во время TX); проверить латентность/буферы libiio. - LNB дрейф — калибровка
LnbPpmпо маяку (ручная/полуавтомат). - Ключ настроек — synthetic-MAC из serial (быстро) vs обобщение до строки.
- libiio как зависимость — динамическая загрузка, поведение при отсутствии.
- 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 |