mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
- 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 <noreply@anthropic.com>
234 lines
9.7 KiB
ObjectPascal
234 lines
9.7 KiB
ObjectPascal
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_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;
|
||
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;
|
||
|
||
// ---- libad9361 (опционально) ----
|
||
// ad9361_set_bb_rate: настраивает BBPLL + ЗАГРУЖАЕТ FIR-таблицу под частоту.
|
||
// Без него прямая запись sampling_frequency на холодную даёт мусор (битый FIR).
|
||
// nil если libad9361 не загружена.
|
||
ad9361_set_bb_rate: function(dev: Piio_device; rate: culong): cint; cdecl;
|
||
ad9361_set_trx_fir_enable: function(dev: Piio_device; enable: cint): cint; cdecl;
|
||
|
||
// Загружает libiio (один раз). True — готово к использованию. Идемпотентно.
|
||
function IIOLoad: Boolean;
|
||
// True, если libiio загружена и символы разрешены.
|
||
function IIOLoaded: Boolean;
|
||
// Расшифровка кода ошибки libiio (err — отрицательный errno).
|
||
function IIOStrError(err: cint): string;
|
||
|
||
implementation
|
||
|
||
var
|
||
GLib: TLibHandle = NilHandle;
|
||
GLibAD: TLibHandle = NilHandle; // libad9361 (опционально)
|
||
GLoaded: Boolean = False;
|
||
|
||
const
|
||
{$IFDEF WINDOWS}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.dll', 'iio.dll');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.dll', 'ad9361.dll');
|
||
{$ELSE}
|
||
{$IFDEF DARWIN}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.dylib', 'libiio.1.dylib');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.dylib', 'libad9361.0.dylib');
|
||
{$ELSE}
|
||
IIO_LIBS: array[0..1] of string = ('libiio.so', 'libiio.so.0');
|
||
AD_LIBS: array[0..1] of string = ('libad9361.so', 'libad9361.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_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');
|
||
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');
|
||
|
||
// libad9361 — опционально (ad9361_set_bb_rate грузит FIR). Отсутствие не
|
||
// фатально: ApplySampleRate откатится на прямую запись sampling_frequency.
|
||
if GLibAD = NilHandle then
|
||
for I := 0 to High(AD_LIBS) do
|
||
begin
|
||
GLibAD := LoadLibrary(AD_LIBS[I]);
|
||
if GLibAD <> NilHandle then Break;
|
||
end;
|
||
if GLibAD <> NilHandle then
|
||
begin
|
||
ad9361_set_bb_rate := GetProcedureAddress(GLibAD, 'ad9361_set_bb_rate');
|
||
ad9361_set_trx_fir_enable := GetProcedureAddress(GLibAD, 'ad9361_set_trx_fir_enable');
|
||
end;
|
||
|
||
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 GLibAD <> NilHandle then
|
||
begin
|
||
UnloadLibrary(GLibAD);
|
||
GLibAD := NilHandle;
|
||
end;
|
||
if GLib <> NilHandle then
|
||
begin
|
||
UnloadLibrary(GLib);
|
||
GLib := NilHandle;
|
||
GLoaded := False;
|
||
end;
|
||
end.
|