# План интеграции 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) не даёт. Предлагаемая модель — отдельная подсистема/слот: ```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 (целочисленно, линейная интерполяция; пресеты рейта кратны 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** ✅ - ✅ `SendDUCIQ` 24→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: TX-стрим живёт всё время Run (Фаза 4), RX+TX одновременно. Остаётся display-duplex (`SetDuplex` — водопад RX во время TX). - TODO: `TQO100Settings` + раздельные RX/TX LO (§3.2), дисплей в downlink-терминах, довести `SetDuplex`. - UI: вход/выход в режим QO-100 (как XVTR-слот), поля LNB LO / ppm / TX atten. - ✅ полноценная работа через спутник. **Фаза 6 — Полировка** - Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr` (discovery/connect Pluto headless); кросс-платформенная сборка с/без libiio; доки. --- ## 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 в бэклоге. 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 |