mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
add Alex setup
This commit is contained in:
+102
-38
@@ -19,7 +19,7 @@ uses
|
||||
{$ELSE}
|
||||
Sockets, BaseUnix,
|
||||
{$ENDIF}
|
||||
HPSDRProtocol, SyncObjs;
|
||||
HPSDRProtocol, SyncObjs, Settings;
|
||||
|
||||
const
|
||||
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
|
||||
@@ -141,6 +141,7 @@ type
|
||||
FIsTransmitting: Boolean;
|
||||
FPAEnabled: Boolean;
|
||||
FAlexEnabled: Boolean;
|
||||
FAlexConfig: TAlexSettings; // per-band antenna + routing config
|
||||
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
|
||||
|
||||
FSendLock: TCriticalSection; // защита concurrent UDP sends
|
||||
@@ -192,6 +193,7 @@ type
|
||||
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
|
||||
Transmitting, PAEnabled, AlexEnabled: Boolean);
|
||||
procedure SetStepAtten(dB: Byte);
|
||||
procedure SetAlexConfig(const A: TAlexSettings);
|
||||
procedure SendFullHP;
|
||||
procedure SendDDCAudio(const LeftRight: array of SmallInt);
|
||||
procedure SendDUCIQ(const IData, QData: array of Integer);
|
||||
@@ -611,6 +613,7 @@ begin
|
||||
FIsTransmitting := False;
|
||||
FPAEnabled := True;
|
||||
FAlexEnabled := True;
|
||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||
FSendLock := TCriticalSection.Create;
|
||||
FDUCIQLock := TCriticalSection.Create;
|
||||
FDUCIQSem := RTLEventCreate;
|
||||
@@ -1071,65 +1074,126 @@ end;
|
||||
// Transmitting — режим передачи
|
||||
// IsOrion2 — ANAN-7000/8000 (другие BPF)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Преобразует частоту в индекс диапазона 0=160m..10=6m (Thetis-compatible).
|
||||
function FreqToBandIndex(FreqHz: Double): Integer;
|
||||
begin
|
||||
if FreqHz >= 39850000 then Result := 10 // 6m
|
||||
else if FreqHz >= 26465000 then Result := 9 // 10m
|
||||
else if FreqHz >= 23170000 then Result := 8 // 12m
|
||||
else if FreqHz >= 19584000 then Result := 7 // 15m
|
||||
else if FreqHz >= 16209000 then Result := 6 // 17m
|
||||
else if FreqHz >= 12075000 then Result := 5 // 20m
|
||||
else if FreqHz >= 8700000 then Result := 4 // 30m
|
||||
else if FreqHz >= 6202000 then Result := 3 // 40m
|
||||
else if FreqHz >= 4665000 then Result := 2 // 60m
|
||||
else if FreqHz >= 2750000 then Result := 1 // 80m
|
||||
else Result := 0; // 160m
|
||||
end;
|
||||
|
||||
// Строит 32-битное слово Alex0 для HP-пакета (байты 1432-1435).
|
||||
// Протокол: openHPSDR Ethernet Protocol v4.3, Appendix D.
|
||||
// Все биты active-high. Bit 27 = T/R relay (0=RX, 1=TX).
|
||||
// Биты 24-26: ANT1/ANT2/ANT3. Биты 20-23,29-31: LPF. Биты 1-12: HPF/BPF.
|
||||
// Биты 8-11: вторичный RX вход (XVTR/Ext1/Ext2/Bypass relay).
|
||||
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
|
||||
Transmitting, IsOrion2: Boolean): LongWord;
|
||||
Transmitting, IsOrion2: Boolean;
|
||||
const Alex: TAlexSettings): LongWord;
|
||||
var
|
||||
txf: Double;
|
||||
BandIdx: Integer;
|
||||
AntVal: Byte;
|
||||
begin
|
||||
Result := 0;
|
||||
|
||||
// TX relay при передаче
|
||||
// Bit 27 ($08000000) = T/R relay для всех плат
|
||||
// Bit 18 ($00040000) = TxRx Status для Orion MkII / Saturn / G2
|
||||
// Bit 27 = T/R relay (0=RX, 1=TX).
|
||||
// Bit 18 = TxRx Status для Orion MkII 5.2 / 7000DLE / Saturn / G2.
|
||||
if Transmitting then
|
||||
begin
|
||||
Result := Result or $08000000; // ALEX_TX_RELAY bit27
|
||||
Result := Result or $08000000; // bit 27: T/R relay
|
||||
if IsOrion2 then
|
||||
Result := Result or $00040000; // TxRx Status bit18 (ANAN-7/8000, Saturn)
|
||||
Result := Result or $00040000; // bit 18: TxRx Status
|
||||
end;
|
||||
|
||||
// RX HPF (для ANAN-100/200) или BPF (для ANAN-7000)
|
||||
// HPF (ANAN-100/200) или BPF (ANAN-7000/8000/Saturn/G2) — по RX-частоте.
|
||||
if IsOrion2 then
|
||||
begin
|
||||
// Band-pass filters ANAN-7000/8000
|
||||
if RXFreqHz < 1500000 then Result := Result or $00001000 // BYPASS_BPF
|
||||
else if RXFreqHz < 2100000 then Result := Result or $00000040 // 160 BPF
|
||||
else if RXFreqHz < 5500000 then Result := Result or $00000020 // 80/60 BPF
|
||||
else if RXFreqHz < 11000000 then Result := Result or $00000010 // 40/30 BPF
|
||||
else if RXFreqHz < 22000000 then Result := Result or $00000002 // 20/15 BPF
|
||||
else if RXFreqHz < 35600000 then Result := Result or $00000004 // 12/10 BPF
|
||||
else Result := Result or $00000008; // 6m+preamp
|
||||
// Band-pass filters — Orion MkII 5.2, ANAN-7000DLE, Saturn, ANAN-G2
|
||||
if RXFreqHz < 1500000 then Result := Result or $00001000 // bit 12: HF Bypass
|
||||
else if RXFreqHz < 2100000 then Result := Result or $00000040 // bit 6: 160m BPF
|
||||
else if RXFreqHz < 5500000 then Result := Result or $00000020 // bit 5: 80/60m BPF
|
||||
else if RXFreqHz < 11000000 then Result := Result or $00000010 // bit 4: 40/30m BPF
|
||||
else if RXFreqHz < 22000000 then Result := Result or $00000002 // bit 1: 20/15m BPF
|
||||
else if RXFreqHz < 35600000 then Result := Result or $00000004 // bit 2: 12/10m BPF
|
||||
else Result := Result or $00000008; // bit 3: 6m + preamp
|
||||
end
|
||||
else
|
||||
begin
|
||||
// High-pass filters ANAN-100/200
|
||||
if RXFreqHz < 1800000 then Result := Result or $00001000 // BYPASS_HPF
|
||||
else if RXFreqHz < 6500000 then Result := Result or $00000040 // 1.5 MHz HPF
|
||||
else if RXFreqHz < 9500000 then Result := Result or $00000020 // 6.5 MHz HPF
|
||||
else if RXFreqHz < 13000000 then Result := Result or $00000010 // 9.5 MHz HPF
|
||||
else if RXFreqHz < 20000000 then Result := Result or $00000002 // 13 MHz HPF
|
||||
else if RXFreqHz < 50000000 then Result := Result or $00000004 // 20 MHz HPF
|
||||
else Result := Result or $00000008; // 6m preamp
|
||||
// High-pass filters — Atlas, Hermes, ANAN-10/10E/100/100B/100D/200D
|
||||
if RXFreqHz < 1800000 then Result := Result or $00001000 // bit 12: HF Bypass
|
||||
else if RXFreqHz < 6500000 then Result := Result or $00000040 // bit 6: 1.5 MHz HPF
|
||||
else if RXFreqHz < 9500000 then Result := Result or $00000020 // bit 5: 6.5 MHz HPF
|
||||
else if RXFreqHz < 13000000 then Result := Result or $00000010 // bit 4: 9.5 MHz HPF
|
||||
else if RXFreqHz < 20000000 then Result := Result or $00000002 // bit 1: 13 MHz HPF
|
||||
else if RXFreqHz < 50000000 then Result := Result or $00000004 // bit 2: 20 MHz HPF
|
||||
else Result := Result or $00000008; // bit 3: 6m preamp
|
||||
end;
|
||||
|
||||
// TX LPF — при RX через Ant1/2/3 сигнал идёт через TX LPF тоже
|
||||
// (для pre-Orion2 без внешней антенны)
|
||||
// TX LPF — по TX-частоте. В RX-режиме для не-Orion2 плат RX-сигнал идёт
|
||||
// через TX LPF (общий сигнальный путь), поэтому используем RX-частоту.
|
||||
if not Transmitting and not IsOrion2 then
|
||||
txf := RXFreqHz // RX через Ant1: используем RX частоту для LPF
|
||||
txf := RXFreqHz
|
||||
else
|
||||
txf := TXFreqHz;
|
||||
|
||||
if txf > 35600000 then Result := Result or $20000000 // 6m bypass LPF
|
||||
else if txf > 24000000 then Result := Result or $40000000 // 12/10m LPF
|
||||
else if txf > 16500000 then Result := Result or $80000000 // 17/15m LPF
|
||||
else if txf > 8000000 then Result := Result or $00100000 // 30/20m LPF
|
||||
else if txf > 5000000 then Result := Result or $00200000 // 60/40m LPF
|
||||
else if txf > 2500000 then Result := Result or $00400000 // 80m LPF
|
||||
else Result := Result or $00800000; // 160m LPF
|
||||
if txf > 35600000 then Result := Result or $20000000 // bit 29: 6m bypass LPF
|
||||
else if txf > 24000000 then Result := Result or $40000000 // bit 30: 12/10m LPF
|
||||
else if txf > 16500000 then Result := Result or $80000000 // bit 31: 17/15m LPF
|
||||
else if txf > 8000000 then Result := Result or $00100000 // bit 20: 30/20m LPF
|
||||
else if txf > 5000000 then Result := Result or $00200000 // bit 21: 60/40m LPF
|
||||
else if txf > 2500000 then Result := Result or $00400000 // bit 22: 80m LPF
|
||||
else Result := Result or $00800000; // bit 23: 160m LPF
|
||||
|
||||
// TX antenna — ANT1 по умолчанию
|
||||
if not Transmitting then
|
||||
Result := Result or $01000000; // ALEX_TX_ANTENNA_1
|
||||
// Биты 24-26: ANT 1/2/3. Выбор зависит от диапазона и режима TX/RX.
|
||||
// Дефолт ANT1 — безопасен при любом состоянии.
|
||||
BandIdx := FreqToBandIndex(RXFreqHz);
|
||||
if Transmitting then
|
||||
AntVal := Alex.TxAnt[BandIdx]
|
||||
else
|
||||
AntVal := Alex.RxAnt[BandIdx];
|
||||
if AntVal < 1 then AntVal := 1;
|
||||
case AntVal of
|
||||
2: Result := Result or $02000000; // bit 25: ANT 2
|
||||
3: Result := Result or $04000000; // bit 26: ANT 3
|
||||
else Result := Result or $01000000; // bit 24: ANT 1 (дефолт)
|
||||
end;
|
||||
|
||||
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
|
||||
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
|
||||
if Transmitting then
|
||||
begin
|
||||
if Alex.RxBypassOnTx then
|
||||
Result := Result or $00000800; // bit 11: Bypass relay
|
||||
if Alex.Ext1OnTx then
|
||||
Result := Result or $00000200 // bit 9: Ext1
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
end
|
||||
else
|
||||
begin
|
||||
case Alex.RxOnly[BandIdx] of
|
||||
1: Result := Result or $00000200 // bit 9: Ext1
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
2: Result := Result or $00000400 // bit 10: Ext2
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
3: Result := Result or $00000100 // bit 8: XVTR DDC In
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetAlexConfig(const A: TAlexSettings);
|
||||
begin
|
||||
FAlexConfig := A;
|
||||
if FConnected then SendFullHP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
||||
@@ -1274,7 +1338,7 @@ begin
|
||||
if FAlexEnabled then
|
||||
begin
|
||||
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
|
||||
FIsTransmitting, IsOrion2);
|
||||
FIsTransmitting, IsOrion2, FAlexConfig);
|
||||
Buf[1432] := (Alex0 shr 24) and $FF;
|
||||
Buf[1433] := (Alex0 shr 16) and $FF;
|
||||
Buf[1434] := (Alex0 shr 8) and $FF;
|
||||
|
||||
+27
-1
@@ -159,6 +159,7 @@ type
|
||||
FAudioOutDevName: string; // текущее имя выходного устройства
|
||||
FAudioInDevName: string; // текущее имя входного устройства (TX mic)
|
||||
FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC)
|
||||
FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board)
|
||||
FWDSPReady: Boolean; // True когда WDSP открыт и работает
|
||||
|
||||
// ---- State ----
|
||||
@@ -560,6 +561,7 @@ type
|
||||
const SerPort: array of string;
|
||||
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
|
||||
TcpEnabled: Boolean; TcpPort: Integer);
|
||||
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
||||
// -------------------------------------------------------------------------
|
||||
procedure BtnWfAGCClick(Sender: TObject);
|
||||
procedure BtnWfNFClick(Sender: TObject);
|
||||
@@ -1173,8 +1175,9 @@ begin
|
||||
FNetwork.OnHPStatus := OnHPStatusCB;
|
||||
FNetwork.OnDDCIQ := OnDDCIQCB;
|
||||
FNetwork.OnMicPacket := OnMicPacketCB;
|
||||
// Дефолты TX (на случай если устройство ещё не выбрано).
|
||||
// Дефолты TX и Alex (на случай если устройство ещё не выбрано).
|
||||
TSettingsManager.DefaultTX(FTXSettings);
|
||||
TSettingsManager.DefaultAlex(FAlexSettings);
|
||||
|
||||
BuildUI;
|
||||
StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
|
||||
@@ -3333,6 +3336,7 @@ begin
|
||||
begin
|
||||
SaveCurrentBand;
|
||||
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
|
||||
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
||||
end;
|
||||
FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate);
|
||||
FSettings.Save;
|
||||
@@ -3467,6 +3471,9 @@ begin
|
||||
CATApplySettings(G_Settings);
|
||||
// TX-настройки per-device — отдельная JSON-секция "tx"
|
||||
FSettings.LoadTX(FDevMAC, FTXSettings);
|
||||
// Alex-настройки per-device (антенны/маршрутизация)
|
||||
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
||||
FNetwork.SetAlexConfig(FAlexSettings);
|
||||
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
|
||||
FTXSpecRange := FTXSettings.TXSpecRange;
|
||||
if FTXSettings.TXSpecGridStep > 0 then
|
||||
@@ -4482,6 +4489,10 @@ procedure TMainForm.ApplyMOX(Active: Boolean);
|
||||
var
|
||||
WasTransmitting: Boolean;
|
||||
begin
|
||||
// Safety: block TX on bands marked DoNotTx in Alex config
|
||||
if Active and FDevConnected
|
||||
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
|
||||
Exit;
|
||||
WasTransmitting := FTransmitting;
|
||||
FTransmitting := Active;
|
||||
FDUCPendingCount := 0;
|
||||
@@ -5437,6 +5448,18 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings);
|
||||
begin
|
||||
FAlexSettings := A;
|
||||
if FNetwork.Connected then
|
||||
FNetwork.SetAlexConfig(A);
|
||||
if FDevConnected then
|
||||
begin
|
||||
FSettings.SaveAlex(FDevMAC, A);
|
||||
FSettings.Save;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrkVolumeChange(Sender: TObject);
|
||||
begin
|
||||
FVolume := TrkVolume.Position;
|
||||
@@ -5721,9 +5744,12 @@ begin
|
||||
SF.OnCATChange := OnCATSettingsChange;
|
||||
SF.OnThemeChange := SetLightTheme;
|
||||
SF.OnTXChange := OnTXSettingsChange;
|
||||
SF.OnAlexChange := OnAlexSettingsChange;
|
||||
end;
|
||||
SF := TSettingsForm(FSettingsForm);
|
||||
SF.LoadTXSettings(FTXSettings);
|
||||
SF.LoadAlexSettings(FAlexSettings,
|
||||
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
|
||||
SF.LoadPASettings(FPAMaxPower, FPABandCal);
|
||||
SF.LoadCATSettings(
|
||||
FCATLastGlobal.CATSerialEnabled,
|
||||
|
||||
@@ -103,6 +103,23 @@ type
|
||||
TXSpecGridStep: Double; // grid step in dB, e.g. 10
|
||||
end;
|
||||
|
||||
// Alex (Antenna + Filter board) settings per-device.
|
||||
// Хранятся в JSON-секции "alex" под MAC.
|
||||
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
|
||||
TAlexSettings = record
|
||||
// Per-band (11 bands: 0=160m..10=6m)
|
||||
RxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
|
||||
TxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
|
||||
// Вторичный RX вход: 0=выкл, 1=BYPS(Ext1+Bypass), 2=EXT1(Ext2+Bypass), 3=XVTR(XvtrDDC+Bypass)
|
||||
RxOnly: array[0..CFG_BAND_COUNT-1] of Byte;
|
||||
DoNotTx: array[0..CFG_BAND_COUNT-1] of Boolean; // программный запрет TX на диапазоне
|
||||
// Глобальные флаги в режиме TX
|
||||
RxBypassOnTx: Boolean; // бит 11 (Bypass relay) во время TX
|
||||
Ext1OnTx: Boolean; // бит 9 (Ext1) + бит 11 во время TX
|
||||
GndBpf2OnTx: Boolean; // заземление BPF2/RX2 во время TX (Alex1 бит 8, ANAN-8000DLE)
|
||||
EnableXvtrHf: Boolean; // режим XVTR HF
|
||||
end;
|
||||
|
||||
TGlobalSettings = record
|
||||
Volume: Integer;
|
||||
DriveLevel: Integer;
|
||||
@@ -210,6 +227,10 @@ type
|
||||
// CAT settings — не привязаны к MAC, хранятся в корне JSON (доступны до подключения)
|
||||
procedure SaveCATSettings(const G: TGlobalSettings);
|
||||
procedure LoadCATSettings(var G: TGlobalSettings);
|
||||
// Alex (antenna) settings per-device (JSON-секция "alex" под MAC).
|
||||
class procedure DefaultAlex(out A: TAlexSettings);
|
||||
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
|
||||
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
|
||||
// Тема — глобальная настройка, не привязана к устройству
|
||||
procedure SaveTheme(ALightTheme: Boolean);
|
||||
function LoadTheme: Boolean;
|
||||
@@ -914,6 +935,65 @@ begin
|
||||
JW(O,'tx_spec_grid_step', T.TXSpecGridStep);
|
||||
end;
|
||||
|
||||
class procedure TSettingsManager.DefaultAlex(out A: TAlexSettings);
|
||||
var i: Integer;
|
||||
begin
|
||||
FillChar(A, SizeOf(A), 0);
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
A.RxAnt[i] := 1; // ANT1 — безопасный дефолт
|
||||
A.TxAnt[i] := 1; // ANT1
|
||||
A.RxOnly[i] := 0; // вторичный вход выключен
|
||||
A.DoNotTx[i] := False;
|
||||
end;
|
||||
A.RxBypassOnTx := False;
|
||||
A.Ext1OnTx := False;
|
||||
A.GndBpf2OnTx := False;
|
||||
A.EnableXvtrHf := False;
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadAlex(const MAC: array of Byte;
|
||||
out A: TAlexSettings): Boolean;
|
||||
var MacStr: string; DevObj, O: TJSONObject; i: Integer;
|
||||
begin
|
||||
DefaultAlex(A);
|
||||
MacStr := MacToStr(MAC);
|
||||
Result := FRoot.Find(MacStr) <> nil;
|
||||
if not Result then Exit;
|
||||
DevObj := GetDevObj(MacStr);
|
||||
if DevObj.Find('alex') = nil then Exit;
|
||||
O := EnsureObj(DevObj, 'alex');
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
A.RxAnt[i] := EnsureRange(JI(O, 'rx_ant_' + IntToStr(i), 1), 1, 3);
|
||||
A.TxAnt[i] := EnsureRange(JI(O, 'tx_ant_' + IntToStr(i), 1), 1, 3);
|
||||
A.RxOnly[i] := EnsureRange(JI(O, 'rx_only_' + IntToStr(i), 0), 0, 3);
|
||||
A.DoNotTx[i] := JB(O, 'do_not_tx_' + IntToStr(i), False);
|
||||
end;
|
||||
A.RxBypassOnTx := JB(O, 'rx_bypass_on_tx', False);
|
||||
A.Ext1OnTx := JB(O, 'ext1_on_tx', False);
|
||||
A.GndBpf2OnTx := JB(O, 'gnd_bpf2_on_tx', False);
|
||||
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveAlex(const MAC: array of Byte;
|
||||
const A: TAlexSettings);
|
||||
var O: TJSONObject; i: Integer;
|
||||
begin
|
||||
O := EnsureObj(GetDevObj(MacToStr(MAC)), 'alex');
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
JW(O, 'rx_ant_' + IntToStr(i), Integer(A.RxAnt[i]));
|
||||
JW(O, 'tx_ant_' + IntToStr(i), Integer(A.TxAnt[i]));
|
||||
JW(O, 'rx_only_' + IntToStr(i), Integer(A.RxOnly[i]));
|
||||
JW(O, 'do_not_tx_' + IntToStr(i), A.DoNotTx[i]);
|
||||
end;
|
||||
JW(O, 'rx_bypass_on_tx', A.RxBypassOnTx);
|
||||
JW(O, 'ext1_on_tx', A.Ext1OnTx);
|
||||
JW(O, 'gnd_bpf2_on_tx', A.GndBpf2OnTx);
|
||||
JW(O, 'enable_xvtr_hf', A.EnableXvtrHf);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveTheme(ALightTheme: Boolean);
|
||||
var O: TJSONObject;
|
||||
begin
|
||||
|
||||
+338
-1
@@ -54,6 +54,7 @@ type
|
||||
const SerStopBits: array of Integer;
|
||||
const SerParity: array of Integer;
|
||||
TcpEnabled: Boolean; TcpPort: Integer) of object;
|
||||
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
||||
|
||||
{ TSettingsForm }
|
||||
TSettingsForm = class(TForm)
|
||||
@@ -176,6 +177,29 @@ type
|
||||
FEdTXRange: TEdit;
|
||||
FCmbTXGridStep: TComboBox;
|
||||
|
||||
// ---- Alex / Antenna tab controls ----
|
||||
FPageAlex: TScrollBox;
|
||||
FNavAlex: TFlatButton;
|
||||
// Per-band antenna radio buttons (bands 0..10 = 160m..6m)
|
||||
FAlexRxAnt: array[0..10, 1..3] of TRadioButton;
|
||||
FAlexTxAnt: array[0..10, 1..3] of TRadioButton;
|
||||
// Per-band secondary RX (взаимно исключающие checkboxes)
|
||||
FAlexRxByps: array[0..10] of TCheckBox; // BYPS: Ext1+Bypass
|
||||
FAlexRxExt1: array[0..10] of TCheckBox; // EXT1: Ext2+Bypass
|
||||
FAlexRxXvtr: array[0..10] of TCheckBox; // XVTR: XvtrDDC+Bypass
|
||||
// Per-band Do Not TX
|
||||
FAlexDoNotTx: array[0..10] of TCheckBox;
|
||||
// Global TX routing flags
|
||||
FAlexRxBypassOnTx: TCheckBox;
|
||||
FAlexExt1OnTx: TCheckBox;
|
||||
FAlexGndBpf2OnTx: TCheckBox;
|
||||
FAlexEnableXvtrHf: TCheckBox;
|
||||
// Current Alex config state
|
||||
FAlexConfig: TAlexSettings;
|
||||
FAlexBoardType: Integer;
|
||||
// Callback
|
||||
FOnAlexChange: TOnAlexChange;
|
||||
|
||||
// ---- CAT tab controls ----
|
||||
FCATSerialEn: array[0..3] of TCheckBox;
|
||||
FCATSerialPort: array[0..3] of TEdit;
|
||||
@@ -211,6 +235,11 @@ type
|
||||
procedure BuildPATab;
|
||||
procedure BuildCATTab;
|
||||
procedure BuildTransmitTab;
|
||||
procedure BuildAlexTab;
|
||||
procedure CollectAlexFromUI;
|
||||
procedure FireAlexChange;
|
||||
procedure OnAlexAnyChange(Sender: TObject);
|
||||
procedure OnAlexRxOnlyChange(Sender: TObject);
|
||||
procedure CollectTXFromUI;
|
||||
procedure FireTXChange;
|
||||
procedure OnTXAnyChange(Sender: TObject);
|
||||
@@ -288,8 +317,10 @@ type
|
||||
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
|
||||
TcpEnabled: Boolean; TcpPort: Integer);
|
||||
procedure LoadTXSettings(const T: TTXSettings);
|
||||
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
|
||||
|
||||
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
|
||||
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
||||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||||
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
|
||||
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
|
||||
@@ -508,6 +539,7 @@ begin
|
||||
FNavPA := MakeNavButton('Power Amplifier', 254);
|
||||
FNavAdvanced := MakeNavButton('Advanced', 290);
|
||||
FNavCAT := MakeNavButton('CAT', 326);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 362);
|
||||
|
||||
FContentPanel := TPanel.Create(Self);
|
||||
FContentPanel.Parent := Self;
|
||||
@@ -523,6 +555,9 @@ begin
|
||||
FPagePA := MakeScrollPage;
|
||||
FPageAdvanced := MakeScrollPage;
|
||||
FPageCAT := MakeScrollPage;
|
||||
FPageAlex := MakeScrollPage;
|
||||
FAlexBoardType := 0;
|
||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||
|
||||
BuildAudioTab;
|
||||
BuildDisplayTab;
|
||||
@@ -530,6 +565,7 @@ begin
|
||||
BuildTransmitTab;
|
||||
BuildPATab;
|
||||
BuildCATTab;
|
||||
BuildAlexTab;
|
||||
|
||||
FBtnClose := TFlatButton.Create(Self);
|
||||
FBtnClose.Parent := Self;
|
||||
@@ -558,7 +594,8 @@ begin
|
||||
else if Sender = FNavTransmit then SelectPage(FPageTransmit, FNavTransmit)
|
||||
else if Sender = FNavPA then SelectPage(FPagePA, FNavPA)
|
||||
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
|
||||
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT);
|
||||
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT)
|
||||
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
|
||||
@@ -571,6 +608,7 @@ begin
|
||||
FPagePA.Visible := APage = FPagePA;
|
||||
FPageAdvanced.Visible := APage = FPageAdvanced;
|
||||
FPageCAT.Visible := APage = FPageCAT;
|
||||
FPageAlex.Visible := APage = FPageAlex;
|
||||
|
||||
FNavAudio.Active := ANav = FNavAudio;
|
||||
FNavDisplay.Active := ANav = FNavDisplay;
|
||||
@@ -580,6 +618,7 @@ begin
|
||||
FNavPA.Active := ANav = FNavPA;
|
||||
FNavAdvanced.Active := ANav = FNavAdvanced;
|
||||
FNavCAT.Active := ANav = FNavCAT;
|
||||
FNavAlex.Active := ANav = FNavAlex;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2194,4 +2233,302 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BuildAlexTab
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
procedure TSettingsForm.BuildAlexTab;
|
||||
const
|
||||
MARGIN = 22;
|
||||
PAD = 12;
|
||||
HDR1_H = 22; // height of "RX Ant / TX Ant" header row
|
||||
HDR2_H = 20; // height of "1 2 3 / BYPS EXT1 XVTR" sub-header row
|
||||
ROW_H = 24;
|
||||
BANDS = 11;
|
||||
BAND_X = PAD;
|
||||
RX1_X = PAD + 50;
|
||||
RX2_X = PAD + 76;
|
||||
RX3_X = PAD + 102;
|
||||
BYPS_X = PAD + 136;
|
||||
EXT1_X = PAD + 192;
|
||||
XVTR_X = PAD + 248;
|
||||
TX1_X = PAD + 308;
|
||||
TX2_X = PAD + 334;
|
||||
TX3_X = PAD + 360;
|
||||
DNTX_X = PAD + 398;
|
||||
GRP_W = DNTX_X + 80;
|
||||
GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + BANDS * ROW_H + PAD;
|
||||
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
|
||||
TXGRP_H = GRP_TITLE_H + 4 * 28 + PAD;
|
||||
|
||||
BAND_NAMES: array[0..10] of string =
|
||||
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
||||
|
||||
var
|
||||
Grp, GrpTX: TPanel;
|
||||
b: Integer;
|
||||
Y: Integer;
|
||||
Chk: TCheckBox;
|
||||
RxGrp, TxGrp: TPanel;
|
||||
|
||||
// In Lazarus, TRadioButton has no GroupIndex; buttons are grouped by parent.
|
||||
// We use small invisible panels as parents so each band's RX and TX groups
|
||||
// are independent of each other.
|
||||
function MkRbGroup(AParent: TWinControl; AX, AY, AW: Integer): TPanel;
|
||||
begin
|
||||
Result := TPanel.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.SetBounds(AX, AY, AW, 20);
|
||||
Result.BevelOuter := bvNone;
|
||||
Result.Color := CLR_PANEL;
|
||||
Result.Caption := '';
|
||||
end;
|
||||
|
||||
function MkRb(AParent: TWinControl; AX: Integer): TRadioButton;
|
||||
begin
|
||||
Result := TRadioButton.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.Caption := '';
|
||||
Result.SetBounds(AX, 0, 22, 20);
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnAlexAnyChange;
|
||||
end;
|
||||
|
||||
function MkCh(AParent: TWinControl; const ACap: string;
|
||||
AX, AY, AW: Integer): TCheckBox;
|
||||
begin
|
||||
Result := TCheckBox.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.Caption := ACap;
|
||||
Result.SetBounds(AX, AY, AW, 20);
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnAlexAnyChange;
|
||||
end;
|
||||
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageAlex;
|
||||
Caption := 'Antenna / Alex';
|
||||
SetBounds(MARGIN, 18, 500, 28);
|
||||
Font.Name := UI_FONT; Font.Size := 16;
|
||||
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
|
||||
end;
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageAlex;
|
||||
Caption := 'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
|
||||
SetBounds(MARGIN, 48, 680, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
|
||||
// ---- Antenna grid group ----
|
||||
Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H);
|
||||
|
||||
// Header row 1 — group labels
|
||||
MakeLbl(Grp, 'Band', BAND_X, GRP_TITLE_H + 2, 44);
|
||||
MakeLbl(Grp, 'RX Antenna', RX1_X, GRP_TITLE_H + 2, 80);
|
||||
MakeLbl(Grp, 'Secondary RX', BYPS_X, GRP_TITLE_H + 2, 170);
|
||||
MakeLbl(Grp, 'TX Antenna', TX1_X, GRP_TITLE_H + 2, 80);
|
||||
MakeLbl(Grp, 'No TX', DNTX_X, GRP_TITLE_H + 2, 56);
|
||||
|
||||
// Header row 2 — sub-column labels
|
||||
MakeLbl(Grp, '1', RX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
MakeLbl(Grp, '2', RX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
MakeLbl(Grp, '3', RX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
MakeLbl(Grp, 'BYPS', BYPS_X, GRP_TITLE_H + HDR1_H + 2, 52);
|
||||
MakeLbl(Grp, 'EXT1', EXT1_X, GRP_TITLE_H + HDR1_H + 2, 52);
|
||||
MakeLbl(Grp, 'XVTR', XVTR_X, GRP_TITLE_H + HDR1_H + 2, 52);
|
||||
MakeLbl(Grp, '1', TX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
MakeLbl(Grp, '2', TX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
MakeLbl(Grp, '3', TX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
|
||||
|
||||
// Data rows (bands 0..10 = 160m..6m)
|
||||
for b := 0 to BANDS - 1 do
|
||||
begin
|
||||
Y := DATA_Y0 + b * ROW_H;
|
||||
MakeLbl(Grp, BAND_NAMES[b], BAND_X, Y + 2, 44);
|
||||
|
||||
// RX antenna — 3 radio buttons in a dedicated panel so they are mutually
|
||||
// exclusive only within that band row (Lazarus groups by parent, not GroupIndex)
|
||||
RxGrp := MkRbGroup(Grp, RX1_X, Y, RX3_X - RX1_X + 26);
|
||||
FAlexRxAnt[b, 1] := MkRb(RxGrp, 0);
|
||||
FAlexRxAnt[b, 2] := MkRb(RxGrp, RX2_X - RX1_X);
|
||||
FAlexRxAnt[b, 3] := MkRb(RxGrp, RX3_X - RX1_X);
|
||||
FAlexRxAnt[b, 1].Checked := True;
|
||||
|
||||
// Secondary RX checkboxes — mutually exclusive, handled in OnAlexRxOnlyChange
|
||||
// Tag encodes: b=BYPS, b+100=EXT1, b+200=XVTR
|
||||
Chk := MkCh(Grp, '', BYPS_X, Y, 22);
|
||||
Chk.Tag := b;
|
||||
Chk.OnChange := OnAlexRxOnlyChange;
|
||||
FAlexRxByps[b] := Chk;
|
||||
|
||||
Chk := MkCh(Grp, '', EXT1_X, Y, 22);
|
||||
Chk.Tag := b + 100;
|
||||
Chk.OnChange := OnAlexRxOnlyChange;
|
||||
FAlexRxExt1[b] := Chk;
|
||||
|
||||
Chk := MkCh(Grp, '', XVTR_X, Y, 22);
|
||||
Chk.Tag := b + 200;
|
||||
Chk.OnChange := OnAlexRxOnlyChange;
|
||||
FAlexRxXvtr[b] := Chk;
|
||||
|
||||
// TX antenna — same pattern: dedicated panel per band row
|
||||
TxGrp := MkRbGroup(Grp, TX1_X, Y, TX3_X - TX1_X + 26);
|
||||
FAlexTxAnt[b, 1] := MkRb(TxGrp, 0);
|
||||
FAlexTxAnt[b, 2] := MkRb(TxGrp, TX2_X - TX1_X);
|
||||
FAlexTxAnt[b, 3] := MkRb(TxGrp, TX3_X - TX1_X);
|
||||
FAlexTxAnt[b, 1].Checked := True;
|
||||
|
||||
// Do Not TX checkbox
|
||||
FAlexDoNotTx[b] := MkCh(Grp, '', DNTX_X, Y, 22);
|
||||
end;
|
||||
|
||||
// ---- TX Routing flags group ----
|
||||
GrpTX := MakeGroupPanel(FPageAlex, 'TX Routing',
|
||||
MARGIN, 86 + GRP_H + 12, 480, TXGRP_H);
|
||||
|
||||
FAlexRxBypassOnTx := MkCh(GrpTX, 'Rx BYPASS on TX',
|
||||
PAD, GRP_TITLE_H + 8, 440);
|
||||
FAlexExt1OnTx := MkCh(GrpTX, 'Ext 1 on TX',
|
||||
PAD, GRP_TITLE_H + 8 + 28, 440);
|
||||
FAlexGndBpf2OnTx := MkCh(GrpTX, 'Ground BPF2 on TX (ANAN-8000DLE)',
|
||||
PAD, GRP_TITLE_H + 8 + 56, 440);
|
||||
FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF',
|
||||
PAD, GRP_TITLE_H + 8 + 84, 440);
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Alex event handlers and data collectors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
procedure TSettingsForm.CollectAlexFromUI;
|
||||
var b, a: Integer;
|
||||
begin
|
||||
for b := 0 to 10 do
|
||||
begin
|
||||
FAlexConfig.RxAnt[b] := 1;
|
||||
for a := 1 to 3 do
|
||||
if (FAlexRxAnt[b, a] <> nil) and FAlexRxAnt[b, a].Checked then
|
||||
begin
|
||||
FAlexConfig.RxAnt[b] := a;
|
||||
Break;
|
||||
end;
|
||||
FAlexConfig.TxAnt[b] := 1;
|
||||
for a := 1 to 3 do
|
||||
if (FAlexTxAnt[b, a] <> nil) and FAlexTxAnt[b, a].Checked then
|
||||
begin
|
||||
FAlexConfig.TxAnt[b] := a;
|
||||
Break;
|
||||
end;
|
||||
if (FAlexRxXvtr[b] <> nil) and FAlexRxXvtr[b].Checked then
|
||||
FAlexConfig.RxOnly[b] := 3
|
||||
else if (FAlexRxExt1[b] <> nil) and FAlexRxExt1[b].Checked then
|
||||
FAlexConfig.RxOnly[b] := 2
|
||||
else if (FAlexRxByps[b] <> nil) and FAlexRxByps[b].Checked then
|
||||
FAlexConfig.RxOnly[b] := 1
|
||||
else
|
||||
FAlexConfig.RxOnly[b] := 0;
|
||||
FAlexConfig.DoNotTx[b] := (FAlexDoNotTx[b] <> nil) and FAlexDoNotTx[b].Checked;
|
||||
end;
|
||||
FAlexConfig.RxBypassOnTx := (FAlexRxBypassOnTx <> nil) and FAlexRxBypassOnTx.Checked;
|
||||
FAlexConfig.Ext1OnTx := (FAlexExt1OnTx <> nil) and FAlexExt1OnTx.Checked;
|
||||
FAlexConfig.GndBpf2OnTx := (FAlexGndBpf2OnTx <> nil) and FAlexGndBpf2OnTx.Checked;
|
||||
FAlexConfig.EnableXvtrHf := (FAlexEnableXvtrHf <> nil) and FAlexEnableXvtrHf.Checked;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.FireAlexChange;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
CollectAlexFromUI;
|
||||
if Assigned(FOnAlexChange) then
|
||||
FOnAlexChange(FAlexConfig);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnAlexAnyChange(Sender: TObject);
|
||||
begin
|
||||
FireAlexChange;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnAlexRxOnlyChange(Sender: TObject);
|
||||
var
|
||||
Tag, b: Integer;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
Tag := TCheckBox(Sender).Tag;
|
||||
if Tag >= 200 then
|
||||
begin
|
||||
b := Tag - 200; // XVTR
|
||||
if TCheckBox(Sender).Checked then
|
||||
begin
|
||||
FLoading := True;
|
||||
FAlexRxByps[b].Checked := False;
|
||||
FAlexRxExt1[b].Checked := False;
|
||||
FLoading := False;
|
||||
end;
|
||||
end
|
||||
else if Tag >= 100 then
|
||||
begin
|
||||
b := Tag - 100; // EXT1
|
||||
if TCheckBox(Sender).Checked then
|
||||
begin
|
||||
FLoading := True;
|
||||
FAlexRxByps[b].Checked := False;
|
||||
FAlexRxXvtr[b].Checked := False;
|
||||
FLoading := False;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
b := Tag; // BYPS
|
||||
if TCheckBox(Sender).Checked then
|
||||
begin
|
||||
FLoading := True;
|
||||
FAlexRxExt1[b].Checked := False;
|
||||
FAlexRxXvtr[b].Checked := False;
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
FireAlexChange;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
|
||||
var b, ant: Integer;
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
FAlexConfig := A;
|
||||
FAlexBoardType := BoardType;
|
||||
for b := 0 to 10 do
|
||||
begin
|
||||
for ant := 1 to 3 do
|
||||
if FAlexRxAnt[b, ant] <> nil then
|
||||
FAlexRxAnt[b, ant].Checked := (A.RxAnt[b] = ant);
|
||||
for ant := 1 to 3 do
|
||||
if FAlexTxAnt[b, ant] <> nil then
|
||||
FAlexTxAnt[b, ant].Checked := (A.TxAnt[b] = ant);
|
||||
if FAlexRxByps[b] <> nil then FAlexRxByps[b].Checked := (A.RxOnly[b] = 1);
|
||||
if FAlexRxExt1[b] <> nil then FAlexRxExt1[b].Checked := (A.RxOnly[b] = 2);
|
||||
if FAlexRxXvtr[b] <> nil then FAlexRxXvtr[b].Checked := (A.RxOnly[b] = 3);
|
||||
if FAlexDoNotTx[b] <> nil then FAlexDoNotTx[b].Checked := A.DoNotTx[b];
|
||||
end;
|
||||
if FAlexRxBypassOnTx <> nil then FAlexRxBypassOnTx.Checked := A.RxBypassOnTx;
|
||||
if FAlexExt1OnTx <> nil then FAlexExt1OnTx.Checked := A.Ext1OnTx;
|
||||
if FAlexGndBpf2OnTx <> nil then FAlexGndBpf2OnTx.Checked := A.GndBpf2OnTx;
|
||||
if FAlexEnableXvtrHf <> nil then FAlexEnableXvtrHf.Checked := A.EnableXvtrHf;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user