mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +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:
+130
-10
@@ -42,6 +42,7 @@ uses
|
||||
ChannelStore, ChannelsForm,
|
||||
PowerInhibit,
|
||||
DeviceStore,
|
||||
RadioBackend, PlutoBackend,
|
||||
RadioController, ChannelController;
|
||||
|
||||
const
|
||||
@@ -324,6 +325,9 @@ type
|
||||
procedure OnWidebandCB(ADCIndex: Integer; const Samples: array of SmallInt;
|
||||
Count: Integer);
|
||||
procedure OnWDSPOpenDone(Success: Boolean);
|
||||
function ResolveDevice(const IP: string): THPSDRDevice;
|
||||
procedure RelabelBands; // подписи band-кнопок под план бэкенда
|
||||
procedure ApplyBackendCapsToUI; // sample-rate пресеты + гейтинг полей по Caps
|
||||
procedure DoConnectDevice(const Dev: THPSDRDevice);
|
||||
// Старт с уже выбранным устройством (preload rate + WDSP open + connect).
|
||||
// Переиспользуется десктоп-диалогом и web-оверлеем — без модального диалога.
|
||||
@@ -1313,6 +1317,8 @@ begin
|
||||
// возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему.
|
||||
FController.FLoadedGlobal.LightTheme := FLightTheme;
|
||||
FCATAdapter.ApplySettings(FController.FLoadedGlobal);
|
||||
RelabelBands; // HF ↔ VHF/UHF подписи под тип бэкенда
|
||||
ApplyBackendCapsToUI; // пресеты sample rate + гейтинг полей
|
||||
RebuildXvtrButtons;
|
||||
PushXvtrToWeb;
|
||||
TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive%
|
||||
@@ -2664,10 +2670,12 @@ const
|
||||
MARGIN_Y = 4;
|
||||
begin
|
||||
if (FSampleRateOverlay = nil) or (PbSpectrum = nil) then Exit;
|
||||
// Ширина = доступная ширина спектра справа от MARGIN_X (контент сам клампится
|
||||
// к этому пределу). Раньше фиксировалось 320px → 8 Pluto-кнопок не влезали.
|
||||
FSampleRateOverlay.SetBounds(
|
||||
MARGIN_X,
|
||||
MARGIN_Y,
|
||||
FSampleRateOverlay.Width,
|
||||
Max(320, PbSpectrum.Width - MARGIN_X * 2),
|
||||
FSampleRateOverlay.Height);
|
||||
end;
|
||||
|
||||
@@ -3296,7 +3304,40 @@ end;
|
||||
procedure TMainForm.BtnDiscoverFromDialog(Sender: TObject);
|
||||
// Запускается когда пользователь нажимает DISCOVER внутри диалога.
|
||||
// Контроллер чистит список и стартует поток; UI слушает rfDeviceList.
|
||||
// Сетевые Pluto (без mDNS) не находятся scan'ом — собираем список URI для
|
||||
// прямого probe: сохранённые Pluto + ручной ввод IP в поле диалога.
|
||||
var
|
||||
URIs: array of string;
|
||||
i: Integer;
|
||||
ip: string;
|
||||
|
||||
procedure AddURI(const U: string);
|
||||
var k: Integer;
|
||||
begin
|
||||
if U = '' then Exit;
|
||||
for k := 0 to High(URIs) do if SameText(URIs[k], U) then Exit;
|
||||
SetLength(URIs, Length(URIs) + 1);
|
||||
URIs[High(URIs)] := U;
|
||||
end;
|
||||
|
||||
begin
|
||||
SetLength(URIs, 0);
|
||||
for i := 0 to FController.FDeviceStore.SavedCount - 1 do
|
||||
if FController.FDeviceStore.Saved(i).Kind = bkPluto then
|
||||
AddURI(FController.FDeviceStore.Saved(i).URI);
|
||||
if Assigned(FDeviceDialog) then
|
||||
begin
|
||||
ip := FDeviceDialog.ManualIP;
|
||||
if ip <> '' then
|
||||
begin
|
||||
if (Pos('ip:', ip) = 1) or (Pos('usb:', ip) = 1) or (Pos('local:', ip) = 1) then
|
||||
AddURI(ip)
|
||||
else
|
||||
AddURI('ip:' + ip);
|
||||
end;
|
||||
end;
|
||||
FController.SetPlutoProbeURIs(URIs);
|
||||
|
||||
SetStatusText(2, 'Discovering...');
|
||||
FController.Discover;
|
||||
end;
|
||||
@@ -3352,13 +3393,52 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Создаём запись устройства из IP + BoardType (из диалога/autostart) и стартуем.
|
||||
FillChar(Dev, SizeOf(Dev), 0);
|
||||
Dev.IPAddress := FPendingIP;
|
||||
Dev.BoardType := FPendingBoardType;
|
||||
// Создаём запись устройства. Если это известный (discovered/saved) Pluto —
|
||||
// восстанавливаем Kind/URI/Serial/MAC из store по IP; иначе HPSDR по IP.
|
||||
Dev := ResolveDevice(FPendingIP);
|
||||
if Dev.Kind = bkHPSDR then Dev.BoardType := FPendingBoardType;
|
||||
StartWithDevice(Dev);
|
||||
end;
|
||||
|
||||
function TMainForm.ResolveDevice(const IP: string): THPSDRDevice;
|
||||
// По IP/URI ищет устройство среди discovered, затем saved, чтобы восстановить
|
||||
// Kind/URI/Serial/MAC (нужно для Pluto). Если не найдено — простой HPSDR по IP.
|
||||
var
|
||||
i: Integer;
|
||||
dd: TDiscoveredDevice;
|
||||
sd: TSavedDevice;
|
||||
begin
|
||||
// Result — managed-поля (строки) при входе nil, FillChar безопасен.
|
||||
FillChar(Result, SizeOf(Result), 0);
|
||||
Result.Kind := bkHPSDR;
|
||||
Result.IPAddress := IP;
|
||||
|
||||
i := FController.FDeviceStore.FindDiscoveredByIP(IP);
|
||||
if i >= 0 then
|
||||
begin
|
||||
dd := FController.FDeviceStore.Discovered(i);
|
||||
Result.Kind := dd.Kind;
|
||||
Result.URI := dd.URI;
|
||||
Result.Serial := dd.Serial;
|
||||
Result.BoardType := dd.BoardType;
|
||||
Move(dd.MAC[0], Result.MAC[0], 6);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
for i := 0 to FController.FDeviceStore.SavedCount - 1 do
|
||||
begin
|
||||
sd := FController.FDeviceStore.Saved(i);
|
||||
if SameText(sd.IPAddress, IP) or SameText(sd.URI, IP) then
|
||||
begin
|
||||
Result.Kind := sd.Kind;
|
||||
Result.URI := sd.URI;
|
||||
Result.Serial := sd.Serial;
|
||||
Result.BoardType := sd.BoardType;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoStopAndDisconnect;
|
||||
// STOP-teardown, общий для десктоп-STOP и web-disconnect. Движок (graceful Run=0,
|
||||
// сброс TX/телеметрии) + персист настроек + Disconnect — в контроллере
|
||||
@@ -3435,6 +3515,33 @@ begin
|
||||
DoConnectDevice(FPendingDev);
|
||||
end;
|
||||
|
||||
procedure TMainForm.RelabelBands;
|
||||
// Переписывает подписи band-кнопок под план активного бэкенда (HF ↔ VHF/UHF).
|
||||
var i: Integer; pl: Boolean;
|
||||
begin
|
||||
pl := FController.IsPluto;
|
||||
for i := 0 to BAND_COUNT - 1 do
|
||||
if BtnBand[i] <> nil then
|
||||
BtnBand[i].Caption := BandName(i, pl);
|
||||
end;
|
||||
|
||||
procedure TMainForm.ApplyBackendCapsToUI;
|
||||
// Пресеты sample-rate оверлея из Caps (Pluto >560k) + (TODO) гейтинг полей,
|
||||
// которых нет у бэкенда (PA/SWR/Alex/Wideband).
|
||||
var
|
||||
Caps: TBackendCaps;
|
||||
begin
|
||||
Caps := FController.BackendCaps;
|
||||
if Assigned(FSampleRateOverlay) then
|
||||
begin
|
||||
if (Caps.SampleRateMode = srmContinuous) and (Length(Caps.RatePresets) > 0) then
|
||||
FSampleRateOverlay.SetRatePresets(Caps.RatePresets)
|
||||
else
|
||||
FSampleRateOverlay.SetRatePresetsDefault; // HPSDR 48k..1536k
|
||||
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
||||
begin
|
||||
BtnStartStop.Enabled := True;
|
||||
@@ -3798,7 +3905,7 @@ begin
|
||||
|
||||
if FController.FCurrentXvtr < 0 then
|
||||
begin
|
||||
BandIdx := FreqToBandIdx(ClickFreq);
|
||||
BandIdx := FreqToBandIdx(ClickFreq, FController.IsPluto);
|
||||
if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then
|
||||
begin
|
||||
if FController.FDevConnected then
|
||||
@@ -3834,7 +3941,7 @@ begin
|
||||
end;
|
||||
if FController.FCurrentXvtr < 0 then
|
||||
begin
|
||||
BandIdx := FreqToBandIdx(FController.FVfoB);
|
||||
BandIdx := FreqToBandIdx(FController.FVfoB, FController.IsPluto);
|
||||
if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then
|
||||
begin
|
||||
if FController.FCurrentBand >= 0 then
|
||||
@@ -5114,10 +5221,14 @@ begin
|
||||
NewFreq := Base; // снэп к нижней границе
|
||||
end;
|
||||
|
||||
// В XVTR-режиме HF-клэмп не применяем: ApplyVfoA сам ограничит по FreqBegin/FreqEnd
|
||||
// Клэмп диапазона перестройки. XVTR — ApplyVfoA сам ограничит по FreqBegin/End.
|
||||
// Pluto — по диапазону бэкенда (Caps). Иначе HF (30 кГц..60 МГц).
|
||||
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then
|
||||
NewFreq := Max(0, NewFreq)
|
||||
else if FController.IsPluto then
|
||||
NewFreq := Max(Round(FController.BackendCaps.MinFreqHz),
|
||||
Min(Round(FController.BackendCaps.MaxFreqHz), NewFreq))
|
||||
else
|
||||
NewFreq := Max(30000, Min(60000000, NewFreq));
|
||||
if FController.FActiveVfo = 0 then
|
||||
@@ -5329,8 +5440,17 @@ begin
|
||||
FController.FLoadedGlobal.FreqMhzDigits := Digits; // зеркало в persist-буфер контроллера
|
||||
FreqDispA.MinMhzDigits := Digits;
|
||||
FreqDispB.MinMhzDigits := Digits;
|
||||
FreqDispA.MaxFreq := MaxFreqs[Digits];
|
||||
FreqDispB.MaxFreq := MaxFreqs[Digits];
|
||||
// Pluto/VHF/UHF: верхняя граница из Caps (до 6 ГГц), иначе HF по числу цифр.
|
||||
if FController.IsPluto then
|
||||
begin
|
||||
FreqDispA.MaxFreq := Round(FController.BackendCaps.MaxFreqHz);
|
||||
FreqDispB.MaxFreq := Round(FController.BackendCaps.MaxFreqHz);
|
||||
end
|
||||
else
|
||||
begin
|
||||
FreqDispA.MaxFreq := MaxFreqs[Digits];
|
||||
FreqDispB.MaxFreq := MaxFreqs[Digits];
|
||||
end;
|
||||
if Assigned(FWebServer) then
|
||||
FWebServer.FreqMhzDigits := Digits;
|
||||
LayoutTopVfoBlock;
|
||||
|
||||
Reference in New Issue
Block a user