Files
ewsdr/doc/PLUTO_INTEGRATION_PLAN.md
T
ew8bakandClaude Opus 4.8 a988849d3e feat: Pluto/AD936x SDR backend (discovery, RX, VHF band plan)
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>
2026-06-16 18:55:20 +03:00

321 lines
20 KiB
Markdown
Raw 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` конвертирует 24→16-бит, пишет в tx-буфер libiio с пейсингом
(модель FIFO-cushion, см. memory `project_txiq_pacing`). ⚠️ TX-rate Pluto
≥ 521 ksps, WDSP TXA отдаёт 192k — нужен ресэмпл 192k→PlutoTX или смена TX-rate.
### 4.7 Усиление / телеметрия
- RX gain: контрол manual gain (073 дБ) / режим 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**
- `SendDUCIQ` 24→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. Риски и открытые вопросы
1. **2 м / 70 см ниже 325 МГц** → требуется freq-range мод прошивки Pluto.
Документировать; возможно гейтить эти кнопки при стоковой прошивке.
2. **TX sample-rate mismatch** (192k WDSP vs ≥521k Pluto) — ресэмпл или смена
TX-rate WDSP.
3. **QO-100 full-duplex** — довести `SetDuplex` (сейчас TODO) до реального
одновременного 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 |