Add ADALM-Pluto / AD9361 support as a second hardware backend alongside openHPSDR, sharing the WDSP DSP pipeline and the existing controller API (UI stays decoupled from logic). - RadioBackend.pas: abstract TRadioBackend + TBackendCaps + TRadioDevice (Kind/URI/Serial). THPSDRNetwork now derives from it (state via virtual getters); TRadioController.FNetwork is the base type. - IIOBindings.pas: dynamic libiio loader (runs without libiio present). - PlutoBackend.pas: scan/probe-by-URI, connect, LO/rate/bandwidth/gain control, RX streaming thread (int16->24bit BE -> OnDDCIQ), Q conjugated to match WDSP IQ convention. Verified on LibreSDR (AD9361) over network. - Unified discovery: TDiscoverThread scans both backends; network Plutos found via direct ProbeURI (no mDNS needed). ConnectDevice dispatches by Dev.Kind (EnsureBackend swaps backend, preserving callbacks). - DeviceStore/DeviceForm: persist Kind/URI/Serial; save Pluto via AddSavedPluto so saved/autostart devices reconnect across restarts. - VHF/UHF band plan (BoardUtils, kind-aware): 6m..ADS-B for Pluto; fixes HF clamps (band detect, mouse-wheel 60 MHz cap, freq-display max). - SampleRateOverlay: configurable presets (Pluto 576k..5760k, >520 ksps), auto-width to fit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
20 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конвертирует 24→16-бит, пишет в tx-буфер libiio с пейсингом (модель FIFO-cushion, см. memoryproject_txiq_pacing). ⚠️ TX-rate Pluto ≥ 521 ksps, WDSP TXA отдаёт 192k — нужен ресэмпл 192k→PlutoTX или смена TX-rate.
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.
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, XO ppm, транспорт (USB/IP), buffer/timeout, температура/RSSI.
- Persist per-device по synthetic-MAC из serial.
Фаза 4 — TX-тракт Pluto
SendDUCIQ24→16 + пейсинг; ресэмпл/смена TX-rate (§4.6); TX atten вместо drive/PA; гейтинг PA/SWR/Alex/keyer. ✅ передача (SSB/CW/FM).
Фаза 5 — Режим QO-100
TQO100Settings+ раздельные RX/TX LO (§3.2), дисплей в downlink-терминах, калибровка LNB по маяку, full-duplex (довестиSetDuplex).- UI: вход/выход в режим QO-100 (как XVTR-слот), поля LNB LO / ppm / TX atten.
- ✅ полноценная работа через спутник.
Фаза 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) — ресэмпл или смена TX-rate WDSP.
- QO-100 full-duplex — довести
SetDuplex(сейчас TODO) до реального одновременного 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 |