Files
ewsdr/RadioBackend.pas

399 lines
19 KiB
ObjectPascal
Raw Permalink 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.
{
Copyright (C)
2026 - Uladzimir Karpenka, EW8BAK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
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);
TBackendRateArray = array of Integer; // пресеты sample-rate (общий тип UI)
// Описание возможностей бэкенда. Контроллер гейтит 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)
// Панадаптеры (этап 3): сколько всего панов может показать бэкенд
// (1 = только главный; HPSDR P2 = главный + доп. DDC). Паны N>0 живут
// на своих аппаратных DDC (SetPanDDC*).
MaxPans: Integer;
// False = частоты панов связаны с паном 0 (Pluto+ 2r2t: общий RX LO).
IndependentPanFreq: Boolean;
// Число АЦП (SCU): >1 → у панов есть селектор ADC-источника (A1/A2, 3.4).
// Достоверно после Connect (у HPSDR зависит от BoardType).
NumADCs: Integer;
// Переключаемые антенные разъёмы AD936x (RX1/RX2, TX1/TX2) — у плат с двумя
// парами SMA (LibreSDR/AntSDR/Pluto+). Гейтит блок «Antenna» в настройках;
// у openHPSDR антеннами рулит Alex, поэтому False. Достоверно после Connect.
HasAntSwitch: Boolean;
// PureSignal-предыскажение: openHPSDR P2 платы со свободными DDC0/1 под
// feedback (Angelia/Orion/Orion2/Saturn). Гейтит кнопки PS/2TON.
// Достоверно после Connect.
HasPureSignal: Boolean;
// Телеграфный кейер в железе: точки/тире формирует прошивка по параметрам из
// DUC Specific (openHPSDR P2), а манипулятор воткнут в сам трансивер. False
// (голый AD936x) означает, что телеграф обязан формировать локальный
// генератор — см. CWKeyer.pas.
HasCWKeyer: Boolean;
MinSampleRate: Integer;
RatePresets: TBackendRateArray; // пресеты для 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;
// ---- Панадаптеры на доп. аппаратных DDC (этап 3, только HPSDR) ----
// SetPanDDC включает/выключает и конфигурирует DDC с hw-индексом DDCIdx
// (rate/ADC → DDC Specific, частота → High Priority). SetPanDDCFreq —
// только ретюн (дёшево, без реконфига DDC). База — no-op (Caps.MaxPans=1).
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
RateKHz: Word; ADCSource: Byte); virtual;
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); virtual;
// Антенные разъёмы AD936x: 0 = RX1/TX1, 1 = RX2/TX2 (см. Caps.HasAntSwitch).
// У бэкендов без переключателя — no-op.
procedure SetAntPorts(RxPort, TxPort: Byte); virtual;
// ---- PureSignal (openHPSDR P2, платы с DDC0/1 под feedback) ----
// SetPureSignal — фича вооружена (кнопка PS в UI). SetPureSignalTX — фаза
// PTT: на True бэкенд включает feedback-DDC0 (+sync DDC1 = TX DAC) на 192к
// и ставит их частоты = TX; на False возвращает RX-конфигурацию.
// База — no-op (Pluto и др. без PS-тракта).
procedure SetPureSignal(Enabled: Boolean); virtual;
procedure SetPureSignalTX(Active: Boolean); virtual;
// ---- Частота / 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;
Tuning: Boolean = False; TwoTone: Boolean = False); 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;
// Pre-roll: закачать Pairs нулевых IQ-пар в TX-очередь на старте передачи —
// радио начинает с наполненным DUC-FIFO (подавление underrun-всплеска
// первых миллисекунд). База/Pluto — no-op: у Pluto
// TX-стрим непрерывный, его FIFO при недоборе сам добивается нулями.
procedure PrimeDUCIQ(Pairs: Integer); 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;
// Бит CWX (High Priority байт 5 bit0): «ключ нажат» для программной
// передачи телеграфа — несущую по нему формирует прошивка. Пакет уходит
// немедленно, каждый фронт (тайминги держит PC). Pluto — no-op.
procedure SetCWXKey(Down: Boolean); virtual;
// Кейер в прошивке вооружён: железо может замкнуть ключ САМО, без нашего
// PTT. Пока флаг взведён, уровень мощности обязан лежать в каждом HP-кадре
// (обычно он шлётся только на передаче — а «передачи» с точки зрения софта
// тут не происходит вовсе). Pluto — no-op.
procedure SetCWKeyerArmed(Armed: Boolean); 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;
// OC Control (Open Collector выходы Penny/Alex) — no-op по умолчанию
// (Pluto/AD936x не имеет такого выхода).
procedure SetOCConfig(const O: TOCSettings); virtual;
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
ASlot: Integer = -1); 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: температура трансивера (TempC) и Zynq SoC/FPGA (SocTempC) в °C,
// RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False.
// Недоступные значения = TELEMETRY_NONE.
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual;
// Текущее усиление приёмного тракта (дБ), если оно есть в железе и может
// меняться само: AD936x hardwaregain (в режимах AGC железо крутит его сам).
// Нужно для абсолютной шкалы уровней — контроллер вычитает его из показаний.
// HPSDR: усиления нет (есть аттенюатор), возвращает TELEMETRY_NONE.
function RxGainNowDb: Double; virtual;
// Частотная калибровка опорника, ppm: «опора выше номинала на N ppm».
// HPSDR — масштаб частотного слова, AD936x — xo_correction в ad9361-phy.
procedure SetFreqCalPPM(P: Double); 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.PrimeDUCIQ(Pairs: Integer);
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.SetCWXKey(Down: Boolean);
begin
end;
procedure TRadioBackend.SetCWKeyerArmed(Armed: Boolean);
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;
ASlot: Integer);
begin
end;
procedure TRadioBackend.SetOCConfig(const O: TOCSettings);
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, SocTempC, RSSIdB: Double): Boolean;
begin
TempC := TELEMETRY_NONE;
SocTempC := TELEMETRY_NONE;
RSSIdB := TELEMETRY_NONE;
Result := False;
end;
function TRadioBackend.RxGainNowDb: Double;
begin
Result := TELEMETRY_NONE;
end;
procedure TRadioBackend.SetFreqCalPPM(P: Double);
begin
end;
procedure TRadioBackend.SetTxAtten(Db: Double);
begin
end;
procedure TRadioBackend.SetPanDDC(DDCIdx: Integer; Enabled: Boolean;
FreqHz: Double; RateKHz: Word; ADCSource: Byte);
begin
end;
procedure TRadioBackend.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double);
begin
end;
procedure TRadioBackend.SetAntPorts(RxPort, TxPort: Byte);
// Бэкенды без переключаемых разъёмов (openHPSDR — там Alex) ничего не делают.
begin
end;
procedure TRadioBackend.SetPureSignal(Enabled: Boolean);
// PureSignal есть только у openHPSDR P2 (THPSDRNetwork переопределяет).
begin
end;
procedure TRadioBackend.SetPureSignalTX(Active: Boolean);
begin
end;
end.