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:
+199
@@ -0,0 +1,199 @@
|
||||
unit IIOBindings;
|
||||
|
||||
{
|
||||
Минимальный биндинг к libiio (классический API 0.x; в системе 0.26) с
|
||||
ДИНАМИЧЕСКОЙ загрузкой. Если libiio нет — IIOLoad возвращает False, и
|
||||
Pluto-бэкенд просто не участвует в discovery (приложение работает с HPSDR).
|
||||
|
||||
Загружаем libiio.so / libiio.so.0 (Linux), iio.dll (Windows),
|
||||
libiio.dylib (macOS). Имена символов одинаковые на всех платформах.
|
||||
|
||||
Покрыты функции для: scan (discovery), context create/destroy, поиск
|
||||
device/channel, чтение/запись атрибутов, буферы RX/TX. Этого набора хватает
|
||||
на фазы 1–4 (discovery → RX → управление → TX).
|
||||
}
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$MODE Delphi}
|
||||
{$ENDIF}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
dynlibs, ctypes, SysUtils;
|
||||
|
||||
type
|
||||
// Непрозрачные указатели libiio
|
||||
Piio_scan_context = Pointer;
|
||||
Piio_context_info = Pointer;
|
||||
PPiio_context_info = ^Piio_context_info; // struct iio_context_info **
|
||||
Piio_context = Pointer;
|
||||
Piio_device = Pointer;
|
||||
Piio_channel = Pointer;
|
||||
Piio_buffer = Pointer;
|
||||
|
||||
var
|
||||
// ---- Scan / discovery ----
|
||||
iio_create_scan_context: function(backend: PAnsiChar; flags: cuint): Piio_scan_context; cdecl;
|
||||
iio_scan_context_destroy: procedure(ctx: Piio_scan_context); cdecl;
|
||||
iio_scan_context_get_info_list: function(ctx: Piio_scan_context;
|
||||
var info: PPiio_context_info): ptrint; cdecl;
|
||||
iio_context_info_list_free: procedure(info: PPiio_context_info); cdecl;
|
||||
iio_context_info_get_description: function(info: Piio_context_info): PAnsiChar; cdecl;
|
||||
iio_context_info_get_uri: function(info: Piio_context_info): PAnsiChar; cdecl;
|
||||
|
||||
// ---- Context ----
|
||||
iio_create_context_from_uri: function(uri: PAnsiChar): Piio_context; cdecl;
|
||||
iio_context_destroy: procedure(ctx: Piio_context); cdecl;
|
||||
iio_context_get_name: function(ctx: Piio_context): PAnsiChar; cdecl;
|
||||
iio_context_find_device: function(ctx: Piio_context; name: PAnsiChar): Piio_device; cdecl;
|
||||
iio_context_get_attr_value: function(ctx: Piio_context; name: PAnsiChar): PAnsiChar; cdecl;
|
||||
|
||||
// ---- Device / channel ----
|
||||
// output — C '_Bool' (1 байт). Передаём явный байт 0/1 (ByteBool/True давал
|
||||
// некорректное значение → output-каналы не находились).
|
||||
iio_device_find_channel: function(dev: Piio_device; name: PAnsiChar;
|
||||
output: cuchar): Piio_channel; cdecl;
|
||||
iio_device_set_kernel_buffers_count: function(dev: Piio_device; nb: cuint): cint; cdecl;
|
||||
iio_channel_enable: procedure(chn: Piio_channel); cdecl;
|
||||
iio_channel_disable: procedure(chn: Piio_channel); cdecl;
|
||||
|
||||
// ---- Атрибуты ----
|
||||
iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
||||
out val: clonglong): 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;
|
||||
src: PAnsiChar): ptrint; cdecl;
|
||||
iio_channel_attr_write_bool: function(chn: Piio_channel; attr: PAnsiChar;
|
||||
val: ByteBool): cint; cdecl;
|
||||
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||||
val: cdouble): cint; cdecl;
|
||||
|
||||
// ---- Буферы (RX refill / TX push) ----
|
||||
iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
|
||||
cyclic: ByteBool): Piio_buffer; cdecl;
|
||||
iio_buffer_destroy: procedure(buf: Piio_buffer); cdecl;
|
||||
iio_buffer_cancel: procedure(buf: Piio_buffer); cdecl; // разблокирует refill/push
|
||||
iio_buffer_refill: function(buf: Piio_buffer): ptrint; cdecl;
|
||||
iio_buffer_push: function(buf: Piio_buffer): ptrint; cdecl;
|
||||
iio_buffer_first: function(buf: Piio_buffer; chn: Piio_channel): Pointer; cdecl;
|
||||
iio_buffer_step: function(buf: Piio_buffer): ptrint; cdecl;
|
||||
iio_buffer_end: function(buf: Piio_buffer): Pointer; cdecl;
|
||||
|
||||
// ---- Утилиты ----
|
||||
iio_strerror: procedure(err: cint; dst: PAnsiChar; len: csize_t); cdecl;
|
||||
|
||||
// Загружает libiio (один раз). True — готово к использованию. Идемпотентно.
|
||||
function IIOLoad: Boolean;
|
||||
// True, если libiio загружена и символы разрешены.
|
||||
function IIOLoaded: Boolean;
|
||||
// Расшифровка кода ошибки libiio (err — отрицательный errno).
|
||||
function IIOStrError(err: cint): string;
|
||||
|
||||
implementation
|
||||
|
||||
var
|
||||
GLib: TLibHandle = NilHandle;
|
||||
GLoaded: Boolean = False;
|
||||
|
||||
const
|
||||
{$IFDEF WINDOWS}
|
||||
IIO_LIBS: array[0..1] of string = ('libiio.dll', 'iio.dll');
|
||||
{$ELSE}
|
||||
{$IFDEF DARWIN}
|
||||
IIO_LIBS: array[0..1] of string = ('libiio.dylib', 'libiio.1.dylib');
|
||||
{$ELSE}
|
||||
IIO_LIBS: array[0..1] of string = ('libiio.so', 'libiio.so.0');
|
||||
{$ENDIF}
|
||||
{$ENDIF}
|
||||
|
||||
function Sym(const Name: string): Pointer;
|
||||
begin
|
||||
Result := GetProcedureAddress(GLib, Name);
|
||||
if Result = nil then
|
||||
raise Exception.CreateFmt('libiio: symbol "%s" not found', [Name]);
|
||||
end;
|
||||
|
||||
function IIOLoaded: Boolean;
|
||||
begin
|
||||
Result := GLoaded;
|
||||
end;
|
||||
|
||||
function IIOLoad: Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
if GLoaded then Exit(True);
|
||||
if GLib = NilHandle then
|
||||
for I := 0 to High(IIO_LIBS) do
|
||||
begin
|
||||
GLib := LoadLibrary(IIO_LIBS[I]);
|
||||
if GLib <> NilHandle then Break;
|
||||
end;
|
||||
if GLib = NilHandle then Exit(False);
|
||||
|
||||
try
|
||||
iio_create_scan_context := Sym('iio_create_scan_context');
|
||||
iio_scan_context_destroy := Sym('iio_scan_context_destroy');
|
||||
iio_scan_context_get_info_list := Sym('iio_scan_context_get_info_list');
|
||||
iio_context_info_list_free := Sym('iio_context_info_list_free');
|
||||
iio_context_info_get_description := Sym('iio_context_info_get_description');
|
||||
iio_context_info_get_uri := Sym('iio_context_info_get_uri');
|
||||
iio_create_context_from_uri := Sym('iio_create_context_from_uri');
|
||||
iio_context_destroy := Sym('iio_context_destroy');
|
||||
iio_context_get_name := Sym('iio_context_get_name');
|
||||
iio_context_find_device := Sym('iio_context_find_device');
|
||||
iio_context_get_attr_value := Sym('iio_context_get_attr_value');
|
||||
iio_device_find_channel := Sym('iio_device_find_channel');
|
||||
iio_device_set_kernel_buffers_count := Sym('iio_device_set_kernel_buffers_count');
|
||||
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_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');
|
||||
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
||||
iio_device_create_buffer := Sym('iio_device_create_buffer');
|
||||
iio_buffer_destroy := Sym('iio_buffer_destroy');
|
||||
iio_buffer_cancel := Sym('iio_buffer_cancel');
|
||||
iio_buffer_refill := Sym('iio_buffer_refill');
|
||||
iio_buffer_push := Sym('iio_buffer_push');
|
||||
iio_buffer_first := Sym('iio_buffer_first');
|
||||
iio_buffer_step := Sym('iio_buffer_step');
|
||||
iio_buffer_end := Sym('iio_buffer_end');
|
||||
iio_strerror := Sym('iio_strerror');
|
||||
GLoaded := True;
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
UnloadLibrary(GLib);
|
||||
GLib := NilHandle;
|
||||
GLoaded := False;
|
||||
end;
|
||||
end;
|
||||
Result := GLoaded;
|
||||
end;
|
||||
|
||||
function IIOStrError(err: cint): string;
|
||||
var
|
||||
Buf: array[0..255] of AnsiChar;
|
||||
begin
|
||||
if GLoaded and Assigned(iio_strerror) then
|
||||
begin
|
||||
FillChar(Buf, SizeOf(Buf), 0);
|
||||
iio_strerror(Abs(err), @Buf[0], SizeOf(Buf));
|
||||
Result := string(PAnsiChar(@Buf[0]));
|
||||
end
|
||||
else
|
||||
Result := Format('iio error %d', [err]);
|
||||
end;
|
||||
|
||||
finalization
|
||||
if GLib <> NilHandle then
|
||||
begin
|
||||
UnloadLibrary(GLib);
|
||||
GLib := NilHandle;
|
||||
GLoaded := False;
|
||||
end;
|
||||
end.
|
||||
Reference in New Issue
Block a user