{
  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 HPSDRProtocol;

{
  openHPSDR Ethernet Protocol V4.3 constants and packet structures.
  Big-Endian (Network Byte Order) unless otherwise noted.
}

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

// ---------------------------------------------------------------------------
// UDP Ports (defaults)
// ---------------------------------------------------------------------------
const
  PORT_COMMAND        = 1024;   // Discovery, Erase, Program, Set IP
  PORT_DDC_SPECIFIC   = 1025;   // DDC Specific packet (PC -> HW)
  PORT_DUC_SPECIFIC   = 1026;   // DUC/TXA Specific packet (PC -> HW)
  PORT_HP_FROM_PC     = 1027;   // High Priority from PC
  PORT_HP_TO_PC       = 1025;   // High Priority Status from HW
  PORT_DDC_AUDIO      = 1028;   // DDC Audio (PC -> HW)
  PORT_DUC_IQ         = 1029;   // DUC I&Q data (PC -> HW)
  PORT_MIC_DATA       = 1026;   // Microphone data (HW -> PC)
  PORT_WIDEBAND_ADC0  = 1027;   // Wideband data (HW -> PC)
  PORT_DDC0_IQ        = 1035;   // DDC0 I&Q (HW -> PC), DDC1=1036 etc.

// ---------------------------------------------------------------------------
// Command bytes (Byte 4 of packet)
// ---------------------------------------------------------------------------
  CMD_GENERAL         = $00;
  CMD_DISCOVERY       = $02;
  CMD_SET_IP          = $03;
  CMD_ERASE           = $04;
  CMD_PROGRAM         = $05;

// ---------------------------------------------------------------------------
// Discovery Reply Byte 4
// ---------------------------------------------------------------------------
  REPLY_DISCOVERY_FREE    = $02;  // Hardware available
  REPLY_DISCOVERY_INUSE   = $03;  // Hardware in use by another host
  REPLY_XML_DESC          = $FE;  // XML hardware description follows
  REPLY_FULL_DESC         = $FF;  // Full hardware description follows

// ---------------------------------------------------------------------------
// Board types (Discovery Reply Byte 11)
// ---------------------------------------------------------------------------
  BOARD_ATLAS         = 0;
  BOARD_HERMES        = 1;   // ANAN-10, 100
  BOARD_HERMES_10E    = 2;   // ANAN-10E, 100B
  BOARD_ANGELA        = 3;   // ANAN-100D
  BOARD_ORION         = 4;   // ANAN-200D
  BOARD_ORION_MK2     = 5;   // ANAN-7/8000DLE
  BOARD_HERMES_LITE   = 6;
  BOARD_SATURN        = 10;  // ANAN-G2

// ---------------------------------------------------------------------------
// Packet sizes
// ---------------------------------------------------------------------------
  DISCOVERY_PACKET_SIZE   = 60;
  GENERAL_PACKET_SIZE     = 60;
  DDC_SPECIFIC_SIZE       = 1444;
  DUC_SPECIFIC_SIZE       = 60;
  HP_DATA_PC_SIZE         = 1444;
  HP_STATUS_HW_SIZE       = 60;
  DDC_AUDIO_SIZE          = 260;   // 4 hdr + 64*2*2
  DUC_IQ_SIZE             = 1444;  // 4 hdr + 240*6
  MIC_DATA_SIZE           = 132;   // 4 hdr + 64*2
  DDC_IQ_SIZE             = 1444;  // 4 hdr + 12 info + 238*6

// ---------------------------------------------------------------------------
// DDC sample rates (ksps)
// ---------------------------------------------------------------------------
  DDC_RATE_48   = 48;
  DDC_RATE_96   = 96;
  DDC_RATE_192  = 192;
  DDC_RATE_384  = 384;
  DDC_RATE_768  = 768;
  DDC_RATE_1536 = 1536;

// ---------------------------------------------------------------------------
// DSP clock (Hz)
// ---------------------------------------------------------------------------
  DSP_CLOCK_HZ: Double = 122880000.0;

// ---------------------------------------------------------------------------
// Maximum DDCs / ADCs
// ---------------------------------------------------------------------------
  MAX_DDCS = 80;
  MAX_ADCS = 8;
  MAX_DUCS = 4;

// ---------------------------------------------------------------------------
// High Priority Byte 4 bits (PC -> HW)
// ---------------------------------------------------------------------------
  HP_RUN  = $01;
  HP_PTT0 = $02;
  HP_PTT1 = $04;
  HP_PTT2 = $08;
  HP_PTT3 = $10;

// ---------------------------------------------------------------------------
// High Priority Status Byte 4 bits (HW -> PC)
// ---------------------------------------------------------------------------
  HPS_PTT          = $01;
  HPS_DOT          = $02;
  HPS_DASH         = $04;
  HPS_PLL_LOCKED   = $10;
  HPS_FIFO_EMPTY   = $20;
  HPS_FIFO_FULL    = $40;

// ---------------------------------------------------------------------------
// DUC Specific Byte 5 bits (CW options)
// ---------------------------------------------------------------------------
  CW_EER              = $01;
  CW_MODE             = $02;
  CW_REVERSE_KEYS     = $04;
  CW_IAMBIC           = $08;
  CW_SIDETONE         = $10;
  CW_MODE_B           = $20;
  CW_STRICT_SPACING   = $40;
  CW_BREAK_IN         = $80;

// ---------------------------------------------------------------------------
// Packet record types
// ---------------------------------------------------------------------------

type
  // Raw byte array for a generic 60-byte command packet
  TRawPacket60   = array[0..59] of Byte;
  TRawPacket1444 = array[0..1443] of Byte;
  PRawPacket1444 = ^TRawPacket1444;
  PRawPacket60   = ^TRawPacket60;

  // -------------------------------------------------------------------------
  // Discovery packet (PC -> HW)
  // -------------------------------------------------------------------------
  TDiscoveryPacket = packed record
    SeqHi, SeqMidHi, SeqMidLo, SeqLo: Byte;  // Bytes 0-3: always 0
    Command: Byte;                              // Byte  4: CMD_DISCOVERY=$02
    Zeros: array[5..59] of Byte;               // Bytes 5-59: zero
  end;

  // -------------------------------------------------------------------------
  // Discovery Reply (HW -> PC)
  // -------------------------------------------------------------------------
  TDiscoveryReply = packed record
    SeqHi, SeqMidHi, SeqMidLo, SeqLo: Byte;   // 0-3
    Status: Byte;                                // 4: $02 free/$03 in use
    MAC: array[0..5] of Byte;                    // 5-10
    BoardType: Byte;                             // 11
    ProtocolVersion: Byte;                       // 12
    FirmwareVersion: Byte;                       // 13
    Mercury0: Byte;                              // 14
    Mercury1: Byte;                              // 15
    Mercury2: Byte;                              // 16
    Mercury3: Byte;                              // 17
    PennyVersion: Byte;                          // 18
    MetisVersion: Byte;                          // 19
    NumDDCs: Byte;                               // 20
    FreqOrPhase: Byte;                           // 21: 0=freq, 1=phase
    EndianModes: Byte;                           // 22
    BetaVersion: Byte;                           // 23
    Reserved: array[24..59] of Byte;
  end;

  // -------------------------------------------------------------------------
  // General Packet (PC -> HW, port 1024)
  // -------------------------------------------------------------------------
  TGeneralPacket = packed record
    Seq: array[0..3] of Byte;          // 0-3
    Command: Byte;                     // 4: CMD_GENERAL=$00
    DDCSpecPort: array[0..1] of Byte;  // 5-6: default 1025
    DUCSpecPort: array[0..1] of Byte;  // 7-8: default 1026
    HPFromPCPort: array[0..1] of Byte; // 9-10: default 1027
    HPToPCPort: array[0..1] of Byte;   // 11-12: default 1025
    DDCAudioPort: array[0..1] of Byte; // 13-14: default 1028
    DUCIQPort: array[0..1] of Byte;    // 15-16: default 1029
    DDC0Port: array[0..1] of Byte;     // 17-18: default 1035
    MicPort: array[0..1] of Byte;      // 19-20: default 1026
    WBPort: array[0..1] of Byte;       // 21-22: default 1027
    WBEnable: Byte;                    // 23
    WBSamplesPerPkt: array[0..1] of Byte; // 24-25: default 512
    WBSampleSize: Byte;                // 26: default 16
    WBUpdateRate: Byte;                // 27: 0-255ms
    WBPacketsPerFrame: Byte;           // 28: default 32
    MemMapFromPCPort: array[0..1] of Byte; // 29-30
    MemMapToPCPort: array[0..1] of Byte;   // 31-32
    EnvPWMMin: array[0..1] of Byte;    // 33-34
    EnvPWMMax: array[0..1] of Byte;    // 35-36
    Flags37: Byte;                     // 37: timestamp/VITA/VNA/phase
    Flags38: Byte;                     // 38: HW timer enable
    DataFormat: Byte;                  // 39: endian/format
    Reserved40: array[40..55] of Byte;
    AtlasBusConfig: Byte;              // 56: Atlas config [2:0]
    Ref10MHz: Byte;                    // 57: 10MHz source [1:0]
    PAConfig: Byte;                    // 58: PA/Apollo/Mercury/clock
    AlexEnable: Byte;                  // 59: Alex[0..7] enable
  end;

  // -------------------------------------------------------------------------
  // DDC Specific Packet (PC -> HW)
  // -------------------------------------------------------------------------
  TDDCSpecificPacket = packed record
    Seq: array[0..3] of Byte;          // 0-3
    NumADCs: Byte;                     // 4: number of ADCs
    DitherADC: Byte;                   // 5: dither enable bits
    RandomADC: Byte;                   // 6: random enable bits
    DDCEnable: array[0..9] of Byte;    // 7-16: enable bits DDC0..79
    // DDC config: 6 bytes each for DDC0..79
    // Byte 17+n*6: ADC selection
    // Byte 18+n*6: SampleRate [15:8]
    // Byte 19+n*6: SampleRate [7:0]
    // Byte 20+n*6: CIC1 (future)
    // Byte 21+n*6: CIC2 (future)
    // Byte 22+n*6: SampleSize (default 24)
    DDCConfig: array[0..479] of Byte;  // 17-496
    Filler: array[497..1362] of Byte;
    SyncDDC: array[0..79] of Byte;    // 1363-1442
    Unused: Byte;                     // 1443
  end;

  // Per-DDC config helper (maps into DDCConfig array above)
  TDDCConfig = packed record
    ADCSource: Byte;            // which ADC (0..NumADCs-1) or NumADCs for DAC
    SampleRateHi, SampleRateLo: Byte;
    CIC1, CIC2: Byte;
    SampleSize: Byte;           // default 24
  end;

  // -------------------------------------------------------------------------
  // DUC (Transmitter) Specific Packet (PC -> HW)
  // -------------------------------------------------------------------------
  TDUCSpecificPacket = packed record
    Seq: array[0..3] of Byte;
    NumDACs: Byte;              // 4
    CWOptions: Byte;            // 5: CW_* flags
    SidetoneLevel: Byte;        // 6: 0..127
    SidetoneFreqHi: Byte;       // 7
    SidetoneFreqLo: Byte;       // 8
    KeyerSpeed: Byte;           // 9: 0..60 WPM
    KeyerWeight: Byte;          // 10: 33..66
    HangDelayHi: Byte;          // 11
    HangDelayLo: Byte;          // 12
    RFDelay: Byte;              // 13
    DUC0RateHi: Byte;           // 14
    DUC0RateLo: Byte;           // 15
    DUC0Bits: Byte;             // 16: default 24
    CWRampPeriod: Byte;         // 17: ms, 0=default
    Reserved18: array[18..49] of Byte;
    MicLineSelect: Byte;        // 50
    LineInGain: Byte;           // 51: 0=-34.5dB .. 31=+12dB (шаг 1.5 dB)
    Reserved52: array[52..56] of Byte;
    StepAtten2: Byte;           // 57: ADC2 atten during TX (0-31dB)
    StepAtten1: Byte;           // 58: ADC1 atten during TX
    StepAtten0: Byte;           // 59: ADC0 atten during TX
  end;

  // -------------------------------------------------------------------------
  // High Priority Packet (PC -> HW, port 1027)
  // Full packet is 1444 bytes but we define the key fields
  // -------------------------------------------------------------------------
  THighPriorityPacket = packed record
    Seq: array[0..3] of Byte;
    RunPTT: Byte;               // 4: HP_RUN | HP_PTTn
    CWX0: Byte;                 // 5: [0]=CWX [1]=Dot [2]=Dash
    CWX1: Byte;                 // 6: reserved
    CWX2: Byte;                 // 7: reserved
    CWX3: Byte;                 // 8: reserved
    // Bytes 9-12:  DDC0 frequency/phase word (Big-Endian)
    // Bytes 13-16: DDC1 ...
    // Bytes 17-328: DDC2..DDC79
    // Bytes 329-332: DUC0
    // Byte 345: DUC0 drive level (0-255)
    // Bytes 1398-1399: CAT over TCP port
    // Byte 1400: Transverter/audio enable
    // Byte 1401: Open Collector outputs
    // Bytes 1432-1435: Alex0 filter config
    // Byte 1443: Step Attenuator 0
    Data: array[9..1443] of Byte;
  end;

  // -------------------------------------------------------------------------
  // High Priority Status Packet (HW -> PC, port 1025)
  // -------------------------------------------------------------------------
  THighPriorityStatus = packed record
    Seq: array[0..3] of Byte;
    StatusBits: Byte;          // 4: HPS_PTT/DOT/DASH/PLL_LOCKED
    ADCOverload: Byte;         // 5: [0..7] = ADC0..7 overload
    ExciterPwr0Hi: Byte;       // 6
    ExciterPwr0Lo: Byte;       // 7
    ExciterPwr1: array[0..1] of Byte; // 8-9 (reserved)
    ExciterPwr2: array[0..1] of Byte; // 10-11 (reserved)
    ExciterPwr3: array[0..1] of Byte; // 12-13 (reserved)
    FwdPwrAlex0Hi: Byte;       // 14
    FwdPwrAlex0Lo: Byte;       // 15
    FwdPwrAlex1: array[0..1] of Byte; // 16-17 (reserved)
    FwdPwrAlex2: array[0..1] of Byte; // 18-19 (reserved)
    FwdPwrAlex3: array[0..1] of Byte; // 20-21 (reserved)
    RevPwrAlex0Hi: Byte;       // 22
    RevPwrAlex0Lo: Byte;       // 23
    RevPwrAlex1: array[0..1] of Byte; // 24-25 (reserved)
    RevPwrAlex2: array[0..1] of Byte; // 26-27 (reserved)
    RevPwrAlex3: array[0..1] of Byte; // 28-29 (reserved)
    FIFOOverflow: Byte;        // 30: [3:0] overflow bits
    DDCFIFODepthHi: Byte;      // 31
    DDCFIFODepthLo: Byte;      // 32
    MicFIFODepthHi: Byte;      // 33
    MicFIFODepthLo: Byte;      // 34
    DUCFIFODepthHi: Byte;      // 35
    DUCFIFODepthLo: Byte;      // 36
    SpkFIFODepthHi: Byte;      // 37
    SpkFIFODepthLo: Byte;      // 38
    Unused39: array[39..48] of Byte;
    SupplyVoltsHi: Byte;       // 49
    SupplyVoltsLo: Byte;       // 50
    UserADC3Hi: Byte;          // 51
    UserADC3Lo: Byte;          // 52
    UserADC2Hi: Byte;          // 53
    UserADC2Lo: Byte;          // 54
    UserADC1Hi: Byte;          // 55
    UserADC1Lo: Byte;          // 56
    UserADC0Hi: Byte;          // 57
    UserADC0Lo: Byte;          // 58
    UserInputBits: Byte;       // 59: IO4/IO5/IO6/IO8/IO2 etc.
  end;

  // -------------------------------------------------------------------------
  // Microphone Data Packet (HW -> PC, default port 1026)
  // -------------------------------------------------------------------------
  TMicDataPacket = packed record
    Seq: array[0..3] of Byte;
    Samples: array[0..63] of SmallInt;  // 64 x 16-bit signed, big-endian
  end;

  // -------------------------------------------------------------------------
  // DDC I&Q Packet (HW -> PC, DDC0 default port 1035)
  // -------------------------------------------------------------------------
  TDDCIQPacket = packed record
    Seq: array[0..3] of Byte;            // 0-3
    TimeStamp: array[0..7] of Byte;      // 4-11: 64-bit VITA-49 timestamp
    BitsPerSample: array[0..1] of Byte;  // 12-13
    SamplesPerFrame: array[0..1] of Byte;// 14-15
    // From byte 16: interleaved I/Q samples
    // For 24-bit: 3 bytes I, 3 bytes Q repeated
    // Max 238 IQ pairs for 24-bit = 1428 bytes, total packet = 1444
    IQData: array[0..1427] of Byte;      // 16-1443
  end;

  // -------------------------------------------------------------------------
  // DDC Audio Packet (PC -> HW, default port 1028)
  // 64 Left + 64 Right 16-bit samples at 48ksps
  // -------------------------------------------------------------------------
  TDDCAudioPacket = packed record
    Seq: array[0..3] of Byte;
    // Interleaved Left/Right 16-bit samples (big-endian)
    // [L0_hi, L0_lo, R0_hi, R0_lo, L1_hi, L1_lo, R1_hi, R1_lo, ...]
    AudioData: array[0..255] of Byte;   // 64 * 4 bytes
  end;

  // -------------------------------------------------------------------------
  // DUC I&Q Data Packet (PC -> HW, default port 1029)
  // 240 IQ pairs at 192ksps, 24-bit
  // -------------------------------------------------------------------------
  TDUCIQPacket = packed record
    Seq: array[0..3] of Byte;
    // 240 * 6 bytes = 1440 bytes
    IQData: array[0..1439] of Byte;
  end;

// ---------------------------------------------------------------------------
// Helper functions for Big-Endian packing/unpacking
// ---------------------------------------------------------------------------

// Pack a 32-bit word into 4 bytes (Big-Endian)
procedure PackU32BE(Value: LongWord; out B: array of Byte; Offset: Integer = 0);
// Unpack 4 bytes to a 32-bit word (Big-Endian)
function UnpackU32BE(const B: array of Byte; Offset: Integer = 0): LongWord;
// Pack a 16-bit word into 2 bytes (Big-Endian)
procedure PackU16BE(Value: Word; out B: array of Byte; Offset: Integer = 0);
// Unpack 2 bytes to a 16-bit word (Big-Endian)
function UnpackU16BE(const B: array of Byte; Offset: Integer = 0): Word;

// Convert frequency in Hz to phase word for HPSDR hardware
function FreqToPhaseWord(FreqHz: Double): LongWord;

// Convert phase word back to frequency
function PhaseWordToFreq(PhaseWord: LongWord): Double;

// Convert raw ADC value to supply voltage.
// Returns -1.0 if the board has no supply voltage measurement.
function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;

// True when the board type reports supply voltage in HP Status.
function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;

// True when the board type reports supply current in HP Status.
function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;

// Convert raw ADC value to supply current in Amps (Board 5 / Orion MkII only)
// Uses UserADC1 (bytes 55-56). Returns -1 for unsupported boards.
// VoffMV/SensMVA — смещение нуля и чувствительность датчика тока.
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer;
  VoffMV: Double = 360.0; SensMVA: Double = 120.0): Double;

// Convert raw ADC value to RF power in Watts (ANAN-100)
function ADCToWatts100(RawADC: Word): Double;

// Convert raw ADC value to RF power in Watts (ANAN-10)
function ADCToWatts10(RawADC: Word): Double;

// Alex bridge FWD/REV detector → Watts; параметры зависят от модели трансивера:
//   0 = ANAN-10/100/100B/100D (Vref 3.3V)
//   1 = ANAN-200D / Orion     (Vref 5.0V)
//   2 = ANAN-7000DLE / Anvelina Pro 3 / G2 / Red Pitaya
//   3 = ANAN-8000DLE / Orion MkII
// IsRev — REV-детектор (свой bridge_volt/offset), Is6m — 6m-коррекция REV.
function AlexDetToWatts(RawADC: Word; DetModel: Integer;
  IsRev, Is6m: Boolean): Double;

implementation

procedure PackU32BE(Value: LongWord; out B: array of Byte; Offset: Integer);
begin
  B[Offset + 0] := (Value shr 24) and $FF;
  B[Offset + 1] := (Value shr 16) and $FF;
  B[Offset + 2] := (Value shr  8) and $FF;
  B[Offset + 3] :=  Value         and $FF;
end;

function UnpackU32BE(const B: array of Byte; Offset: Integer): LongWord;
begin
  Result := (LongWord(B[Offset]) shl 24)
          or (LongWord(B[Offset+1]) shl 16)
          or (LongWord(B[Offset+2]) shl 8)
          or  LongWord(B[Offset+3]);
end;

procedure PackU16BE(Value: Word; out B: array of Byte; Offset: Integer);
begin
  B[Offset + 0] := (Value shr 8) and $FF;
  B[Offset + 1] :=  Value        and $FF;
end;

function UnpackU16BE(const B: array of Byte; Offset: Integer): Word;
begin
  Result := (Word(B[Offset]) shl 8) or Word(B[Offset+1]);
end;

function FreqToPhaseWord(FreqHz: Double): LongWord;
begin
  // phase_word = 2^32 * freq / DSP_CLOCK
  Result := Round(4294967296.0 * FreqHz / DSP_CLOCK_HZ);
end;

function PhaseWordToFreq(PhaseWord: LongWord): Double;
begin
  Result := PhaseWord * DSP_CLOCK_HZ / 4294967296.0;
end;

function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;
begin
  // Protocol Appendix A gives the ADC reference voltage only.
  // The PCB voltage divider must also be applied.
  // Board 0 (Atlas): no supply voltage measurement.
  // Boards 1,2,3 (Hermes/Angelia): Vref=3.3V, PCB divider R1=4.7kΩ/R2=0.82kΩ.
  // Board 4 (Orion): protocol reports supply voltage with a 5.0V constant.
  // Board 5 (Orion MkII): Vref=5.0V, PCB divider R1=22kΩ/R2=1.1kΩ.
  //   NOTE: for board 5 caller must pass UserADC0, not supply_volts bytes.
  // Other boards: not available (return -1).
  case BoardType of
    1, 2, 3: Result := (RawADC / 4095.0) * 3.3 * ((4.7 + 0.82) / 0.82);
    4:       Result := (RawADC / 4095.0) * 5.0;
    5:       Result := (RawADC / 4095.0) * 5.0 * ((22.0 + 1.0) / 1.1);
  else       Result := -1.0;
  end;
end;

function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;
begin
  Result := BoardType in [1, 2, 3, 4, 5];
end;

function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;
begin
  Result := BoardType = 5;
end;

function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer;
  VoffMV: Double; SensMVA: Double): Double;
begin
  // Board 5 (Orion MkII): UserADC1, Vref=5V → mV = adc/4095*5000.
  // Formula: amps = (mV - Voff) / Sens. Defaults: Voff=360 mV,
  // Sens=120 mV/A; both values can be overridden by calibration.
  if (BoardType = 5) and (SensMVA > 0) then
  begin
    Result := ((RawADC / 4095.0) * 5000.0 - VoffMV) / SensMVA;
    if Result < 0.0 then Result := 0.0;
  end else
    Result := -1.0;
end;

function ADCToWatts100(RawADC: Word): Double;
var
  V: Double;
begin
  // W = (ADC/4095 * 3.3)^2 / 0.095  (ANAN-100, 0-150W range)
  V := (RawADC / 4095.0) * 3.3;
  Result := (V * V) / 0.095;
end;

function ADCToWatts10(RawADC: Word): Double;
var
  V: Double;
begin
  // W = (ADC/4095 * 3.3)^2 / 0.09  (ANAN-10, 0-20W range)
  V := (RawADC / 4095.0) * 3.3;
  Result := (V * V) / 0.09;
end;

function AlexDetToWatts(RawADC: Word; DetModel: Integer;
  IsRev, Is6m: Boolean): Double;
var
  Bridge, Vref, V: Double;
  CalOff: Integer;
begin
  // W = ((ADC - CalOff)/4095 * Vref)^2 / Bridge.
  case DetModel of
    1: begin  // ANAN-200D / Orion
         Vref := 5.0;
         if IsRev then begin
           Bridge := 0.108; if Is6m then Bridge := 0.5;
           CalOff := 2;
         end else begin
           Bridge := 0.108; CalOff := 4;
         end;
       end;
    2: begin  // ANAN-7000DLE / Anvelina Pro 3 / G2 / Red Pitaya
         Vref := 5.0;
         if IsRev then begin
           Bridge := 0.15; if Is6m then Bridge := 0.7;
           CalOff := 28;
         end else begin
           Bridge := 0.12; CalOff := 32;
         end;
       end;
    3: begin  // ANAN-8000DLE / Orion MkII
         Vref := 5.0; Bridge := 0.08;
         if IsRev then CalOff := 16 else CalOff := 18;
       end;
  else begin  // 0: ANAN-10/100/100B/100D
         Vref := 3.3;
         if IsRev then begin
           Bridge := 0.095; if Is6m then Bridge := 0.5;
           CalOff := 3;
         end else begin
           Bridge := 0.095; CalOff := 6;
         end;
       end;
  end;
  V := ((RawADC - CalOff) / 4095.0) * Vref;
  if V < 0 then V := 0;
  Result := (V * V) / Bridge;
end;

end.
