Files
ewsdr/test_pluto.lpr
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

84 lines
2.6 KiB
ObjectPascal

program test_pluto;
{$MODE Delphi}
// Ручной тест Pluto/AD936x-бэкенда против реального железа (без UI/DSP).
// Сборка: fpc -Mobjfpc -dHEADLESS -Fu. -FUlib-headless/<cpu-os> test_pluto.lpr
// Запуск: ./test_pluto ip:172.16.2.199
uses
{$IFDEF UNIX}cthreads,{$ENDIF}
SysUtils, Classes, ctypes, Math,
RadioBackend, IIOBindings, HPSDRProtocol, PlutoBackend;
type
// Приёмник OnDDCIQ: считает пакеты/пары и средний уровень I (dBFS).
THelper = class
Pkts: Integer;
Pairs: Int64;
SumSq: Double;
procedure OnIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
end;
procedure THelper.OnIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
const SCALE = 1.0 / 8388608.0;
var spf, i, pos, ir: Integer; fi: Double;
begin
spf := (Integer(Data.SamplesPerFrame[0]) shl 8) or Integer(Data.SamplesPerFrame[1]);
Inc(Pkts);
Pairs := Pairs + spf;
for i := 0 to spf - 1 do
begin
pos := i * 6;
ir := (Integer(Data.IQData[pos]) shl 16) or (Integer(Data.IQData[pos+1]) shl 8)
or Integer(Data.IQData[pos+2]);
if (ir and $800000) <> 0 then ir := ir or Integer($FF000000);
fi := ir * SCALE;
SumSq := SumSq + fi * fi;
end;
end;
var
URI: string;
pl: TPlutoBackend;
dev: TRadioDevice;
h: THelper;
rms: Double;
begin
if ParamCount >= 1 then URI := ParamStr(1) else URI := 'ip:172.16.2.199';
if not IIOLoad then begin writeln('FAIL: libiio not loaded'); Halt(1); end;
writeln('libiio loaded OK');
pl := TPlutoBackend.Create;
h := THelper.Create;
try
if not pl.ProbeURI(URI, dev) then begin writeln('FAIL: probe'); Halt(1); end;
writeln('PROBE OK: ', dev.Model, ' serial=', dev.Serial, ' ip=', dev.IPAddress);
if not pl.Connect(dev) then begin writeln('FAIL connect: ', pl.LastError); Halt(1); end;
writeln('CONNECT OK');
pl.OnDDCIQ := h.OnIQ;
pl.ConfigureDDCs(1, 1536, 0, True, True); // 1.536 MS/s
pl.SetRunAndFreq(True, 100000000, 435000000, 100); // RX 100 MHz (FM band)
writeln('RUN: streaming 800 ms @ RX=100MHz, rate=1.536M ...');
Sleep(800);
pl.SetRunAndFreq(False, 100000000, 435000000, 0);
if h.Pairs > 0 then rms := Sqrt(h.SumSq / h.Pairs) else rms := 0;
writeln(' packets=', h.Pkts, ' pairs=', h.Pairs,
' (~', (h.Pairs / 0.8 / 1e6):0:3, ' MS/s)');
if rms > 0 then
writeln(' I level: rms=', rms:0:5, ' (', (20*Log10(rms)):0:1, ' dBFS)')
else
writeln(' I level: zero (no signal/clip)');
pl.Disconnect;
writeln('DISCONNECT OK');
finally
h.Free;
pl.Free;
end;
writeln('DONE');
end.