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; 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-всплеска // первых мс, как pre-roll 1024 в piHPSDR). База/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; 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.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.