mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27:33 +00:00
TX path for Pluto/AD936x (RX was already working): - PlutoBackend: continuous full-duplex TX stream (cf-ad9361-dds-core-lpc) with FIFO + push thread; SendDUCIQ upsamples WDSP 192k -> device rate (integer, linear interp), 24->16 bit, Q inverted; zeros on underrun. - Power via output attenuator (ADI-recommended): SetTxAtten writes hardwaregain on voltage0(out). Controller maps drive% -> dB (PlutoTxAttForDrive/ApplyPlutoTxAtten); -89.75 dB on RX (~off), ceiling PlutoTxMaxAttDb on 100% drive. - Configurable max-att (default -10 dB, ADI-linear) for external amp: persisted in global settings, UI spin on PA page, routed through controller SetPlutoTxMaxAtt (UI stays decoupled from logic). - RadioBackend: virtual SetTxAtten (no-op for HPSDR). - Update doc/PLUTO_INTEGRATION_PLAN.md: phases 3/4/5 + risks. Builds clean (headless + qt6 GUI). Not yet verified on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
263 lines
11 KiB
ObjectPascal
263 lines
11 KiB
ObjectPascal
unit RadioBackend;
|
|
|
|
{
|
|
Абстракция бэкенда железа. Базовый класс TRadioBackend, который реализуют
|
|
конкретные транспорты:
|
|
- THPSDRNetwork (openHPSDR Ethernet protocol) — HPSDRNetwork.pas
|
|
- TPlutoBackend (libiio / AD936x) — PlutoBackend.pas (позже)
|
|
|
|
TRadioController держит FNetwork: TRadioBackend и работает только через этот
|
|
интерфейс. Возможности бэкенда описывает TBackendCaps (контроллер гейтит по
|
|
ним UI и поведение).
|
|
|
|
Здесь же — общая запись устройства (TRadioDevice) и типы колбэков данных,
|
|
чтобы оба бэкенда и контроллер ссылались на один источник правды.
|
|
TDDCIQPacket/THighPriorityStatus/TMicDataPacket берём из HPSDRProtocol —
|
|
это сетевой формат HPSDR, а Pluto-бэкенд синтезирует те же структуры, поэтому
|
|
WDSP-вход остаётся неизменным.
|
|
}
|
|
|
|
{$IFDEF FPC}
|
|
{$MODE Delphi}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, HPSDRProtocol, Settings;
|
|
|
|
type
|
|
TBackendKind = (bkHPSDR, bkPluto);
|
|
TSampleRateMode = (srmDiscrete, srmContinuous);
|
|
|
|
// Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям.
|
|
TBackendCaps = record
|
|
Kind: TBackendKind;
|
|
HasTX: Boolean; // есть передатчик
|
|
HasPA: Boolean; // fwd power / SWR / supply (HP status)
|
|
HasAlex: Boolean; // Alex фильтры / антенный коммутатор
|
|
HasWideband: Boolean; // wideband ADC дисплей
|
|
HasDitherRandom: Boolean; // dither / random на ADC
|
|
HasHWMic: Boolean; // mic over network / HW keyer / sidetone
|
|
HasPLLStatus: Boolean; // PLL lock в статусе
|
|
HasHWGain: Boolean; // manual gain / hw-AGC (Pluto)
|
|
HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto)
|
|
HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100)
|
|
MinSampleRate: Integer;
|
|
MaxSampleRate: Integer;
|
|
SampleRateMode: TSampleRateMode;
|
|
RatePresets: array of Integer; // пресеты для SampleRateOverlay
|
|
MinFreqHz: Double;
|
|
MaxFreqHz: Double;
|
|
end;
|
|
|
|
// Общая запись найденного/подключённого устройства. Поля HPSDR (MAC/BoardType/
|
|
// NumDDCs…) и Pluto (URI/Serial) сосуществуют; актуальность по Kind.
|
|
TRadioDevice = record
|
|
Kind: TBackendKind;
|
|
IPAddress: string;
|
|
Port: Word;
|
|
MAC: array[0..5] of Byte; // HPSDR; для Pluto — synthetic из Serial
|
|
BoardType: Byte;
|
|
ProtocolVersion: Byte;
|
|
FirmwareVersion: Byte;
|
|
NumDDCs: Byte;
|
|
FreqOrPhase: Byte;
|
|
EndianModes: Byte;
|
|
URI: string; // Pluto: 'usb:1.5.5' / 'ip:192.168.2.1'
|
|
Serial: string; // Pluto serial (hw_serial)
|
|
Model: string; // Pluto hw_model (для отображения)
|
|
InUse: Boolean;
|
|
Valid: Boolean;
|
|
end;
|
|
PRadioDevice = ^TRadioDevice;
|
|
TRadioDeviceArray = array of TRadioDevice;
|
|
|
|
const
|
|
// Признак «значение телеметрии недоступно» (ReadTelemetry). Заведомо вне
|
|
// физического диапазона температур/RSSI.
|
|
TELEMETRY_NONE = -1.0E9;
|
|
|
|
type
|
|
|
|
// Колбэки данных (вызываются из рабочих потоков бэкенда).
|
|
TOnDeviceFound = procedure(const Dev: TRadioDevice) 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;
|
|
|
|
{ TRadioBackend — абстрактный бэкенд железа }
|
|
TRadioBackend = class
|
|
protected
|
|
// Колбэки — общее хранилище для всех бэкендов (свойства ниже).
|
|
FOnDeviceFound: TOnDeviceFound;
|
|
FOnDDCIQ: TOnDDCIQPacket;
|
|
FOnMic: TOnMicPacket;
|
|
FOnHPStatus: TOnHPStatus;
|
|
FOnWideband: TOnWidebandFrame;
|
|
|
|
function GetConnected: Boolean; virtual; abstract;
|
|
function GetRunning: Boolean; virtual; abstract;
|
|
function GetDevice: TRadioDevice; virtual; abstract;
|
|
function GetLastError: string; virtual; abstract;
|
|
function GetDirectIP: string; virtual;
|
|
procedure SetDirectIP(const V: string); virtual;
|
|
public
|
|
// ---- Возможности ----
|
|
function Caps: TBackendCaps; virtual; abstract;
|
|
|
|
// ---- Жизненный цикл / discovery ----
|
|
function Discover(TimeoutMs: Integer = 3000): TRadioDeviceArray; virtual; abstract;
|
|
function Connect(const Dev: TRadioDevice): Boolean; virtual; abstract;
|
|
procedure Disconnect; virtual; abstract;
|
|
|
|
// ---- Конфигурация потока RX ----
|
|
procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0;
|
|
DitherEnabled: Boolean = True;
|
|
RandomEnabled: Boolean = True); virtual; abstract;
|
|
|
|
// ---- Частота / drive / TX-состояние ----
|
|
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
|
DriveLevel: Byte = 100); virtual; abstract;
|
|
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
|
|
Transmitting, PAEnabled, AlexEnabled: Boolean); virtual; abstract;
|
|
procedure SendFullHP; virtual; abstract;
|
|
procedure SendPTT(Active: Boolean); virtual; abstract;
|
|
|
|
// ---- TX IQ ----
|
|
procedure SendDUCIQ(const IData, QData: array of Integer); virtual;
|
|
procedure ClearDUCIQQueue; virtual;
|
|
|
|
// ---- Опциональные (HPSDR-специфичные): no-op по умолчанию ----
|
|
procedure SendGeneralPacket(const Pkt: TGeneralPacket); virtual;
|
|
procedure ConfigureWideband(ADCIndex: Integer; Enabled: Boolean;
|
|
SamplesPerPacket: Integer = 512; SampleBits: Integer = 16;
|
|
UpdateRateMS: Integer = 70; PacketsPerFrame: Integer = 32); virtual;
|
|
procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); virtual;
|
|
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); virtual;
|
|
procedure SendHighPriority(const Pkt: THighPriorityPacket); virtual;
|
|
procedure SetStepAtten(dB: Byte); virtual;
|
|
// RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack,
|
|
// 3=hybrid. GainDb применяется только в manual. HPSDR — no-op.
|
|
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); virtual;
|
|
// TX-аттенюация выходного тракта (Pluto/AD936x): hardwaregain на voltage0
|
|
// (output), дБ в диапазоне ~ -89.75..0 (0 = макс. мощность). Контроллер ставит
|
|
// максимум на приёме (≈выкл) и значение от drive на передаче. HPSDR — no-op
|
|
// (мощность там через DriveLevel/DUC). Db < 0.
|
|
procedure SetTxAtten(Db: Double); virtual;
|
|
procedure SetAlexConfig(const A: TAlexSettings); virtual;
|
|
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); virtual;
|
|
procedure SendDDCAudio(const LeftRight: array of SmallInt); virtual;
|
|
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
|
|
procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
|
|
|
|
// Аппаратная телеметрия, не приходящая в потоке (опрашивается контроллером).
|
|
// Pluto/AD936x: температура чипа (°C) и RSSI приёмника (дБ). HPSDR — нет такой
|
|
// (использует HP-status), возвращает False. Недоступные значения = TELEMETRY_NONE.
|
|
function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; virtual;
|
|
|
|
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
|
property Connected: Boolean read GetConnected;
|
|
property Running: Boolean read GetRunning;
|
|
property Device: TRadioDevice read GetDevice;
|
|
property LastError: string read GetLastError;
|
|
property DirectIP: string read GetDirectIP write SetDirectIP;
|
|
|
|
// ---- Колбэки данных ----
|
|
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;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TRadioBackend — пустые реализации опциональных методов (no-op).
|
|
Конкретные бэкенды переопределяют то, что поддерживают. }
|
|
|
|
function TRadioBackend.GetDirectIP: string;
|
|
begin
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TRadioBackend.SetDirectIP(const V: string);
|
|
begin
|
|
// no-op по умолчанию (unicast discovery — фича HPSDR)
|
|
end;
|
|
|
|
procedure TRadioBackend.SendDUCIQ(const IData, QData: array of Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.ClearDUCIQQueue;
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendGeneralPacket(const Pkt: TGeneralPacket);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.ConfigureWideband(ADCIndex: Integer; Enabled: Boolean;
|
|
SamplesPerPacket: Integer; SampleBits: Integer;
|
|
UpdateRateMS: Integer; PacketsPerFrame: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendDDCSpecific(const Pkt: TDDCSpecificPacket);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendDUCSpecific(const Pkt: TDUCSpecificPacket);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendHighPriority(const Pkt: THighPriorityPacket);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SetStepAtten(dB: Byte);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SetRxGain(ModeIdx: Integer; GainDb: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SetAlexConfig(const A: TAlexSettings);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendDDCAudio(const LeftRight: array of SmallInt);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SendSpeakerAudio(const Left, Right: array of Single; Count: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TRadioBackend.SetSpeakerAudio(AEnabled: Boolean);
|
|
begin
|
|
end;
|
|
|
|
function TRadioBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean;
|
|
begin
|
|
TempC := TELEMETRY_NONE;
|
|
RSSIdB := TELEMETRY_NONE;
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TRadioBackend.SetTxAtten(Db: Double);
|
|
begin
|
|
end;
|
|
|
|
end.
|