mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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>
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
# План интеграции 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` конвертирует 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 (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**
|
||||
- `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 |
|
||||
Reference in New Issue
Block a user