From 1321e415f73f79d9579c43c5d5f4c1ba962bf284 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 19 Jun 2026 11:31:27 +0300 Subject: [PATCH] feat: Pluto board name + hardware telemetry in status bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Status bar / web now show Pluto/LibreSDR model + serial instead of "Unknown Board" (BoardDisplayName in controller; UI just renders it). - Field 3 shows AD936x chip temperature + RX RSSI for Pluto (no PA → Supply only for openHPSDR). Polled via backend.ReadTelemetry (~2 Hz), filtered for sensor glitches (plausibility window + slew-limit, with anti-stick) — fixes rare bogus -15°C readings. - Add libiio binding iio_channel_attr_read_double. - Remove stale root pluto_integration.MD (superseded by doc/PLUTO_INTEGRATION_PLAN.md). Co-Authored-By: Claude Opus 4.8 --- BoardUtils.pas | 15 ++ IIOBindings.pas | 3 + MainForm.pas | 31 ++- PlutoBackend.pas | 36 +++ RadioBackend.pas | 19 ++ RadioController.pas | 80 +++++++ WebAdapter.pas | 19 +- pluto_integration.MD | 518 ------------------------------------------- 8 files changed, 199 insertions(+), 522 deletions(-) delete mode 100644 pluto_integration.MD diff --git a/BoardUtils.pas b/BoardUtils.pas index 4a8c1a3..3377695 100644 --- a/BoardUtils.pas +++ b/BoardUtils.pas @@ -7,6 +7,8 @@ unit BoardUtils; interface function BoardTypeName(BoardType: Integer): string; +// Короткое человекочитаемое имя AD936x-платы по hw_model (Pluto / LibreSDR / прочие). +function PlutoModelName(const HwModel: string): string; // Индекс диапазона по частоте. Pluto=True → VHF/UHF план, иначе HF (openHPSDR). function FreqToBandIdx(Hz: Double; Pluto: Boolean = False): Integer; // Короткое имя диапазона для кнопки (зависит от плана). @@ -61,6 +63,19 @@ begin end; end; +function PlutoModelName(const HwModel: string): string; +var + L: string; +begin + // hw_model клонов AD936x обычно содержит маркер платы. Узнаём Pluto/LibreSDR, + // иначе показываем сырой hw_model, а при его отсутствии — обобщённое имя. + L := LowerCase(HwModel); + if Pos('libre', L) > 0 then Result := 'LibreSDR' + else if Pos('pluto', L) > 0 then Result := 'PlutoSDR' + else if HwModel <> '' then Result := HwModel + else Result := 'AD936x SDR'; +end; + function FreqToBandIdx(Hz: Double; Pluto: Boolean): Integer; var i: Integer; diff --git a/IIOBindings.pas b/IIOBindings.pas index d6fd0eb..5063e23 100644 --- a/IIOBindings.pas +++ b/IIOBindings.pas @@ -61,6 +61,8 @@ var // ---- Атрибуты ---- iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar; out val: clonglong): cint; cdecl; + iio_channel_attr_read_double: function(chn: Piio_channel; attr: PAnsiChar; + out val: cdouble): cint; cdecl; iio_channel_attr_write_longlong: function(chn: Piio_channel; attr: PAnsiChar; val: clonglong): cint; cdecl; iio_channel_attr_write: function(chn: Piio_channel; attr: PAnsiChar; @@ -161,6 +163,7 @@ begin iio_channel_enable := Sym('iio_channel_enable'); iio_channel_disable := Sym('iio_channel_disable'); iio_channel_attr_read_longlong := Sym('iio_channel_attr_read_longlong'); + iio_channel_attr_read_double := Sym('iio_channel_attr_read_double'); iio_channel_attr_write_longlong := Sym('iio_channel_attr_write_longlong'); iio_channel_attr_write := Sym('iio_channel_attr_write'); iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool'); diff --git a/MainForm.pas b/MainForm.pas index 8e85ed2..4a76fde 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -353,6 +353,7 @@ type // Public UI update (called from sync objects) procedure UpdateTXMeters; procedure UpdateStatusField7; // поле 7: PLL (HPSDR) или beacon (Pluto) + procedure UpdateStatusField3; // поле 3: Supply (HPSDR) или Temp/RSSI (Pluto) function BeaconStatusText: string; // компактный статус QO-100 beacon lock procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord); @@ -2972,6 +2973,8 @@ begin // QO-100 beacon lock: одна итерация контура (no-op если лок выключен). FController.ServiceBeaconLock; + // Pluto: опрос температуры/RSSI (троттлинг внутри; no-op для HPSDR). + FController.ServicePlutoTelemetry; // Сглаженное среднее (EMA) FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA; @@ -3179,6 +3182,32 @@ procedure TMainForm.UpdateTXMeters; // в TRadioController.ApplyHPStatus. begin UpdateStatusField7; // PLL (openHPSDR) или статус beacon lock (Pluto) + UpdateStatusField3; // Supply (openHPSDR) или Temp/RSSI (Pluto) +end; + +procedure TMainForm.UpdateStatusField3; +// Поле 3 статус-бара. openHPSDR — supply V/A (HP status). Pluto — температура +// чипа и RSSI приёмника (нет PA → supply неприменим). Значения берём из +// контроллера (FLastPlutoTempC/RSSIdB / FLastSupplyV/A) — логика опроса в ядре. +var + S: string; +begin + // Pluto: нет PA/supply — показываем аппаратную телеметрию AD936x. + if FController.IsPluto then + begin + if not FController.FRunning then begin SetStatusText(3, 'Pluto --'); Exit; end; + S := ''; + if FController.FLastPlutoTempC > TELEMETRY_NONE then + S := Format('Temp %.0fC', [FController.FLastPlutoTempC]); + if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then + begin + if S <> '' then S := S + ' '; + S := S + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]); + end; + if S = '' then S := 'Pluto --'; + SetStatusText(3, S); + Exit; + end; if not FController.FRunning then begin @@ -3674,7 +3703,7 @@ begin UpdateVfoDisplay; SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress); - SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType)); + SetStatusText(0, 'Board: ' + FController.BoardDisplayName); FSpecView.AGCTop := FController.FAGCTop; SetStatusText(6, 'SEQ OK'); SetStatusText(2, 'Running'); diff --git a/PlutoBackend.pas b/PlutoBackend.pas index e51ed42..020397f 100644 --- a/PlutoBackend.pas +++ b/PlutoBackend.pas @@ -95,6 +95,7 @@ type procedure SendFullHP; override; procedure SendPTT(Active: Boolean); override; procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override; + function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; override; end; // Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device @@ -578,4 +579,39 @@ begin if FConnected then ApplyGain; end; +function TPlutoBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean; +// Опрос аппаратной телеметрии ad9361-phy (вызывается контроллером, не из RX-потока): +// temp0/input — температура чипа в миллиградусах C (longlong); +// voltage0/rssi — RSSI входного тракта в дБ (double; строка вида '12.34 dB', +// libiio парсит ведущее число). Валиден только во время приёма. +var + Ch: Piio_channel; + raw: clonglong; + d: cdouble; +begin + TempC := TELEMETRY_NONE; + RSSIdB := TELEMETRY_NONE; + Result := False; + if (not FConnected) or (FPhy = nil) or (not IIOLoaded) then Exit; + + Ch := iio_device_find_channel(FPhy, 'temp0', 0); + if (Ch <> nil) and (iio_channel_attr_read_longlong(Ch, 'input', raw) = 0) then + begin + TempC := raw / 1000.0; + Result := True; + end; + + // RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор). + if FStreaming then + begin + Ch := iio_device_find_channel(FPhy, 'voltage0', 0); + if (Ch <> nil) and Assigned(iio_channel_attr_read_double) + and (iio_channel_attr_read_double(Ch, 'rssi', d) = 0) then + begin + RSSIdB := d; + Result := True; + end; + end; +end; + end. diff --git a/RadioBackend.pas b/RadioBackend.pas index c38c1a7..2c98203 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -73,6 +73,13 @@ type PRadioDevice = ^TRadioDevice; TRadioDeviceArray = array of TRadioDevice; +const + // Признак «значение телеметрии недоступно» (ReadTelemetry). Заведомо вне + // физического диапазона температур/RSSI. + TELEMETRY_NONE = -1.0E9; + +type + // Колбэки данных (вызываются из рабочих потоков бэкенда). TOnDeviceFound = procedure(const Dev: TRadioDevice) of object; TOnDDCIQPacket = procedure(DDCIndex: Integer; @@ -143,6 +150,11 @@ type procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual; procedure SetSpeakerAudio(AEnabled: Boolean); virtual; + // Аппаратная телеметрия, не приходящая в потоке (опрашивается контроллером). + // Pluto/AD936x: температура чипа (°C) и RSSI приёмника (дБ). HPSDR — нет такой + // (использует HP-status), возвращает False. Недоступные значения = TELEMETRY_NONE. + function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; virtual; + // ---- Свойства состояния (полиморфные через виртуальные геттеры) ---- property Connected: Boolean read GetConnected; property Running: Boolean read GetRunning; @@ -231,4 +243,11 @@ procedure TRadioBackend.SetSpeakerAudio(AEnabled: Boolean); begin end; +function TRadioBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean; +begin + TempC := TELEMETRY_NONE; + RSSIdB := TELEMETRY_NONE; + Result := False; +end; + end. diff --git a/RadioController.pas b/RadioController.pas index d89a38f..4a97ddd 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -272,6 +272,11 @@ type // ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ---- FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double; FLastPLLLock: Boolean; + // Pluto-телеметрия (нет HP-status): температура чипа °C / RSSI приёмника дБ. + // Опрашиваются ServicePlutoTelemetry с UI-таймера; TELEMETRY_NONE = нет данных. + FLastPlutoTempC, FLastPlutoRSSIdB: Double; + FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг) + FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание) // ---- RX/seq телеметрия data-path (пишется из сетевого колбэка OnDDCIQ) ---- FRXPacketCount: LongWord; // счётчик принятых IQ пакетов @@ -515,6 +520,13 @@ type function BackendCaps: TBackendCaps; // True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF). function IsPluto: Boolean; + // Человекочитаемое имя подключённой платы для статус-строки (UI/Web). + // HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть). + function BoardDisplayName: string; + // Опрос аппаратной телеметрии Pluto (температура/RSSI) с троттлингом — зовётся + // с UI-таймера (MeterTimer @10 Гц), как ServiceBeaconLock. No-op для HPSDR. + // Результат — в FLastPlutoTempC/FLastPlutoRSSIdB (UI читает и форматирует). + procedure ServicePlutoTelemetry; // Список URI для probe сетевых Pluto (mDNS недоступен) — задаёт фронтенд // перед Discover (сохранённые Pluto + ручной ввод IP). procedure SetPlutoProbeURIs(const URIs: array of string); @@ -551,6 +563,8 @@ begin FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True; FFMStepOn := True; FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1; + FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE; + FPlutoTeleTick := 0; FPlutoTempBad := 0; FillChar(FDevMAC, SizeOf(FDevMAC), 0); end; @@ -2727,6 +2741,8 @@ begin end; FRXPacketCount := 0; FActiveDDC := 0; + FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE; + FPlutoTeleTick := 0; FPlutoTempBad := 0; Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP) end; @@ -3021,6 +3037,70 @@ begin Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto); end; +function TRadioController.BoardDisplayName: string; +var + Dev: THPSDRDevice; +begin + if FNetwork = nil then Exit(''); + Dev := FNetwork.Device; + if Dev.Kind = bkPluto then + begin + Result := PlutoModelName(Dev.Model); + // Серийник дописываем, только если это реальный hw_serial, а не URI-fallback + // ('usb:1.5.5' / 'ip:…'), который PlutoBackend кладёт при отсутствии серийника. + if (Dev.Serial <> '') and (Pos('usb:', Dev.Serial) = 0) + and (Pos('ip:', Dev.Serial) = 0) then + Result := Result + ' SN:' + Dev.Serial; + end + else + Result := BoardTypeName(Dev.BoardType); +end; + +procedure TRadioController.ServicePlutoTelemetry; +const + POLL_MS = 500; // опрос ~2 Гц: чтение attr — USB round-trip, чаще ни к чему + TEMP_MIN_C = 0.0; // работающий AD936x всегда теплее окружающей (само-нагрев) + TEMP_MAX_C = 120.0; // абсолютный потолок (датчик исправен) + TEMP_STEP_C = 12.0; // макс. физичный шаг между опросами (тепловая инерция чипа) + TEMP_FORCE = 3; // принять «выброс», если держится N раз подряд (не залипаем) +var + t, r: Double; + now_: QWord; +begin + if (FNetwork = nil) or (not FRunning) or (FNetwork.Caps.Kind <> bkPluto) then Exit; + now_ := GetTickCount64; + if (FPlutoTeleTick <> 0) and (now_ - FPlutoTeleTick < POLL_MS) then Exit; + FPlutoTeleTick := now_; + if not FNetwork.ReadTelemetry(t, r) then Exit; + + // Температура: датчик AD9361 иногда отдаёт мусор (мид-конверсия / контеншн на + // USB-контексте с RX-потоком) — наблюдалось редкое -15 °C. Фильтруем: жёсткое + // окно правдоподобия + slew-limit по тепловой инерции; устойчивый «выброс» + // (реальный скачок) всё же принимаем через TEMP_FORCE опросов, чтобы не залипнуть. + if t > TELEMETRY_NONE then + begin + if (t >= TEMP_MIN_C) and (t <= TEMP_MAX_C) then + begin + if (FLastPlutoTempC <= TELEMETRY_NONE) or (Abs(t - FLastPlutoTempC) <= TEMP_STEP_C) then + begin + FLastPlutoTempC := t; FPlutoTempBad := 0; + end + else + begin + Inc(FPlutoTempBad); + if FPlutoTempBad >= TEMP_FORCE then + begin FLastPlutoTempC := t; FPlutoTempBad := 0; end; + end; + end; + // вне окна правдоподобия — молча отбрасываем, держим прошлое валидное значение + end; + + // RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден. + if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r; + + Changed(rfSupply); // поле телеметрии статус-бара (поле 3); web-адаптер тоже +end; + procedure TRadioController.SetPlutoProbeURIs(const URIs: array of string); var i: Integer; begin diff --git a/WebAdapter.pas b/WebAdapter.pas index 46a3ad0..3677b84 100644 --- a/WebAdapter.pas +++ b/WebAdapter.pas @@ -19,7 +19,7 @@ interface uses Classes, SysUtils, - RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, BoardUtils, Settings; + RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, RadioBackend, BoardUtils, Settings; type // UI/оркестрационные операции, которые адаптер дёргает у хозяина (пока не @@ -394,7 +394,7 @@ begin begin if FController.FRunning then StatusText := 'Running' else StatusText := 'Connected'; - BoardText := 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType); + BoardText := 'Board: ' + FController.BoardDisplayName; IPText := 'IP: ' + FController.FNetwork.Device.IPAddress; end else @@ -414,7 +414,20 @@ begin if FController.FLastPLLLock then PLLText := 'PLL OK' else PLLText := 'PLL?'; - if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then + if FController.IsPluto then + begin + // Pluto: нет PA/supply — температура чипа + RSSI приёмника (telemetry-поле). + SupplyText := ''; + if FController.FLastPlutoTempC > TELEMETRY_NONE then + SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC]); + if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then + begin + if SupplyText <> '' then SupplyText := SupplyText + ' '; + SupplyText := SupplyText + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]); + end; + if SupplyText = '' then SupplyText := 'Pluto --'; + end + else if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then SupplyText := 'Supply n/a' else if FController.FLastSupplyV >= 0 then begin diff --git a/pluto_integration.MD b/pluto_integration.MD deleted file mode 100644 index 463f75d..0000000 --- a/pluto_integration.MD +++ /dev/null @@ -1,518 +0,0 @@ -# Интеграция ADALM-PLUTO (libiio) в EWSDR — полный план работ - -> Цель: добавить поддержку **PlutoSDR** (AD9363/AD9364) через **libiio** как -> второй тип «железа» наряду с трансиверами **openHPSDR**. Discovery-форма -> остаётся прежней, но дополняется board-специфическими параметрами Pluto. -> В UI скрываются функции, которых у Pluto нет, и появляются те, что есть -> только у Pluto. Весь DSP по-прежнему идёт через **WDSP**. Платформенно- -> специфичный код выносится в отдельные файлы, чтобы не загромождать -> существующую логику openHPSDR. - ---- - -## 0. Краткое резюме решения - -1. Ввести **тонкий слой абстракции backend'а** (`SDRBackend.pas`, - `TSDRBackend`), описывающий ровно тот API, которым `MainForm` сегодня - пользуется у `THPSDRNetwork`. Существующий `THPSDRNetwork` обворачивается - адаптером `THPSDRBackend` (код самого `HPSDRNetwork.pas` не трогаем). -2. Новый backend Pluto — `PlutoBackend.pas` (`TPlutoBackend`), и низкоуровневые - биндинги libiio — `IIO.pas` (по образцу `WDSP.pas`). -3. Поток IQ от Pluto (int16 LE) **переупаковывается в тот же 24-битный - big-endian формат**, что и openHPSDR, и подаётся в существующий - `TWDSPEngine.PushDDCPacket` — DSP-конвейер не меняется. -4. Возможности железа описываются записью **`TBoardCaps`** (`BoardCaps.pas`). - `MainForm`/`SettingsForm` гейтят UI по `Caps`, а не по жёстким спискам - `BoardType in [...]`. -5. Discovery-форма расширяется панелью Pluto-параметров (URI, gain mode, - gain, RF bandwidth, sample rate, диапазон LO) — раскладка прежняя. -6. Минимальный sample rate Pluto = **≈521 кS/s** (без программируемого FIR) - учитывается в `SampleRateOverlay` и в валидации. - ---- - -## 1. Текущая архитектура (контекст) - -Стек: **Lazarus / Free Pascal** (FPC 3.2 / Lazarus 2.x), LCL, кросс-платформенно -(Windows / Linux / macOS). DSP — внешняя **WDSP** (`wdsp.dll` / `libwdsp.so` / -`libwdsp.dylib`). Аудио — PortAudio (`AudioInput.pas` / `AudioOutput.pas`). - -### 1.1 Путь данных RX -``` -THPSDRNetwork (UDP, HPSDRNetwork.pas) - → receive thread → MainForm.OnDDCIQCB(DDCIndex, TDDCIQPacket) - → FDSPEngine.PushDDCPacket(IQData, 0, SamplesPerFrame) // сырые 24-bit BE байты - → WDSP DSP-поток: декод 24-bit BE → double (scale 1/8388608), - RXA-цепь → OnAudio (AudioOutput) + OnSpectrum/OnWaterfall -``` -Декод IQ зашит в `TWDSPEngine.PushIQItemToDSP` (`WDSPEngine.pas:1199-1209`): -3 байта I + 3 байта Q, big-endian, `SCALE = 1/8388608.0`. - -### 1.2 Путь данных TX -``` -Mic: OnMicPacketCB (HW-mic) или AudioInput (звуковая карта) - → FDSPEngine.PushTXMicSamples* - → WDSP TXA-цепь → OnTXIQReady(double IQ @ 192k, MainForm.pas:6795) - → батчинг по 240 пар → FNetwork.SendDUCIQ → UDP (порт 1029) -``` -Масштаб TX: `V * 8388607.0`, кламп в 24-bit (`MainForm.pas:6801`). - -### 1.3 Discovery / устройство -- `DeviceForm.pas` (`TDeviceDialog`): слева сохранённые устройства - (`TSavedDevice`: Name / IPAddress / BoardType / AutoStart → `hpsdr_devices.ini`), - справа найденные. Кнопка DISCOVER → callback `OnDiscover` → - `MainForm.BtnDiscoverFromDialog` → `TDiscoverThread` → `FNetwork.Discover` - → `OnDeviceFound` → `FDeviceDialog.AddDiscovered(IP, name, BoardType)`. -- Устройство в сети идентифицируется по **IP**, а настройки хранятся по **MAC** - (`Settings.pas`, JSON по `MacToStr`). - -### 1.4 Поверхность API `THPSDRNetwork`, которой пользуется MainForm -(`HPSDRNetwork.pas:204-249`) — именно это станет интерфейсом backend'а: -- `Discover`, `Connect(THPSDRDevice)`, `Disconnect` -- `ConfigureDDCs(NumDDCs, SampleRate, ADCSource, Dither, Random)` -- `SetRunAndFreq(Run, RXFreq, TXFreq, Drive)` -- `UpdateState(RXFreq, TXFreq, Drive, TX, PAEnabled, AlexEnabled)` -- `SendDUCIQ(I, Q)`, `SendDDCAudio`, `SendDUCSpecific`, `SetStepAtten`, - `SetAlexConfig`, `SetXvtrMode`, `SendPTT`, `SendFullHP` -- Свойства: `Connected`, `Running`, `Device`, `LastError` -- Колбэки: `OnDeviceFound`, `OnDDCIQ`, `OnMicPacket`, `OnHPStatus`, `OnWideband` - -### 1.5 Board-специфичные места (по `BoardType`) -- `BoardUtils.BoardTypeName`, `FreqToBandIdx`. -- `HPSDRProtocol`: `BoardSupportsSupplyVoltage/Current`, `ADCToWatts*`, - `ADCToSupplyVolts/Current`. -- `HPSDRNetwork.ConfigureDDCs`: число ADC и базовый DDC (`BoardType in [3,4,5,10]`). -- `MainForm`: телеметрия (supply V/A, fwd/rev power, SWR) в `OnHPStatusCB`, - step-attenuator (`FAtten`), Alex/XVTR, wideband-панель, dither/random. -- `SettingsForm`: вкладки Antenna/Alex, Transverter, Tip/Ring (Orion), - ADC dither/random, PA-калибровка. - -### 1.6 Линковка внешних библиотек -- `WDSP.pas`: `WDSP_LIB` по платформе; на macOS `{$linklib wdsp}`; weak — - если библиотеки нет, указатели `nil`, проверяется `Assigned(@WDSPwisdom)`. -- `ewsdr.lpi`: `CustomOptions = -k-L/usr/local/lib`. -- Сборка: `lazbuild --ws=qt6 ewsdr.lpr` (`BUILD.MD`). - ---- - -## 2. Pluto SDR / libiio — факты и ограничения - -**Железо:** ADALM-PLUTO на AD9363 (заводской), прошивкой разблокируется до -возможностей AD9364/AD9361. **Один RX и один TX** канал (один ADC / один DAC). - -**libiio API (нужный минимум):** -- Контекст: `iio_create_context_from_uri("ip:192.168.2.1" | "ip:pluto.local" - | "usb:1.x.x" | "local:")`. -- Discovery: `iio_create_scan_context(NULL,0)` → - `iio_scan_context_get_info_list` → `iio_context_info_get_uri/_description`. -- Устройства: `ad9361-phy` (управление), `cf-ad9361-lpc` (RX-стрим), - `cf-ad9361-dds-core-lpc` (TX-стрим). -- Каналы phy: `voltage0` (RX, input) / `voltage0` (TX, output) — gain, - rf_bandwidth, sampling_frequency; `altvoltage0` (RX LO), `altvoltage1` (TX LO). -- Стрим: `iio_device_create_buffer`, `iio_buffer_refill` (RX), - `iio_buffer_push` (TX); каналы `voltage0`(I)+`voltage1`(Q), формат **int16 LE**, - интерливинг I/Q. Полезных бит — 12 (знаковые, в младших битах). -- Атрибуты (строки): `frequency` (LO, Hz), `sampling_frequency` (Hz), - `rf_bandwidth` (Hz), `hardwaregain` (dB), `gain_control_mode` - (`manual|slow_attack|fast_attack|hybrid`), `rssi`, `temp0` (xadc, mC). - -**Платформенные ограничения Pluto (важно для UI/валидации):** -| Параметр | Значение | -|---|---| -| Sample rate (min) | **≈520.83 кS/s** без FIR; ~65 кS/s только с загруженным FIR | -| Sample rate (max) | 61.44 МS/s (по USB 2.0 реально ~5–6 МS/s непрерывно) | -| Частота LO (AD9363) | 325 МГц – 3.8 ГГц | -| Частота LO (AD9364, разблок.) | 70 МГц – 6 ГГц | -| RX gain | 0–73 дБ (manual) + режимы AGC | -| TX «gain» | аттенюатор −89.75…0 дБ | -| RF bandwidth | 200 кГц – 56 МГц | -| Транспорт | USB / Ethernet-gadget; интерфейс — URI, не broadcast | - -**Чего у Pluto НЕТ:** нескольких DDC/ADC, dither/random, Alex-фильтров/реле -антенн, аппаратного wideband-ADC, PureSignal, измерения напряжения/тока питания, -fwd/rev power и КСВ, аппаратного step-attenuator, HW-CW-keyer. XVTR-логика -возможна только как программный сдвиг частоты (без реле). - ---- - -## 3. openHPSDR vs Pluto — матрица возможностей - -| Возможность | openHPSDR | Pluto | Действие в UI | -|---|---|---|---| -| Несколько DDC | да | нет (1 RX) | скрыть мульти-DDC | -| Dither / Random ADC | да | нет | скрыть в Advanced | -| Step attenuator (RX) | да | нет (есть RX gain dB) | заменить контрол | -| RX gain / AGC mode | косвенно | да (manual/AGC) | показать для Pluto | -| Drive level (TX) | 0–255 | TX atten dB | переинтерпретировать | -| Alex антенны/фильтры | да | нет | скрыть вкладку | -| XVTR (реле) | да | только сдвиг | показать упрощённо | -| Wideband ADC панель | да | нет | скрыть | -| Supply V / A | часть плат | нет (есть temp) | заменить на temp | -| Fwd/Rev power, SWR | да | нет | скрыть SWR/PWR | -| RF bandwidth | фикс. | настраиваемый | показать для Pluto | -| Диапазон частот | 0–61.44М (HF/6м) | 70М–6Г | расширить ввод/бэнд-план | -| Sample rates | 48к–1536к | 521к–~6М | свой набор в overlay | - ---- - -## 4. Архитектурное решение - -### 4.1 Слой абстракции backend'а — `SDRBackend.pas` (НОВЫЙ) -Абстрактный класс `TSDRBackend`, повторяющий публичный контракт -`THPSDRNetwork` (раздел 1.4). MainForm работает с `FBackend: TSDRBackend` -вместо прямого `FNetwork`. Колбэки переносятся на backend. - -```pascal -type - TSDRKind = (sdrHPSDR, sdrPluto); - - TSDRBackend = class - protected - FOnDDCIQ: TOnDDCIQPacket; // (повторно используем типы из HPSDRNetwork) - FOnMic: TOnMicPacket; - FOnHPStatus: TOnHPStatus; // для Pluto заполняем синтетически (RSSI/temp) - FOnWideband: TOnWidebandFrame;// Pluto: не вызывается - FOnDeviceFound: TOnDeviceFound; - public - function Kind: TSDRKind; virtual; abstract; - function Caps: TBoardCaps; virtual; abstract; - function Discover(TimeoutMs: Integer): THPSDRDeviceArray; virtual; abstract; - function Connect(const Dev: THPSDRDevice): Boolean; virtual; abstract; - procedure Disconnect; virtual; abstract; - procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ...); virtual; abstract; - procedure SetRunAndFreq(Run: Boolean; RXFreqHz, TXFreqHz: Double; Drive: Byte); virtual; abstract; - procedure UpdateState(...); virtual; abstract; - procedure SendDUCIQ(const I, Q: array of Integer); virtual; abstract; - // ... остальные методы из 1.4; для неподдерживаемых — пустая реализация в Pluto - property Connected: Boolean read FConnected; - property Running: Boolean read FRunning; - property Device: THPSDRDevice read FDevice; - property LastError: string read FLastError; - property OnDDCIQ: ... ; // + остальные колбэки - end; -``` - -**Почему не интерфейс (`interface`/IUnknown):** класс проще для FPC-кода без -ARC-сюрпризов, повторяет существующий стиль и не требует менять -`HPSDRNetwork.pas`. - -### 4.2 `THPSDRBackend` (адаптер) — в `SDRBackend.pas` или отдельно -Тонкая обёртка: внутри `FNet: THPSDRNetwork`, делегирует все методы и -пробрасывает колбэки. `Caps` возвращает capabilities по `BoardType`. -`HPSDRNetwork.pas` остаётся **без изменений**. - -### 4.3 `TPlutoBackend` — `PlutoBackend.pas` (НОВЫЙ) -Реализует `TSDRBackend` поверх libiio. Внутри: -- RX-поток: `iio_buffer_refill` → int16→24-bit BE repack → синтез - `TDDCIQPacket` (seq, SamplesPerFrame) → `FOnDDCIQ(0, pkt)`. -- TX-поток: очередь как в HPSDR (`DUC_TX_QUEUE_SIZE`), пары I/Q (24-bit) - → int16 → `iio_buffer_push`. -- Телеметрия-поток (~5–10 Гц): читает `rssi`/`temp0` → синтетический - `THighPriorityStatus` → `FOnHPStatus` (S-метр + температура). -- `Discover` → `iio_create_scan_context`; маппинг URI/serial → `THPSDRDevice` - (см. 7.3 про идентификацию). - -### 4.4 `IIO.pas` — биндинги libiio (НОВЫЙ) -По образцу `WDSP.pas`. **Рекомендация: динамическая загрузка через `dynlibs`** -(`LoadLibrary`/`GetProcedureAddress`) с `function IIOAvailable: Boolean` — -тогда приложение запускается даже без установленной libiio, а Pluto просто -недоступен (как сейчас weak-WDSP). Имена: `libiio.so.0`/`libiio.so` (Linux), -`iio.dll` (Windows), `libiio.dylib`/framework (macOS). - -### 4.5 `BoardCaps.pas` — описание возможностей (НОВЫЙ) -```pascal -type - TBoardCaps = record - Kind: TSDRKind; - DisplayName: string; - HasMultiDDC, HasDitherRandom, HasStepAtten, HasRXGain, - HasAGCModes, HasAlex, HasXvtrRelays, HasWideband, - HasSupplyVolts, HasSupplyCurrent, HasPower, HasSWR, - HasRFBandwidth, HasHWMic, HasPureSignal: Boolean; - MinSampleRate, MaxSampleRate: Integer; - MinFreqHz, MaxFreqHz: Double; - SampleRates: array of Integer; // что показывать в overlay - end; - -function BoardCapsFor(Kind: TSDRKind; BoardType: Integer): TBoardCaps; -``` -Единый источник правды для гейтинга UI — заменяет разбросанные -`if BoardType in [...]`. - ---- - -## 5. Список новых и изменяемых файлов - -**Новые:** -- `IIO.pas` — биндинги libiio (динамическая загрузка). -- `PlutoBackend.pas` — `TPlutoBackend` (RX/TX/telemetry потоки, repack). -- `SDRBackend.pas` — `TSDRBackend` + `THPSDRBackend`-адаптер + `TSDRKind`. -- `BoardCaps.pas` — `TBoardCaps` + `BoardCapsFor`. -- `PlutoUtils.pas` (опц.) — частотные бэнд-планы/пресеты для VHF/UHF/SHF. - -**Изменяемые:** -- `MainForm.pas` — `FNetwork: THPSDRNetwork` → `FBackend: TSDRBackend` - (+ фабрика по `Kind`); гейтинг UI по `FBackend.Caps`; телеметрия; - drive/gain; START/STOP. -- `DeviceForm.pas` — `TSavedDevice` += поля Pluto; панель Pluto-параметров; - `AddDiscovered` += URI/Kind. -- `SettingsForm.pas` — показывать/прятать вкладки и контролы по Caps; - новые контролы Pluto (RX gain, AGC mode, RF bandwidth). -- `BoardUtils.pas` — имя для Pluto, бэнд-план VHF/UHF (или вынести в PlutoUtils). -- `Settings.pas` — ключ устройства (MAC vs serial), новые поля (gain mode, - gain, rf_bandwidth, pluto sample rate), set частот для Pluto. -- `SampleRateOverlay.pas` — набор скоростей зависит от backend/Caps. -- `ewsdr.lpr` — добавить новые units в `uses`. -- `ewsdr.lpi` — пути/линковка libiio (если link-time), новые файлы в проекте. -- `bundle_app.sh` — включать libiio в macOS-бандл. -- `BUILD.MD` — отметить зависимость libiio (опциональная). - ---- - -## 6. Детальный разбор по подсистемам - -### 6.1 libiio биндинги (`IIO.pas`) -Объявить нужные типы-указатели (`p_iio_context`, `p_iio_device`, -`p_iio_channel`, `p_iio_buffer`, `p_iio_scan_context`, `p_iio_context_info`) -и функции: -- контекст: `iio_create_context_from_uri`, `iio_context_destroy`, - `iio_context_get_device`, `iio_context_find_device`. -- scan: `iio_create_scan_context`, `iio_scan_context_get_info_list`, - `iio_context_info_get_uri`, `iio_context_info_get_description`, - `iio_context_info_list_free`, `iio_scan_context_destroy`. -- каналы: `iio_device_find_channel`, `iio_channel_enable`, - `iio_channel_attr_write_longlong/_read_longlong`, `..._write/_read` (строки), - `iio_channel_attr_write_double` (для gain). -- буферы: `iio_device_create_buffer`, `iio_buffer_destroy`, - `iio_buffer_refill`, `iio_buffer_push`, `iio_buffer_first`, - `iio_buffer_step`, `iio_buffer_end`, `iio_channel_read/_write` (или прямой - доступ к памяти буфера). -- `iio_strerror`. - -Все `cdecl`. Использовать `TIIO_*` typedef'ы. Реализовать `InitIIO/IIOAvailable` -(грузим библиотеку, резолвим символы; при отсутствии — `False`). - -### 6.2 Backend-абстракция и подключение в MainForm -- Завести фабрику: `function CreateBackend(Kind: TSDRKind): TSDRBackend;`. -- В `MainForm` заменить тип поля `FNetwork` на `FBackend: TSDRBackend` - (или оставить имя `FNetwork`, сменив тип — меньше правок в тексте). -- Колбэки (`OnDeviceFound`, `OnDDCIQCB`, `OnHPStatusCB`, `OnMicPacketCB`, - `OnWidebandCB`) переключить на backend. -- При выборе устройства в discovery узнаём `Kind` (Pluto vs HPSDR) и создаём - нужный backend перед `Connect`. -- `BoardTypeName`-вывод в статус-баре и Web (`MainForm:3172`, - `3257`) — через `FBackend.Caps.DisplayName`. - -### 6.3 Discovery-форма (`DeviceForm.pas`) -- `TSavedDevice` расширить: `Kind: TSDRKind; URI: string; - PlutoGainMode: Integer; PlutoGainDB: Double; PlutoRFBW: Integer; - PlutoSampleRate: Integer;` (BoardType остаётся для HPSDR). -- INI (`hpsdr_devices.ini`): дописать поля; обратная совместимость — - отсутствующие читать с дефолтами, `Kind` по умолчанию `sdrHPSDR`. -- DISCOVER запускает **обе** ветки: HPSDR broadcast и libiio-scan (если - `IIOAvailable`). В списке найденных помечать тип: `[HPSDR]` / `[PLUTO]`. - `AddDiscovered` получает доп. параметры (URI, Kind, отображаемое имя). -- Раскладка прежняя; снизу/справа добавить **сворачиваемую панель - «Pluto parameters»**, активную только когда выбранное/добавляемое - устройство — Pluto: поля URI, Gain mode (combo), Gain (dB), RF bandwidth, - Sample rate. Для HPSDR-устройств панель скрыта/неактивна. -- `BtnConnect` отдаёт в результат не только IP, но и `Kind`/URI/Pluto-параметры. - -### 6.4 Идентификация устройства и Settings -- HPSDR: ключ настроек — **MAC**. Pluto: MAC нет → использовать **serial - number** Pluto (атрибут контекста `hw_serial`/usb serial) как ключ. - В `Settings.pas` обобщить `MacToStr` до строкового device-id, либо завести - отдельную ветку ключей `pluto:`. -- `THPSDRDevice` дополнить (или ввести `TSDRDeviceInfo`) полями `Kind`, - `URI`, `Serial`. Чтобы не плодить типы, можно положить serial в поле `MAC` - как хэш — но честнее расширить запись. (Решить в Фазе 1, см. открытые вопросы.) -- Новые per-device поля в `TGlobalSettings` (или отдельная секция `pluto`): - `PlutoGainMode`, `PlutoGainDB`, `PlutoRFBW`, `PlutoLOOffset`. - -### 6.5 Sample rate (`SampleRateOverlay.pas` + валидация) -- Текущий `SPAN_RATES = (48k..1536k)` сделать зависимым от `Caps.SampleRates`. -- Для Pluto предложить набор: `521000, 768000, 1000000, 1536000, 2000000, - 3000000` (верх — с оглядкой на USB-троттлинг; пометить «USB limit»). -- Жёсткая валидация: для Pluto `SampleRate >= MinSampleRate(521000)`. - При попытке выставить ниже — клампить + статус-предупреждение. -- `FSpanHz` = выбранный rate (как сейчас). WDSP rate-agnostic; пересоздание - движка через существующий `RecreateDSPEngine/ChangeSampleRate`. -- **CPU/латентность:** при 521к–3М `FBufSize = BufSize*Rate/48000` большой - (≈5.5k–32k). Проверить нагрузку; при необходимости — уменьшить базовый - `BufSize` для Pluto или включить программируемый FIR Pluto для понижения - rate. (Замер в Фазе 4.) - -### 6.6 RX-поток IQ и формат сэмплов (ключевой момент) -- Pluto отдаёт **int16 LE** (12 значащих бит). openHPSDR/WDSP ждёт **24-bit BE**, - scale `1/8388608`. -- **Решение (минимальная инвазивность):** в `TPlutoBackend` RX-потоке - переупаковывать каждый int16-сэмпл в 3-байтовый big-endian с тем же - динамическим диапазоном: `v24 = v16 << 12` (12→24 бита, знак сохраняется), - затем разложить в 3 байта BE. Это даёт корректный масштаб для существующего - декодера `PushIQItemToDSP` без изменений в WDSPEngine. - - Альтернатива: добавить в `TWDSPEngine` метод `PushIQDoubles(constI,Q)` и - кормить double напрямую (чище, но трогает WDSPEngine — оставить как опцию). -- Размер «пакета»: эмулируем `TDDCIQPacket` — заполняем `Seq` (монотонно), - `SamplesPerFrame` (например 238, как у HPSDR, чтобы переиспользовать ветку - `MainForm.OnDDCIQCB`), и режем буфер `iio_buffer_refill` на такие кадры. -- `FActiveDDC`/seq-контроль в `OnDDCIQCB` продолжают работать (DDCIndex=0). - -### 6.7 TX-поток -- `OnTXIQReady` (MainForm:6795) уже даёт 24-bit-клампленные `I/Q` пары → - `FBackend.SendDUCIQ`. В `TPlutoBackend.SendDUCIQ` конвертировать - 24-bit → int16 (`v16 = v24 >> 12` или scale), писать в TX-буфер, - `iio_buffer_push` в отдельном потоке (равномерная подача, как DUC-sender). -- TX-частота: Pluto имеет отдельный TX LO (`altvoltage1`). Поддержать - split/duplex; при не-дуплексе LO RX=TX. -- «Drive level» (0–100 слайдер) маппить в TX `hardwaregain` (аттенюатор - 0…−89.75 дБ): 100%→0 дБ, 0%→−89.75 дБ (или разумный практический диапазон, - напр. 0…−40 дБ). См. `CalcDriveByte`/`TrkDriveChange` в MainForm. - -### 6.8 Gain / AGC модель -- HPSDR: step-attenuator (`FAtten`, 0/10/20 дБ) + WDSP-AGC. -- Pluto: RX `gain_control_mode` (manual/slow/fast/hybrid) + `hardwaregain` (дБ). -- В UI: если `Caps.HasRXGain` — показать RX-gain слайдер (0–73 дБ) и combo - AGC-mode вместо step-attenuator. Когда mode≠manual — слайдер gain disable. -- WDSP-AGC RXA оставить включённым для пост-обработки (как у HPSDR), но - основной gain — аппаратный Pluto. - -### 6.9 Телеметрия (S-meter / temp вместо supply/SWR) -- `OnHPStatusCB` (MainForm:3286) и панель статуса завязаны на supply V/A, - fwd/rev power, SWR. -- Pluto: `Caps.HasSupplyVolts/Current/Power/SWR = False`. В этих местах - показывать прочерк/скрывать. Вместо supply показать `temp0` (°C) Pluto. -- S-метр: WDSP `GetSMeterDBm` остаётся основным (по IQ); RSSI Pluto можно - показывать как доп. индикатор. -- TX-метр (см. memory `project_smeter_tx`: мощность+КСВ): для Pluto скрыть КСВ - и показывать только относительный уровень / TX gain (нет датчиков мощности). - -### 6.10 Частотный диапазон / LO / XVTR -- Расширить допустимый ввод частоты до 70 МГц–6 ГГц (по `Caps.MinFreqHz/Max`). -- Бэнд-план: `BoardUtils.FreqToBandIdx` сейчас только HF/6м. Добавить - VHF/UHF/SHF бэнды (2м, 70см, 23см, и т.д.) для Pluto (в `PlutoUtils.pas` - или расширить `BoardUtils`), чтобы per-band настройки/калибровка работали. -- XVTR-реле у Pluto нет → вкладка Transverter в упрощённом виде (только - программный сдвиг частоты), либо скрыть, оставив встроенную перестройку LO. - -### 6.11 Гейтинг UI (`MainForm` + `SettingsForm`) -- Везде, где сейчас `BoardType in [...]` или прямые предположения о HPSDR — - заменить на `FBackend.Caps.*`. -- `SettingsForm`: вкладки `Antenna/Alex`, `Transverter`, контролы Tip/Ring, - ADC dither/random, PA-калибровка показывать только при соответствующих Caps. - Для Pluto добавить секцию «Pluto RF» (gain mode, gain, RF bandwidth, LO offset). -- Главная форма: кнопки/оверлеи Wideband, ATT, SWR-индикатор — скрывать по Caps. - ---- - -## 7. Сборка и зависимости - -- **Линковка libiio:** предпочтительно **рантайм-загрузка** (`dynlibs`) — - приложение работает без libiio (Pluto просто не появляется в discovery). - Имена: Linux `libiio.so.0`, Windows `iio.dll` (libiio MSVC/MinGW сборка), - macOS `libiio.dylib` или `/Library/Frameworks/iio.framework`. -- Если выбрать link-time (как WDSP `{$linklib}`) — добавить путь в `ewsdr.lpi` - (`-k-L...`) и `{$linklib iio}` под `{$IFDEF DARWIN}`/нужные платформы. -- `ewsdr.lpr`: добавить `IIO, SDRBackend, PlutoBackend, BoardCaps` в `uses`. -- `ewsdr.lpi`: внести новые `.pas` в список файлов проекта. -- `bundle_app.sh`: при сборке macOS-бандла копировать `libiio.dylib` и его - зависимости (как уже делается для прочих не-системных dylib). -- `BUILD.MD`: отметить опциональную зависимость и где взять libiio - (пакет `libiio` / Analog Devices релизы). -- Документация: dev-права на USB (Linux udev-правило для Pluto - `0456:b673`), доступ к `ip:192.168.2.1`. - ---- - -## 8. План работ по фазам - -### Фаза 0 — Подготовка и каркас абстракции (без поведения Pluto) -1. `BoardCaps.pas`: `TBoardCaps`, `BoardCapsFor` (заполнить для всех HPSDR-плат). -2. `SDRBackend.pas`: `TSDRBackend` (абстракт), `THPSDRBackend` (обёртка над - существующим `THPSDRNetwork`), фабрика, `TSDRKind`. -3. `MainForm`: перевести `FNetwork` на `FBackend: TSDRBackend` (= `THPSDRBackend`), - все вызовы — через backend. Колбэки — через backend. -4. **Критерий приёмки:** поведение с openHPSDR полностью идентично текущему - (регресс-проверка RX/TX/discovery/settings). Pluto ещё нет. - -### Фаза 1 — libiio биндинги и discovery Pluto -1. `IIO.pas`: рантайм-загрузка + нужные функции; `IIOAvailable`. -2. `TPlutoBackend.Discover` (scan-context) → список Pluto-URI. -3. `DeviceForm`: тип устройства в списке найденных; расширить `TSavedDevice` - и INI; панель Pluto-параметров; результат с `Kind`/URI. -4. Идентификация Pluto по serial; ключ Settings. -5. **Критерий:** Pluto находится в discovery, сохраняется/загружается, - подключение `iio_create_context_from_uri` успешно (без стрима). - -### Фаза 2 — RX-стрим Pluto → WDSP -1. `TPlutoBackend`: конфигурация phy (LO, sampling_frequency, rf_bandwidth, - gain mode/gain), создание RX-буфера, RX-поток. -2. Repack int16→24-bit BE → синтез `TDDCIQPacket` → `OnDDCIQ`. -3. `SampleRateOverlay`/валидация по `Caps` (min 521к). -4. MainForm START/STOP-ветка для Pluto (вместо `ConfigureDDCs`/`SetRunAndFreq` - — вызовы backend, которые у Pluto означают set-rate + start-stream). -5. **Критерий:** живой спектр/водопад и аудио с Pluto на FM/AM/SSB. - -### Фаза 3 — TX-стрим Pluto -1. TX-буфер, TX-поток (`iio_buffer_push`), 24-bit→int16. -2. `SendDUCIQ`/`SendPTT` для Pluto; drive→TX hardwaregain. -3. Split/duplex LO RX/TX. -4. **Критерий:** передача SSB/FM, корректный уровень, отсутствие underrun. - -### Фаза 4 — UI-гейтинг и Pluto-специфика -1. Заменить все `BoardType in [...]` на `Caps`. -2. Скрыть Alex/XVTR/Wideband/Dither/SWR для Pluto; показать RX gain / AGC mode / - RF bandwidth; temp вместо supply. -3. VHF/UHF/SHF бэнд-план; расширенный ввод частоты до 6 ГГц. -4. Замер CPU/латентности на высоких rate; тюнинг буферов/FIR. -5. **Критерий:** UI согласован с возможностями железа для обоих типов. - -### Фаза 5 — Полировка, сборка, документация -1. `bundle_app.sh`/`lpi`/`BUILD.MD`/udev. -2. Обработка ошибок libiio (`iio_strerror`), реконнект, выдёргивание USB. -3. Регресс-тесты openHPSDR + Pluto. - ---- - -## 9. Риски и открытые вопросы - -1. **WDSP на высоких sample rate (521к–3М):** проверить CPU/латентность; - возможно нужен меньший `BufSize` для Pluto или FIR-децимация Pluto. -2. **Идентификация Pluto (нет MAC):** окончательно выбрать ключ Settings — - расширять `THPSDRDevice` (поле `Serial`/`URI`) или вводить новый device-info - тип. Влияет на форму и Settings — решить в Фазе 1. -3. **USB-троттлинг:** верхняя граница реального непрерывного rate по USB 2.0 - (~5–6 МS/s); пометить «USB limit» в overlay. -4. **libiio на Windows/macOS:** наличие готовых сборок `iio.dll`/`libiio.dylib`; - рантайм-загрузка снижает риск отсутствия. -5. **Масштаб/нормировка IQ:** подтвердить, что `<<12` даёт правильный уровень - S-метра (калибровка по WDSP); при необходимости — поправочный коэффициент. -6. **TX gain маппинг:** диапазон слайдера drive → TX atten подобрать на железе. -7. **Discovery-таймаут:** libiio-scan может блокировать; запускать в потоке - (как `TDiscoverThread`) и не вешать UI. - ---- - -## 10. Тест-план - -- **Регресс openHPSDR:** discovery, connect, RX все режимы, TX SSB/CW/FM, - Alex/XVTR/wideband, supply/SWR — без изменений после Фазы 0. -- **Pluto RX:** discovery (USB и IP), connect, спектр/водопад/аудио на разных - rate (521к/768к/1.5М), смена LO в пределах диапазона, gain manual/AGC. -- **Pluto TX:** SSB/FM, уровень по drive, split RX/TX, отсутствие - drop/underrun (логи seq/drops). -- **UI-гейтинг:** при Pluto скрыты Alex/XVTR/Wideband/SWR/dither; показаны - RX gain/AGC/RF bandwidth/temp. -- **Устойчивость:** выдёргивание USB / разрыв сети → корректный Disconnect, - reconnect, отсутствие крэшей; запуск без установленной libiio. -- **Кросс-платформа:** Linux (qt6) обязательно; Windows/macOS — по мере - доступности libiio. - ---- - -## 11. Принципы, которых держимся - -- `HPSDRNetwork.pas`, `WDSPEngine.pas`, `WDSP.pas` — **не меняем** (или - минимально: опц. `PushIQDoubles`). Pluto-логика — в новых файлах. -- Один источник правды о возможностях железа — `TBoardCaps`. -- Pluto переиспользует существующий DSP-конвейер за счёт repack в 24-bit BE. -- Discovery-форма сохраняет раскладку; Pluto-параметры — дополнительная панель.