mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27:33 +00:00
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>
318 lines
9.7 KiB
ObjectPascal
318 lines
9.7 KiB
ObjectPascal
unit DeviceStore;
|
||
|
||
{$mode objfpc}{$H+}
|
||
|
||
// Общее хранилище устройств — единый источник правды для десктоп-диалога
|
||
// (DeviceForm) и web-оверлея. Владеет сохранёнными устройствами (persist в
|
||
// hpsdr_devices.ini) и текущим списком найденных при discovery.
|
||
// Контроллер (TRadioController) держит один экземпляр; фронтенды работают
|
||
// через него, поэтому списки не расходятся.
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, IniFiles, BoardUtils, PlatformUtils, RadioBackend;
|
||
|
||
const
|
||
DEVICE_CFG_NAME = 'hpsdr_devices.ini';
|
||
|
||
type
|
||
// Запись о сохранённом устройстве
|
||
TSavedDevice = record
|
||
Name: string; // пользовательское имя
|
||
IPAddress: string;
|
||
BoardType: Integer;
|
||
AutoStart: Boolean; // запускать автоматически при старте
|
||
Kind: TBackendKind; // bkHPSDR / bkPluto
|
||
URI: string; // Pluto: 'ip:..' / 'usb:..'
|
||
Serial: string; // Pluto serial
|
||
end;
|
||
|
||
// Найденное устройство (результат discovery)
|
||
TDiscoveredDevice = record
|
||
IPAddress: string;
|
||
DisplayName: string;
|
||
BoardType: Integer;
|
||
MAC: array[0..5] of Byte; // нужен для preload-rate lookup (LoadDevice по MAC)
|
||
Kind: TBackendKind; // bkHPSDR / bkPluto
|
||
URI: string; // Pluto: context URI
|
||
Serial: string; // Pluto serial
|
||
end;
|
||
|
||
{ TDeviceStore }
|
||
TDeviceStore = class
|
||
private
|
||
FSaved: array of TSavedDevice;
|
||
FSavedCount: Integer;
|
||
FDiscovered: array of TDiscoveredDevice;
|
||
FDiscoveredCount: Integer;
|
||
public
|
||
constructor Create;
|
||
|
||
// ---- Сохранённые устройства ----
|
||
procedure LoadSaved;
|
||
procedure SaveSaved;
|
||
function SavedCount: Integer;
|
||
function Saved(Idx: Integer): TSavedDevice;
|
||
// Добавляет запись (Name пустое → 'HPSDR'); возвращает индекс новой записи.
|
||
function AddSaved(const AName, IP: string; BoardType: Integer): Integer;
|
||
// Добавляет Pluto-устройство (Kind=bkPluto) с URI/Serial.
|
||
function AddSavedPluto(const AName, AURI, ASerial: string): Integer;
|
||
procedure RemoveSaved(Idx: Integer);
|
||
procedure SetAutoStart(Idx: Integer); // эксклюзивно: только одно autostart
|
||
function AutoStartIP: string;
|
||
function AutoStartBoardType: Integer;
|
||
function SavedBoardType(Idx: Integer): Integer;
|
||
function SavedDisplay(Idx: Integer): string; // строка для списка (с * и board)
|
||
|
||
// ---- Найденные (discovery) ----
|
||
procedure ClearDiscovered;
|
||
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
|
||
const MAC: array of Byte;
|
||
Kind: TBackendKind = bkHPSDR;
|
||
const URI: string = ''; const Serial: string = '');
|
||
function DiscoveredCount: Integer;
|
||
function Discovered(Idx: Integer): TDiscoveredDevice;
|
||
// IP найденного устройства по IP (для preload-rate lookup и т.п.)
|
||
function FindDiscoveredByIP(const IP: string): Integer;
|
||
end;
|
||
|
||
implementation
|
||
|
||
{ TDeviceStore }
|
||
|
||
constructor TDeviceStore.Create;
|
||
begin
|
||
inherited Create;
|
||
FSavedCount := 0;
|
||
FDiscoveredCount := 0;
|
||
LoadSaved;
|
||
end;
|
||
|
||
procedure TDeviceStore.LoadSaved;
|
||
var
|
||
Ini: TIniFile;
|
||
I, N: Integer;
|
||
Section: string;
|
||
begin
|
||
FSavedCount := 0;
|
||
SetLength(FSaved, 0);
|
||
if not FileExists(GetAppCfgDir + DEVICE_CFG_NAME) then Exit;
|
||
|
||
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
|
||
try
|
||
N := Ini.ReadInteger('Devices', 'Count', 0);
|
||
SetLength(FSaved, N);
|
||
for I := 0 to N - 1 do
|
||
begin
|
||
Section := 'Device' + IntToStr(I);
|
||
FSaved[I].Name := Ini.ReadString (Section, 'Name', 'HPSDR');
|
||
FSaved[I].IPAddress := Ini.ReadString (Section, 'IP', '');
|
||
FSaved[I].BoardType := Ini.ReadInteger(Section, 'BoardType', 0);
|
||
FSaved[I].AutoStart := Ini.ReadBool (Section, 'AutoStart', False);
|
||
FSaved[I].Kind := TBackendKind(Ini.ReadInteger(Section, 'Kind', 0));
|
||
FSaved[I].URI := Ini.ReadString (Section, 'URI', '');
|
||
FSaved[I].Serial := Ini.ReadString (Section, 'Serial', '');
|
||
Inc(FSavedCount);
|
||
end;
|
||
finally
|
||
Ini.Free;
|
||
end;
|
||
end;
|
||
|
||
procedure TDeviceStore.SaveSaved;
|
||
var
|
||
Ini: TIniFile;
|
||
I: Integer;
|
||
Section: string;
|
||
begin
|
||
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
|
||
try
|
||
Ini.WriteInteger('Devices', 'Count', FSavedCount);
|
||
for I := 0 to FSavedCount - 1 do
|
||
begin
|
||
Section := 'Device' + IntToStr(I);
|
||
Ini.WriteString (Section, 'Name', FSaved[I].Name);
|
||
Ini.WriteString (Section, 'IP', FSaved[I].IPAddress);
|
||
Ini.WriteInteger(Section, 'BoardType', FSaved[I].BoardType);
|
||
Ini.WriteBool (Section, 'AutoStart', FSaved[I].AutoStart);
|
||
Ini.WriteInteger(Section, 'Kind', Ord(FSaved[I].Kind));
|
||
Ini.WriteString (Section, 'URI', FSaved[I].URI);
|
||
Ini.WriteString (Section, 'Serial', FSaved[I].Serial);
|
||
end;
|
||
finally
|
||
Ini.Free;
|
||
end;
|
||
end;
|
||
|
||
function TDeviceStore.SavedCount: Integer;
|
||
begin
|
||
Result := FSavedCount;
|
||
end;
|
||
|
||
function TDeviceStore.Saved(Idx: Integer): TSavedDevice;
|
||
begin
|
||
if (Idx >= 0) and (Idx < FSavedCount) then
|
||
Result := FSaved[Idx]
|
||
else
|
||
begin
|
||
Result.Name := ''; Result.IPAddress := '';
|
||
Result.BoardType := 0; Result.AutoStart := False;
|
||
end;
|
||
end;
|
||
|
||
function TDeviceStore.AddSaved(const AName, IP: string; BoardType: Integer): Integer;
|
||
begin
|
||
Result := FSavedCount;
|
||
Inc(FSavedCount);
|
||
SetLength(FSaved, FSavedCount);
|
||
if Trim(AName) <> '' then FSaved[Result].Name := Trim(AName)
|
||
else FSaved[Result].Name := 'HPSDR';
|
||
FSaved[Result].IPAddress := Trim(IP);
|
||
FSaved[Result].BoardType := BoardType;
|
||
FSaved[Result].AutoStart := False;
|
||
FSaved[Result].Kind := bkHPSDR;
|
||
FSaved[Result].URI := '';
|
||
FSaved[Result].Serial := '';
|
||
SaveSaved;
|
||
end;
|
||
|
||
function TDeviceStore.AddSavedPluto(const AName, AURI, ASerial: string): Integer;
|
||
begin
|
||
Result := FSavedCount;
|
||
Inc(FSavedCount);
|
||
SetLength(FSaved, FSavedCount);
|
||
if Trim(AName) <> '' then FSaved[Result].Name := Trim(AName)
|
||
else FSaved[Result].Name := 'PlutoSDR';
|
||
FSaved[Result].IPAddress := Trim(AURI); // в списке показываем URI
|
||
FSaved[Result].BoardType := 0;
|
||
FSaved[Result].AutoStart := False;
|
||
FSaved[Result].Kind := bkPluto;
|
||
FSaved[Result].URI := Trim(AURI);
|
||
FSaved[Result].Serial := Trim(ASerial);
|
||
SaveSaved;
|
||
end;
|
||
|
||
procedure TDeviceStore.RemoveSaved(Idx: Integer);
|
||
var
|
||
I: Integer;
|
||
begin
|
||
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
|
||
for I := Idx to FSavedCount - 2 do
|
||
FSaved[I] := FSaved[I + 1];
|
||
Dec(FSavedCount);
|
||
SetLength(FSaved, FSavedCount);
|
||
SaveSaved;
|
||
end;
|
||
|
||
procedure TDeviceStore.SetAutoStart(Idx: Integer);
|
||
var
|
||
I: Integer;
|
||
begin
|
||
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
|
||
// Только одно устройство может быть AutoStart
|
||
for I := 0 to FSavedCount - 1 do
|
||
FSaved[I].AutoStart := (I = Idx);
|
||
SaveSaved;
|
||
end;
|
||
|
||
function TDeviceStore.AutoStartIP: string;
|
||
var
|
||
I: Integer;
|
||
begin
|
||
Result := '';
|
||
for I := 0 to FSavedCount - 1 do
|
||
if FSaved[I].AutoStart then
|
||
begin
|
||
Result := FSaved[I].IPAddress;
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
function TDeviceStore.AutoStartBoardType: Integer;
|
||
var
|
||
I: Integer;
|
||
begin
|
||
Result := 0;
|
||
for I := 0 to FSavedCount - 1 do
|
||
if FSaved[I].AutoStart then
|
||
begin
|
||
Result := FSaved[I].BoardType;
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
function TDeviceStore.SavedBoardType(Idx: Integer): Integer;
|
||
begin
|
||
if (Idx >= 0) and (Idx < FSavedCount) then
|
||
Result := FSaved[Idx].BoardType
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
function TDeviceStore.SavedDisplay(Idx: Integer): string;
|
||
begin
|
||
Result := '';
|
||
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
|
||
Result := FSaved[Idx].Name + ' [' + FSaved[Idx].IPAddress + ']';
|
||
if FSaved[Idx].BoardType > 0 then
|
||
Result := Result + ' ' + BoardTypeName(FSaved[Idx].BoardType);
|
||
if FSaved[Idx].AutoStart then
|
||
Result := '* ' + Result;
|
||
end;
|
||
|
||
procedure TDeviceStore.ClearDiscovered;
|
||
begin
|
||
FDiscoveredCount := 0;
|
||
SetLength(FDiscovered, 0);
|
||
end;
|
||
|
||
procedure TDeviceStore.AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
|
||
const MAC: array of Byte; Kind: TBackendKind; const URI: string; const Serial: string);
|
||
var
|
||
Idx, J: Integer;
|
||
begin
|
||
Idx := FDiscoveredCount;
|
||
Inc(FDiscoveredCount);
|
||
SetLength(FDiscovered, FDiscoveredCount);
|
||
FDiscovered[Idx].IPAddress := IP;
|
||
FDiscovered[Idx].DisplayName := DisplayName;
|
||
FDiscovered[Idx].BoardType := BoardType;
|
||
FDiscovered[Idx].Kind := Kind;
|
||
FDiscovered[Idx].URI := URI;
|
||
FDiscovered[Idx].Serial := Serial;
|
||
FillChar(FDiscovered[Idx].MAC, SizeOf(FDiscovered[Idx].MAC), 0);
|
||
for J := 0 to High(MAC) do
|
||
if J <= 5 then FDiscovered[Idx].MAC[J] := MAC[J];
|
||
end;
|
||
|
||
function TDeviceStore.DiscoveredCount: Integer;
|
||
begin
|
||
Result := FDiscoveredCount;
|
||
end;
|
||
|
||
function TDeviceStore.Discovered(Idx: Integer): TDiscoveredDevice;
|
||
begin
|
||
if (Idx >= 0) and (Idx < FDiscoveredCount) then
|
||
Result := FDiscovered[Idx]
|
||
else
|
||
begin
|
||
Result.IPAddress := ''; Result.DisplayName := ''; Result.BoardType := 0;
|
||
end;
|
||
end;
|
||
|
||
function TDeviceStore.FindDiscoveredByIP(const IP: string): Integer;
|
||
var
|
||
I: Integer;
|
||
begin
|
||
Result := -1;
|
||
for I := 0 to FDiscoveredCount - 1 do
|
||
if SameText(FDiscovered[I].IPAddress, IP) then
|
||
begin
|
||
Result := I;
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
end.
|