{
  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.
