Files
ewsdr/IIOBindings.pas
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

200 lines
8.0 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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. Этого набора хватает
на фазы 14 (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.