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:
+103
-64
@@ -19,7 +19,7 @@ uses
|
||||
{$ELSE}
|
||||
Sockets, BaseUnix,
|
||||
{$ENDIF}
|
||||
HPSDRProtocol, SyncObjs, Settings;
|
||||
HPSDRProtocol, SyncObjs, Settings, RadioBackend;
|
||||
|
||||
const
|
||||
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
|
||||
@@ -70,33 +70,19 @@ const
|
||||
|
||||
|
||||
type
|
||||
THPSDRDevice = record
|
||||
IPAddress: string;
|
||||
Port: Word;
|
||||
MAC: array[0..5] of Byte;
|
||||
BoardType: Byte;
|
||||
ProtocolVersion: Byte;
|
||||
FirmwareVersion: Byte;
|
||||
NumDDCs: Byte;
|
||||
FreqOrPhase: Byte;
|
||||
EndianModes: Byte;
|
||||
InUse: Boolean;
|
||||
Valid: Boolean;
|
||||
end;
|
||||
PHPSDRDevice = ^THPSDRDevice;
|
||||
THPSDRDeviceArray = array of THPSDRDevice;
|
||||
// Запись устройства и типы колбэков теперь общие (RadioBackend) — здесь
|
||||
// алиасы для обратной совместимости со старым кодом (THPSDRDevice и т.д.).
|
||||
THPSDRDevice = RadioBackend.TRadioDevice;
|
||||
PHPSDRDevice = RadioBackend.PRadioDevice;
|
||||
THPSDRDeviceArray = RadioBackend.TRadioDeviceArray;
|
||||
TOnDeviceFound = RadioBackend.TOnDeviceFound;
|
||||
TOnDDCIQPacket = RadioBackend.TOnDDCIQPacket;
|
||||
TOnMicPacket = RadioBackend.TOnMicPacket;
|
||||
TOnHPStatus = RadioBackend.TOnHPStatus;
|
||||
TOnWidebandFrame = RadioBackend.TOnWidebandFrame;
|
||||
|
||||
TOnDeviceFound = procedure(const Dev: THPSDRDevice) of object;
|
||||
TOnDDCIQPacket = procedure(DDCIndex: Integer;
|
||||
const Data: TDDCIQPacket) of object;
|
||||
TOnMicPacket = procedure(const Data: TMicDataPacket) of object;
|
||||
TOnHPStatus = procedure(const Status: THighPriorityStatus) of object;
|
||||
TOnWidebandFrame = procedure(ADCIndex: Integer;
|
||||
const Samples: array of SmallInt;
|
||||
Count: Integer) of object;
|
||||
|
||||
{ THPSDRNetwork }
|
||||
THPSDRNetwork = class
|
||||
{ THPSDRNetwork — openHPSDR Ethernet protocol бэкенд }
|
||||
THPSDRNetwork = class(TRadioBackend)
|
||||
private
|
||||
FSocket: TSocket;
|
||||
FLocalPort: Word;
|
||||
@@ -125,11 +111,8 @@ type
|
||||
FKeepaliveThread: TThread;
|
||||
FDUCIQThread: TThread;
|
||||
|
||||
FOnDeviceFound: TOnDeviceFound;
|
||||
FOnDDCIQ: TOnDDCIQPacket;
|
||||
FOnMic: TOnMicPacket;
|
||||
FOnHPStatus: TOnHPStatus;
|
||||
FOnWideband: TOnWidebandFrame;
|
||||
// Колбэки данных (FOnDeviceFound/FOnDDCIQ/FOnMic/FOnHPStatus/FOnWideband)
|
||||
// унаследованы из TRadioBackend (protected) — здесь не дублируем.
|
||||
|
||||
FPortDDCSpec: Word;
|
||||
FPortDUCSpec: Word;
|
||||
@@ -210,59 +193,61 @@ type
|
||||
function NextSeq(var S: LongWord): LongWord;
|
||||
procedure PackSeqBytes(var B: array of Byte; Seq: LongWord);
|
||||
|
||||
protected
|
||||
// Полиморфные геттеры состояния (свойства Connected/Running/Device/LastError/
|
||||
// DirectIP объявлены в TRadioBackend).
|
||||
function GetConnected: Boolean; override;
|
||||
function GetRunning: Boolean; override;
|
||||
function GetDevice: TRadioDevice; override;
|
||||
function GetLastError: string; override;
|
||||
function GetDirectIP: string; override;
|
||||
procedure SetDirectIP(const V: string); override;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
function Discover(TimeoutMs: Integer = 3000): THPSDRDeviceArray;
|
||||
function Connect(const Dev: THPSDRDevice): Boolean;
|
||||
procedure Disconnect;
|
||||
function Caps: TBackendCaps; override;
|
||||
|
||||
procedure SendGeneralPacket(const Pkt: TGeneralPacket);
|
||||
function Discover(TimeoutMs: Integer = 3000): THPSDRDeviceArray; override;
|
||||
function Connect(const Dev: THPSDRDevice): Boolean; override;
|
||||
procedure Disconnect; override;
|
||||
|
||||
procedure SendGeneralPacket(const Pkt: TGeneralPacket); override;
|
||||
procedure ConfigureWideband(ADCIndex: Integer; Enabled: Boolean;
|
||||
SamplesPerPacket: Integer = 512; SampleBits: Integer = 16;
|
||||
UpdateRateMS: Integer = 70; PacketsPerFrame: Integer = 32);
|
||||
procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket);
|
||||
UpdateRateMS: Integer = 70; PacketsPerFrame: Integer = 32); override;
|
||||
procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); override;
|
||||
procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0;
|
||||
DitherEnabled: Boolean = True; RandomEnabled: Boolean = True);
|
||||
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket);
|
||||
procedure SendHighPriority(const Pkt: THighPriorityPacket);
|
||||
DitherEnabled: Boolean = True; RandomEnabled: Boolean = True); override;
|
||||
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); override;
|
||||
procedure SendHighPriority(const Pkt: THighPriorityPacket); override;
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
DriveLevel: Byte = 100);
|
||||
DriveLevel: Byte = 100); override;
|
||||
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
|
||||
Transmitting, PAEnabled, AlexEnabled: Boolean);
|
||||
procedure SetStepAtten(dB: Byte);
|
||||
procedure SetAlexConfig(const A: TAlexSettings);
|
||||
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
||||
procedure SetStepAtten(dB: Byte); override;
|
||||
procedure SetAlexConfig(const A: TAlexSettings); override;
|
||||
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
||||
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
||||
// антенна и Alex routing возвращаются к per-band дефолту.
|
||||
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
|
||||
procedure SendFullHP;
|
||||
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); override;
|
||||
procedure SendFullHP; override;
|
||||
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
|
||||
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
|
||||
procedure SendPTT(Active: Boolean);
|
||||
procedure SendDDCAudio(const LeftRight: array of SmallInt);
|
||||
procedure SendPTT(Active: Boolean); override;
|
||||
procedure SendDDCAudio(const LeftRight: array of SmallInt); override;
|
||||
// Принимает RX-аудио (float [-1..1]) из DSP, конвертирует в 16-bit и
|
||||
// отправляет на радио пакетами по 64 L/R пары. Хвост копится между вызовами.
|
||||
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer);
|
||||
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); override;
|
||||
// Единый переключатель встроенного динамика: True = слать аудио и снять мьют,
|
||||
// False = замьютить динамик (byte 1400 bit1) и не накапливать поток.
|
||||
// Применяется немедленно (шлёт HP-пакет, если подключены).
|
||||
procedure SetSpeakerAudio(AEnabled: Boolean);
|
||||
procedure SendDUCIQ(const IData, QData: array of Integer);
|
||||
procedure ClearDUCIQQueue;
|
||||
procedure SetSpeakerAudio(AEnabled: Boolean); override;
|
||||
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
||||
procedure ClearDUCIQQueue; override;
|
||||
|
||||
property Connected: Boolean read FConnected;
|
||||
property Running: Boolean read FRunning;
|
||||
property Device: THPSDRDevice read FDevice;
|
||||
property LocalPort: Word read FLocalPort;
|
||||
property LastError: string read FLastError;
|
||||
property OnDeviceFound: TOnDeviceFound read FOnDeviceFound write FOnDeviceFound;
|
||||
property OnDDCIQ: TOnDDCIQPacket read FOnDDCIQ write FOnDDCIQ;
|
||||
property OnMicPacket: TOnMicPacket read FOnMic write FOnMic;
|
||||
property OnHPStatus: TOnHPStatus read FOnHPStatus write FOnHPStatus;
|
||||
property OnWideband: TOnWidebandFrame read FOnWideband write FOnWideband;
|
||||
property DirectIP: string read FDirectIP write FDirectIP; // unicast discovery
|
||||
// LocalPort — HPSDR-специфично (локальный UDP-порт), не часть базы.
|
||||
property LocalPort: Word read FLocalPort;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -599,6 +584,60 @@ end;
|
||||
// THPSDRNetwork
|
||||
// ===========================================================================
|
||||
|
||||
// ---- Полиморфные геттеры состояния (TRadioBackend) ----
|
||||
function THPSDRNetwork.GetConnected: Boolean;
|
||||
begin
|
||||
Result := FConnected;
|
||||
end;
|
||||
|
||||
function THPSDRNetwork.GetRunning: Boolean;
|
||||
begin
|
||||
Result := FRunning;
|
||||
end;
|
||||
|
||||
function THPSDRNetwork.GetDevice: TRadioDevice;
|
||||
begin
|
||||
Result := FDevice;
|
||||
end;
|
||||
|
||||
function THPSDRNetwork.GetLastError: string;
|
||||
begin
|
||||
Result := FLastError;
|
||||
end;
|
||||
|
||||
function THPSDRNetwork.GetDirectIP: string;
|
||||
begin
|
||||
Result := FDirectIP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetDirectIP(const V: string);
|
||||
begin
|
||||
FDirectIP := V;
|
||||
end;
|
||||
|
||||
// ---- Возможности HPSDR-бэкенда ----
|
||||
function THPSDRNetwork.Caps: TBackendCaps;
|
||||
begin
|
||||
FillChar(Result, SizeOf(Result), 0);
|
||||
Result.Kind := bkHPSDR;
|
||||
Result.HasTX := True;
|
||||
Result.HasPA := True;
|
||||
Result.HasAlex := True;
|
||||
Result.HasWideband := True;
|
||||
Result.HasDitherRandom := True;
|
||||
Result.HasHWMic := True;
|
||||
Result.HasPLLStatus := True;
|
||||
Result.HasHWGain := False;
|
||||
Result.HasRFBandwidth := False;
|
||||
Result.HasFullDuplex := True;
|
||||
Result.MinSampleRate := 48000;
|
||||
Result.MaxSampleRate := 1536000;
|
||||
Result.SampleRateMode := srmDiscrete;
|
||||
Result.RatePresets := nil; // контроллер использует свой HPSDR-набор
|
||||
Result.MinFreqHz := 0;
|
||||
Result.MaxFreqHz := 61440000;
|
||||
end;
|
||||
|
||||
constructor THPSDRNetwork.Create;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
Reference in New Issue
Block a user