mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
add trancverter
This commit is contained in:
+71
-3
@@ -143,6 +143,21 @@ type
|
||||
FAlexEnabled: Boolean;
|
||||
FAlexConfig: TAlexSettings; // per-band antenna + routing config
|
||||
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
|
||||
// ---- XVTR (Transverter) state ----
|
||||
// FXvtrEnable — устанавливает bit 0 байта 1400 HP-пакета: включает выход
|
||||
// XVTR на трансивере (управляющий пин/реле). Обычно True, пока активный
|
||||
// диапазон — трансвертер.
|
||||
FXvtrEnable: Boolean;
|
||||
// FXvtrRxAntOverride — если 1..3, использовать эту антенну вместо
|
||||
// Alex.RxAnt[band] (per-XVTR RX antenna). 0 = не переопределять.
|
||||
FXvtrRxAntOverride: Byte;
|
||||
// FXvtrDisablePA — при TX подавляет бит 27 Alex (T/R relay): внутренний
|
||||
// PA остаётся в RX-положении, TX-сигнал идёт через XVTR-IF.
|
||||
FXvtrDisablePA: Boolean;
|
||||
// FXvtrUseRxDDCIn — True если RX тракт XVTR подключён к XVTR DDC IN пину
|
||||
// Alex (Alex bit 8 + bit 11). Эквивалент Alex.RxOnly=3, но включается
|
||||
// динамически при выборе XVTR-band, не привязан к band-индексу.
|
||||
FXvtrUseRxDDCIn: Boolean;
|
||||
|
||||
FSendLock: TCriticalSection; // защита concurrent UDP sends
|
||||
|
||||
@@ -194,6 +209,11 @@ type
|
||||
Transmitting, PAEnabled, AlexEnabled: Boolean);
|
||||
procedure SetStepAtten(dB: Byte);
|
||||
procedure SetAlexConfig(const A: TAlexSettings);
|
||||
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
||||
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
||||
// антенна и Alex routing возвращаются к per-band дефолту.
|
||||
procedure SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
|
||||
ARxAnt: Byte);
|
||||
procedure SendFullHP;
|
||||
procedure SendDDCAudio(const LeftRight: array of SmallInt);
|
||||
procedure SendDUCIQ(const IData, QData: array of Integer);
|
||||
@@ -1095,9 +1115,20 @@ end;
|
||||
// Все биты 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).
|
||||
//
|
||||
// XvtrRxAnt — override антенны для RX (1..3) если активен трансвертер;
|
||||
// 0 = не переопределять, использовать Alex.RxAnt[band].
|
||||
// XvtrUseRxDDCIn — True: при RX подключить XVTR DDC IN (Alex bit 8 + bit 11).
|
||||
// Применяется только в RX и только когда XvtrRxAnt активен.
|
||||
// XvtrDisablePA — True: при TX НЕ устанавливать bit 27 (T/R relay):
|
||||
// внутренний PA не активируется, IF-сигнал идёт по тракту
|
||||
// XVTR без усиления.
|
||||
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
|
||||
Transmitting, IsOrion2: Boolean;
|
||||
const Alex: TAlexSettings): LongWord;
|
||||
const Alex: TAlexSettings;
|
||||
XvtrRxAnt: Byte = 0;
|
||||
XvtrUseRxDDCIn: Boolean = False;
|
||||
XvtrDisablePA: Boolean = False): LongWord;
|
||||
var
|
||||
txf: Double;
|
||||
BandIdx: Integer;
|
||||
@@ -1107,9 +1138,13 @@ begin
|
||||
|
||||
// Bit 27 = T/R relay (0=RX, 1=TX).
|
||||
// Bit 18 = TxRx Status для Orion MkII 5.2 / 7000DLE / Saturn / G2.
|
||||
// Если XvtrDisablePA=True — НЕ устанавливаем bit 27 (PA остаётся в RX
|
||||
// положении). Bit 18 (TxRx status) всё равно устанавливаем — это просто
|
||||
// индикатор состояния для логики платы, не управление реле.
|
||||
if Transmitting then
|
||||
begin
|
||||
Result := Result or $08000000; // bit 27: T/R relay
|
||||
if not XvtrDisablePA then
|
||||
Result := Result or $08000000; // bit 27: T/R relay
|
||||
if IsOrion2 then
|
||||
Result := Result or $00040000; // bit 18: TxRx Status
|
||||
end;
|
||||
@@ -1155,9 +1190,14 @@ begin
|
||||
|
||||
// Биты 24-26: ANT 1/2/3. Выбор зависит от диапазона и режима TX/RX.
|
||||
// Дефолт ANT1 — безопасен при любом состоянии.
|
||||
// Если активен XVTR с RX antenna override (XvtrRxAnt 1..3) — он переопределяет
|
||||
// RX антенну. TX антенна для XVTR всё равно берётся из Alex.TxAnt[band] —
|
||||
// обычно та же что для основного HF-band за этой IF-частотой.
|
||||
BandIdx := FreqToBandIndex(RXFreqHz);
|
||||
if Transmitting then
|
||||
AntVal := Alex.TxAnt[BandIdx]
|
||||
else if (XvtrRxAnt >= 1) and (XvtrRxAnt <= 3) then
|
||||
AntVal := XvtrRxAnt
|
||||
else
|
||||
AntVal := Alex.RxAnt[BandIdx];
|
||||
if AntVal < 1 then AntVal := 1;
|
||||
@@ -1169,6 +1209,8 @@ begin
|
||||
|
||||
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
|
||||
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
|
||||
// Когда активен XVTR с XvtrUseRxDDCIn=True — переопределяем на XVTR DDC IN
|
||||
// вне зависимости от Alex.RxOnly[band].
|
||||
if Transmitting then
|
||||
begin
|
||||
if Alex.RxBypassOnTx then
|
||||
@@ -1177,6 +1219,11 @@ begin
|
||||
Result := Result or $00000200 // bit 9: Ext1
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
end
|
||||
else if XvtrUseRxDDCIn then
|
||||
begin
|
||||
Result := Result or $00000100 // bit 8: XVTR DDC In
|
||||
or $00000800; // bit 11: Bypass relay
|
||||
end
|
||||
else
|
||||
begin
|
||||
case Alex.RxOnly[BandIdx] of
|
||||
@@ -1196,6 +1243,19 @@ begin
|
||||
if FConnected then SendFullHP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
|
||||
ARxAnt: Byte);
|
||||
begin
|
||||
FXvtrEnable := AEnable;
|
||||
FXvtrUseRxDDCIn := AEnable and AUseRxDDCIn;
|
||||
FXvtrDisablePA := AEnable and ADisablePA;
|
||||
if AEnable and (ARxAnt >= 1) and (ARxAnt <= 3) then
|
||||
FXvtrRxAntOverride := ARxAnt
|
||||
else
|
||||
FXvtrRxAntOverride := 0;
|
||||
if FConnected then SendFullHP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
||||
ADCSource: Byte);
|
||||
var
|
||||
@@ -1338,13 +1398,21 @@ begin
|
||||
if FAlexEnabled then
|
||||
begin
|
||||
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
|
||||
FIsTransmitting, IsOrion2, FAlexConfig);
|
||||
FIsTransmitting, IsOrion2, FAlexConfig,
|
||||
FXvtrRxAntOverride, FXvtrUseRxDDCIn, FXvtrDisablePA);
|
||||
Buf[1432] := (Alex0 shr 24) and $FF;
|
||||
Buf[1433] := (Alex0 shr 16) and $FF;
|
||||
Buf[1434] := (Alex0 shr 8) and $FF;
|
||||
Buf[1435] := Alex0 and $FF;
|
||||
end;
|
||||
|
||||
// XVTR enable bit (byte 1400 bit 0)
|
||||
// openHPSDR Ethernet Protocol v4.3: byte 1400 — Transverter/audio enable.
|
||||
// bit 0 = XVTR enable, bit 1 = Audio Codec enable (на платах с поддержкой).
|
||||
// Audio codec бит оставляем 0 — мы IQ-mic не используем.
|
||||
if FXvtrEnable then
|
||||
Buf[1400] := Buf[1400] or $01;
|
||||
|
||||
// Step attenuators ADC0/ADC1 (bytes 1443/1442)
|
||||
// 0 dB = 0, max 31 dB
|
||||
Buf[1443] := FStepAtten; // ADC0 attenuation
|
||||
|
||||
+448
-55
@@ -160,6 +160,8 @@ type
|
||||
FAudioInDevName: string; // текущее имя входного устройства (TX mic)
|
||||
FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC)
|
||||
FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board)
|
||||
FXvtrSettings: TXvtrSettings; // transverter slots (per-device)
|
||||
FCurrentXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = active XVTR
|
||||
FWDSPReady: Boolean; // True когда WDSP открыт и работает
|
||||
|
||||
// ---- State ----
|
||||
@@ -317,6 +319,9 @@ type
|
||||
|
||||
PanelBands: TPanel;
|
||||
BtnBand: array[0..BAND_COUNT-1] of TFlatButton;
|
||||
// Динамические кнопки XVTR (создаются/удаляются по мере того,
|
||||
// как пользователь enabled/disabled слоты в Settings → Transverter).
|
||||
BtnXvtrBand: array[0..CFG_XVTR_COUNT-1] of TFlatButton;
|
||||
|
||||
PanelMode: TPanel;
|
||||
BtnMode: array[0..MODE_COUNT-1] of TFlatButton;
|
||||
@@ -497,6 +502,7 @@ type
|
||||
procedure WebOnAttn(Idx: Integer);
|
||||
procedure WebOnTun(On_: Boolean);
|
||||
procedure WebOnMic(Samples: PSingle; Count: Integer);
|
||||
procedure WebOnXvtrBand(Idx: Integer);
|
||||
// Synchronize-обёртки (выполняются в UI-потоке)
|
||||
procedure SyncWebFreq;
|
||||
procedure SyncWebMode;
|
||||
@@ -504,6 +510,7 @@ type
|
||||
procedure SyncWebAGC;
|
||||
procedure SyncWebAGCTop;
|
||||
procedure SyncWebBand;
|
||||
procedure SyncWebXvtrBand;
|
||||
procedure SyncWebSpan;
|
||||
procedure SyncWebVolume;
|
||||
procedure SyncWebWfAGC;
|
||||
@@ -562,6 +569,16 @@ type
|
||||
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
|
||||
TcpEnabled: Boolean; TcpPort: Integer);
|
||||
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
||||
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
|
||||
procedure PushXvtrToWeb;
|
||||
// XVTR helpers
|
||||
function FindXvtrIdxForFreq(FreqHz: Double): Integer;
|
||||
function XvtrTranslate(VisibleHz: Double): Double;
|
||||
procedure ApplyXvtrToNetwork;
|
||||
procedure RebuildXvtrButtons;
|
||||
procedure BtnXvtrBandClick(Sender: TObject);
|
||||
procedure ActivateXvtrBand(Idx: Integer);
|
||||
procedure DeactivateXvtr;
|
||||
// -------------------------------------------------------------------------
|
||||
procedure BtnWfAGCClick(Sender: TObject);
|
||||
procedure BtnWfNFClick(Sender: TObject);
|
||||
@@ -865,6 +882,7 @@ begin
|
||||
Result.WfAGCEnabled := FWfAGCEnabled;
|
||||
Result.WfNFEnabled := FWfNFEnabled;
|
||||
Result.LastBand := FCurrentBand;
|
||||
Result.LastXvtr := FCurrentXvtr;
|
||||
Result.SampleRate := FSampleRate;
|
||||
// Display settings
|
||||
if FWDSPReady then
|
||||
@@ -918,6 +936,18 @@ end;
|
||||
procedure TMainForm.SaveCurrentBand;
|
||||
begin
|
||||
if not FDevConnected then Exit;
|
||||
// В XVTR-режиме НЕ трогаем HF band-cache: FBandCache[FCurrentBand]
|
||||
// относится к HF-диапазону, на котором пользователь был ДО активации
|
||||
// трансвертера, и FVfoA сейчас на XVTR-частоте (144.x, 432.x и т.д.).
|
||||
// Запись в FBandCache[FCurrentBand] стерла бы корректное HF-состояние.
|
||||
// Вместо этого сохраняем LastFreq в XVTR-настройках.
|
||||
if FCurrentXvtr >= 0 then
|
||||
begin
|
||||
if (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
Exit;
|
||||
end;
|
||||
FBandCache[FCurrentBand] := MakeBandSettings;
|
||||
FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]);
|
||||
end;
|
||||
@@ -997,6 +1027,10 @@ begin
|
||||
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
|
||||
if FDevConnected then
|
||||
FSettings.SaveTX(FDevMAC, FTXSettings);
|
||||
// Запомнить LastFreq XVTR для следующего запуска
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
FSettings.Save;
|
||||
end;
|
||||
end;
|
||||
@@ -1107,7 +1141,9 @@ begin
|
||||
FWebServer.OnAttn := WebOnAttn;
|
||||
FWebServer.OnTun := WebOnTun;
|
||||
FWebServer.OnWebMic := WebOnMic;
|
||||
FWebServer.OnXvtrBand := WebOnXvtrBand;
|
||||
FWebServer.Start;
|
||||
PushXvtrToWeb;
|
||||
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
|
||||
FCATLastGlobal.CATTcpPort := 19090;
|
||||
for i := 0 to 3 do
|
||||
@@ -1175,9 +1211,11 @@ begin
|
||||
FNetwork.OnHPStatus := OnHPStatusCB;
|
||||
FNetwork.OnDDCIQ := OnDDCIQCB;
|
||||
FNetwork.OnMicPacket := OnMicPacketCB;
|
||||
// Дефолты TX и Alex (на случай если устройство ещё не выбрано).
|
||||
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
|
||||
TSettingsManager.DefaultTX(FTXSettings);
|
||||
TSettingsManager.DefaultAlex(FAlexSettings);
|
||||
TSettingsManager.DefaultXvtr(FXvtrSettings);
|
||||
FCurrentXvtr := -1;
|
||||
|
||||
BuildUI;
|
||||
StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
|
||||
@@ -1242,7 +1280,7 @@ begin
|
||||
FMeterTimer.Enabled := False;
|
||||
FSpectrumTimer.Enabled := False;
|
||||
if FNetwork.Running then
|
||||
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
FNetwork.Disconnect;
|
||||
FNetwork.Free;
|
||||
FAudioOut.Close;
|
||||
@@ -1280,8 +1318,8 @@ begin
|
||||
begin
|
||||
if FRunning then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, 0, False, True, True);
|
||||
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
FRunning := False;
|
||||
FTransmitting := False;
|
||||
if FWDSPReady then FDSPEngine.SetTXRun(False);
|
||||
@@ -1529,10 +1567,12 @@ begin
|
||||
|
||||
LayoutTopVfoBlock;
|
||||
|
||||
// Band selector
|
||||
// Band selector. Высота резервирует 2 дополнительных ряда под XVTR-кнопки
|
||||
// (создаются динамически в RebuildXvtrButtons). В каждом ряду до 6 кнопок,
|
||||
// итого до 12 XVTR-кнопок (наш CFG_XVTR_COUNT = 8 умещается с запасом).
|
||||
PanelBands := TPanel.Create(Self);
|
||||
PanelBands.Parent := PanelLeft;
|
||||
PanelBands.SetBounds(0, Y, LEFT_W, 74);
|
||||
PanelBands.SetBounds(0, Y, LEFT_W, 74 + 54);
|
||||
PanelBands.BevelOuter := bvNone;
|
||||
MakeLbl(PanelBands, 'BAND', 4, 2);
|
||||
W := (LEFT_W - 6) div 6;
|
||||
@@ -1545,8 +1585,11 @@ begin
|
||||
B.Tag := i;
|
||||
BtnBand[i] := B;
|
||||
end;
|
||||
// XVTR кнопки создаются позже (в RebuildXvtrButtons), их Tag = -(idx+1)
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
BtnXvtrBand[i] := nil;
|
||||
|
||||
Inc(Y, 76);
|
||||
Inc(Y, 76 + 54);
|
||||
|
||||
// Mode selector
|
||||
PanelMode := TPanel.Create(Self);
|
||||
@@ -2669,7 +2712,7 @@ begin
|
||||
BtnStartStop.Caption := 'START';
|
||||
StyleButton(BtnStartStop, False);
|
||||
FRunning := False;
|
||||
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
StatusBar1.Panels[4].Text := 'ТРАНСИВЕР НЕДОСТУПЕН';
|
||||
BtnMOX.Enabled := False;
|
||||
Exit;
|
||||
@@ -2680,7 +2723,7 @@ begin
|
||||
BtnStartStop.Caption := 'START';
|
||||
StyleButton(BtnStartStop, False);
|
||||
FRunning := False;
|
||||
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
StatusBar1.Panels[4].Text := 'ПОТЕРЯ СВЯЗИ С ТРАНСИВЕРОМ';
|
||||
BtnMOX.Enabled := False;
|
||||
Exit;
|
||||
@@ -3321,8 +3364,8 @@ begin
|
||||
// --- STOP ---
|
||||
if FRunning then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, 0, False, True, True);
|
||||
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
FRunning := False;
|
||||
FTransmitting := False;
|
||||
if FWDSPReady then FDSPEngine.SetTXRun(False);
|
||||
@@ -3337,6 +3380,10 @@ begin
|
||||
SaveCurrentBand;
|
||||
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
|
||||
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
||||
// XVTR LastFreq → актуальная видимая частота в момент остановки
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
end;
|
||||
FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate);
|
||||
FSettings.Save;
|
||||
@@ -3474,6 +3521,12 @@ begin
|
||||
// Alex-настройки per-device (антенны/маршрутизация)
|
||||
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
||||
FNetwork.SetAlexConfig(FAlexSettings);
|
||||
// XVTR-настройки per-device (трансвертеры)
|
||||
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
|
||||
FCurrentXvtr := -1;
|
||||
RebuildXvtrButtons;
|
||||
ApplyXvtrToNetwork;
|
||||
PushXvtrToWeb;
|
||||
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
|
||||
FTXSpecRange := FTXSettings.TXSpecRange;
|
||||
if FTXSettings.TXSpecGridStep > 0 then
|
||||
@@ -3566,6 +3619,14 @@ begin
|
||||
end;
|
||||
RestoreBand(FCurrentBand);
|
||||
|
||||
// Восстанавливаем XVTR-режим, если он был активен при выходе.
|
||||
// FCurrentBand уже загружен (HF-диапазон, на который пользователь зашёл
|
||||
// до XVTR), его FBandCache не пострадает — ActivateXvtrBand только
|
||||
// выставит FVfoA на XVTR-частоту и FCurrentXvtr.
|
||||
if (G_Settings.LastXvtr >= 0) and (G_Settings.LastXvtr < CFG_XVTR_COUNT)
|
||||
and FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
|
||||
ActivateXvtrBand(G_Settings.LastXvtr);
|
||||
|
||||
StatusBar1.Panels[0].Text := 'IP: ' + FNetwork.Device.IPAddress;
|
||||
StatusBar1.Panels[1].Text := 'Board: ' + BoardTypeName(FNetwork.Device.BoardType);
|
||||
|
||||
@@ -3609,8 +3670,8 @@ begin
|
||||
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
|
||||
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
|
||||
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, False, True, True);
|
||||
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, False, True, True);
|
||||
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
|
||||
FRunning := True;
|
||||
FRXStartTime := GetTickCount64;
|
||||
FRXLastPktTime := 0;
|
||||
@@ -3720,7 +3781,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
end;
|
||||
@@ -3732,7 +3793,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
end;
|
||||
@@ -3782,7 +3843,7 @@ begin
|
||||
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
@@ -3804,7 +3865,7 @@ begin
|
||||
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
@@ -3830,7 +3891,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
// Обновляем диапазон
|
||||
@@ -3914,6 +3975,15 @@ var
|
||||
Scrolled: Boolean;
|
||||
BandIdx: Integer;
|
||||
begin
|
||||
// Клэмп в пределах XVTR-band — не даём VFO уйти за FreqBegin/FreqEnd
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
|
||||
begin
|
||||
if NewFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then
|
||||
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin);
|
||||
if NewFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then
|
||||
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd);
|
||||
end;
|
||||
FVfoA := NewFreq;
|
||||
Scrolled := False;
|
||||
|
||||
@@ -3962,19 +4032,24 @@ begin
|
||||
// Обновляем VFO дисплей
|
||||
FreqDispA.Frequency := Round(FVfoA);
|
||||
|
||||
// Обновляем кнопку диапазона если частота перешла в другой диапазон
|
||||
BandIdx := FreqToBandIdx(FVfoA);
|
||||
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||
// Обновляем кнопку диапазона если частота перешла в другой диапазон.
|
||||
// В XVTR-режиме HF band-кнопки не подсвечиваем (мы клэмпим VFO внутри
|
||||
// XVTR-диапазона — FreqToBandIdx всегда вернёт -1 для VHF/UHF).
|
||||
if FCurrentXvtr < 0 then
|
||||
begin
|
||||
StyleButton(BtnBand[FCurrentBand], False);
|
||||
FCurrentBand := BandIdx;
|
||||
StyleButton(BtnBand[FCurrentBand], True);
|
||||
BandIdx := FreqToBandIdx(FVfoA);
|
||||
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||
begin
|
||||
StyleButton(BtnBand[FCurrentBand], False);
|
||||
FCurrentBand := BandIdx;
|
||||
StyleButton(BtnBand[FCurrentBand], True);
|
||||
end;
|
||||
end;
|
||||
|
||||
// DDC: передаём FCenterFreq (при CTUN OFF = FVfoA, при CTUN ON = фиксирован)
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
|
||||
@@ -4011,7 +4086,7 @@ begin
|
||||
FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
end
|
||||
@@ -4062,6 +4137,14 @@ end;
|
||||
procedure TMainForm.ApplyTUN(Active: Boolean);
|
||||
begin
|
||||
if Active = FTuning then Exit;
|
||||
// Safety: те же блокировки что в ApplyMOX. TUN — это TX через WDSP PostGen,
|
||||
// он тоже должен молчать на бэндах с DoNotTx или на RX-only XVTR.
|
||||
if Active and FDevConnected and (FCurrentXvtr < 0)
|
||||
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
|
||||
Exit;
|
||||
if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
|
||||
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
|
||||
Exit;
|
||||
if Active then
|
||||
begin
|
||||
// Включаем тон ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном
|
||||
@@ -4088,7 +4171,7 @@ begin
|
||||
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
@@ -4131,7 +4214,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
BandIdx := FreqToBandIdx(FVfoB);
|
||||
@@ -4171,7 +4254,7 @@ begin
|
||||
end;
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
FSpectrumDirty := True;
|
||||
@@ -4189,7 +4272,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
FSpectrumDirty := True;
|
||||
@@ -4328,19 +4411,23 @@ var
|
||||
begin
|
||||
NewBand := (Sender as TFlatButton).Tag;
|
||||
if (NewBand < 0) or (NewBand >= BAND_COUNT) then Exit;
|
||||
if NewBand = FCurrentBand then Exit; // уже на этом диапазоне
|
||||
// Если в XVTR-режиме — DeactivateXvtr вернёт HF-состояние (RestoreBand
|
||||
// на сохранённом FCurrentBand). Если NewBand совпадает с восстановленным
|
||||
// FCurrentBand, дальше делать ничего не нужно.
|
||||
if FCurrentXvtr >= 0 then
|
||||
begin
|
||||
DeactivateXvtr;
|
||||
if NewBand = FCurrentBand then Exit;
|
||||
end
|
||||
else if NewBand = FCurrentBand then
|
||||
Exit;
|
||||
|
||||
// Сохраняем настройки текущего диапазона перед сменой
|
||||
if FDevConnected then
|
||||
begin
|
||||
SaveCurrentBand;
|
||||
FSettings.Save;
|
||||
end;
|
||||
|
||||
// Переключаем диапазон
|
||||
FCurrentBand := NewBand;
|
||||
|
||||
// Восстанавливаем настройки нового диапазона
|
||||
RestoreBand(FCurrentBand);
|
||||
end;
|
||||
|
||||
@@ -4421,7 +4508,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
|
||||
@@ -4453,7 +4540,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
FSpecView.InvalidateRulerCache;
|
||||
@@ -4490,9 +4577,13 @@ var
|
||||
WasTransmitting: Boolean;
|
||||
begin
|
||||
// Safety: block TX on bands marked DoNotTx in Alex config
|
||||
if Active and FDevConnected
|
||||
if Active and FDevConnected and (FCurrentXvtr < 0)
|
||||
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
|
||||
Exit;
|
||||
// Safety: block TX on RX-only transverter
|
||||
if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
|
||||
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
|
||||
Exit;
|
||||
WasTransmitting := FTransmitting;
|
||||
FTransmitting := Active;
|
||||
FDUCPendingCount := 0;
|
||||
@@ -4517,7 +4608,7 @@ begin
|
||||
if FTransmitting and FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.ClearDUCIQQueue;
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
True, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
@@ -4563,7 +4654,7 @@ begin
|
||||
end;
|
||||
if (not FTransmitting) and FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
FTransmitting, True, True);
|
||||
FNetwork.ClearDUCIQQueue;
|
||||
FNetwork.SendFullHP;
|
||||
@@ -4828,7 +4919,7 @@ begin
|
||||
FCenterFreq := FVfoB;
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FRunning then
|
||||
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
|
||||
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
|
||||
BandIdx := FreqToBandIdx(FVfoB);
|
||||
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||
begin
|
||||
@@ -4967,12 +5058,40 @@ end;
|
||||
procedure TMainForm.SyncWebBand;
|
||||
begin
|
||||
if (FWebSyncInt < 0) or (FWebSyncInt >= BAND_COUNT) then Exit;
|
||||
if FWebSyncInt = FCurrentBand then Exit;
|
||||
// Выйти из XVTR при переходе на HF из web. DeactivateXvtr вернёт HF-состояние.
|
||||
if FCurrentXvtr >= 0 then
|
||||
begin
|
||||
DeactivateXvtr;
|
||||
if FWebSyncInt = FCurrentBand then Exit;
|
||||
end
|
||||
else if FWebSyncInt = FCurrentBand then
|
||||
Exit;
|
||||
if FDevConnected then begin SaveCurrentBand; FSettings.Save; end;
|
||||
FCurrentBand := FWebSyncInt;
|
||||
RestoreBand(FCurrentBand);
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnXvtrBand(Idx: Integer);
|
||||
begin
|
||||
FWebSyncInt := Idx;
|
||||
FWebSyncM := SyncWebXvtrBand;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebXvtrBand;
|
||||
begin
|
||||
if FWebSyncInt < 0 then
|
||||
begin
|
||||
DeactivateXvtr;
|
||||
Exit;
|
||||
end;
|
||||
if (FWebSyncInt >= CFG_XVTR_COUNT) then Exit;
|
||||
if not FXvtrSettings.Entries[FWebSyncInt].Enabled then Exit;
|
||||
if FWebSyncInt = FCurrentXvtr then Exit;
|
||||
if FDevConnected then begin SaveCurrentBand; FSettings.Save; end;
|
||||
ActivateXvtrBand(FWebSyncInt);
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnSpan(Hz: Integer);
|
||||
begin
|
||||
FWebSyncInt := Hz;
|
||||
@@ -5182,7 +5301,7 @@ begin
|
||||
if not FCTun then
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FRunning then
|
||||
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
|
||||
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
|
||||
BandIdx := FreqToBandIdx(FVfoB);
|
||||
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||
begin
|
||||
@@ -5233,7 +5352,7 @@ begin
|
||||
else FDSPEngine.SetShift(FVfoB - FCenterFreq);
|
||||
end;
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FSpecView.InvalidateRulerCache;
|
||||
FSpectrumDirty := True;
|
||||
end;
|
||||
@@ -5322,26 +5441,53 @@ begin
|
||||
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMainForm.CalcDriveByte: Byte;
|
||||
var
|
||||
Cal: Double;
|
||||
Pos: Integer;
|
||||
Cal: Double;
|
||||
Pos: Integer;
|
||||
XvtrActive: Boolean;
|
||||
begin
|
||||
if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) and (FPABandCal[FCurrentBand] > 0.0) then
|
||||
Cal := FPABandCal[FCurrentBand]
|
||||
else
|
||||
Cal := 100.0;
|
||||
// В режиме TUN используем отдельный per-device уровень (см. TTXSettings.TUNLevel),
|
||||
// чтобы tune не зависел от Drive-слайдера. Per-band калибровка — общая для обоих.
|
||||
|
||||
XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
|
||||
|
||||
// Pos — итоговый процент драйва 0..100 ДО per-band калибровки.
|
||||
//
|
||||
// Логика (как в Thetis):
|
||||
// • Регулярный TX:
|
||||
// HF → Pos = TrkDrive.Position
|
||||
// XVTR → Pos = TrkDrive.Position × (XVTR.TXPower / 100)
|
||||
// (TXPower работает как верхний предел драйва на трансверторе)
|
||||
// • TUN (кнопка):
|
||||
// HF → Pos = TUNLevel (TTXSettings.TUNLevel)
|
||||
// XVTR, UseXVTRTunePower=False → Pos = TUNLevel (поведение как HF)
|
||||
// XVTR, UseXVTRTunePower=True → Pos = XVTR.TXPower
|
||||
// (галка ЗАМЕНЯЕТ TUNLevel на XVTR power — Thetis-семантика)
|
||||
if FTuning then
|
||||
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100)
|
||||
begin
|
||||
if XvtrActive and FXvtrSettings.UseXVTRTunePower then
|
||||
Pos := EnsureRange(FXvtrSettings.Entries[FCurrentXvtr].TXPower, 0, 100)
|
||||
else
|
||||
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100);
|
||||
end
|
||||
else
|
||||
Pos := TrkDrive.Position;
|
||||
begin
|
||||
if XvtrActive then
|
||||
Pos := Round(TrkDrive.Position
|
||||
* FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0)
|
||||
else
|
||||
Pos := TrkDrive.Position;
|
||||
end;
|
||||
|
||||
Result := Round(Pos / 100.0 * Cal / 100.0 * 255.0);
|
||||
end;
|
||||
|
||||
@@ -5392,7 +5538,7 @@ begin
|
||||
end;
|
||||
FDriveLevel := CalcDriveByte;
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
||||
@@ -5434,7 +5580,7 @@ begin
|
||||
FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
|
||||
FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
@@ -5460,6 +5606,251 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
// XVTR (Transverter) integration
|
||||
// ===========================================================================
|
||||
|
||||
// Ищет XVTR слот, в чьё [FreqBegin..FreqEnd] попадает данная видимая частота.
|
||||
// Возвращает индекс 0..CFG_XVTR_COUNT-1 или -1.
|
||||
function TMainForm.FindXvtrIdxForFreq(FreqHz: Double): Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
with FXvtrSettings.Entries[i] do
|
||||
if Enabled and (FreqHz >= FreqBegin) and (FreqHz <= FreqEnd) then
|
||||
Exit(i);
|
||||
end;
|
||||
|
||||
// Транслирует видимую частоту в IF (то, что слышит/передаёт трансивер).
|
||||
// Если активный XVTR не задан — возвращает входную частоту без изменений.
|
||||
function TMainForm.XvtrTranslate(VisibleHz: Double): Double;
|
||||
begin
|
||||
Result := VisibleHz;
|
||||
if (FCurrentXvtr < 0) or (FCurrentXvtr >= CFG_XVTR_COUNT) then Exit;
|
||||
if not FXvtrSettings.Entries[FCurrentXvtr].Enabled then Exit;
|
||||
// f_IF = f_visible - LOOffset + LOError
|
||||
Result := VisibleHz
|
||||
- FXvtrSettings.Entries[FCurrentXvtr].LOOffset
|
||||
+ FXvtrSettings.Entries[FCurrentXvtr].LOError;
|
||||
end;
|
||||
|
||||
// Передаёт XVTR-режим в сетевой слой. Вызывается при смене XVTR или его
|
||||
// настроек. Управляет XVTR Enable bit, DDC IN routing, T/R relay suppression.
|
||||
procedure TMainForm.ApplyXvtrToNetwork;
|
||||
var
|
||||
En: Boolean;
|
||||
UseDDCIn: Boolean;
|
||||
DisablePA: Boolean;
|
||||
RxAnt: Byte;
|
||||
begin
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
|
||||
begin
|
||||
En := True;
|
||||
// XVTRRF=True означает что трансвертер использует HF тракт — не нужно
|
||||
// переключаться на отдельный XVTR DDC IN на Alex. Default False →
|
||||
// подключаем XVTR-IF к выделенному пину XVTR DDC IN.
|
||||
UseDDCIn := not FXvtrSettings.Entries[FCurrentXvtr].XVTRRF;
|
||||
DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA;
|
||||
RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna;
|
||||
end
|
||||
else
|
||||
begin
|
||||
En := False; UseDDCIn := False; DisablePA := False; RxAnt := 0;
|
||||
end;
|
||||
if FNetwork.Connected then
|
||||
FNetwork.SetXvtrMode(En, UseDDCIn, DisablePA, RxAnt);
|
||||
end;
|
||||
|
||||
// Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые,
|
||||
// создаёт новые для enabled слотов в 3-м и 4-м ряду PanelBands.
|
||||
procedure TMainForm.RebuildXvtrButtons;
|
||||
const
|
||||
BTN_H_LOC = 24;
|
||||
var
|
||||
i, Pos: Integer;
|
||||
W: Integer;
|
||||
B: TFlatButton;
|
||||
Cap: string;
|
||||
begin
|
||||
// Уничтожаем все старые
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if BtnXvtrBand[i] <> nil then
|
||||
begin
|
||||
BtnXvtrBand[i].Free;
|
||||
BtnXvtrBand[i] := nil;
|
||||
end;
|
||||
if PanelBands = nil then Exit;
|
||||
W := (PanelBands.Width - 6) div 6;
|
||||
Pos := 0; // позиция среди enabled слотов
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if FXvtrSettings.Entries[i].Enabled then
|
||||
begin
|
||||
if Pos >= 12 then Break; // 2 ряда по 6 кнопок
|
||||
Cap := FXvtrSettings.Entries[i].ButtonText;
|
||||
if Cap = '' then Cap := 'X' + IntToStr(i + 1);
|
||||
B := TFlatButton.Create(Self);
|
||||
B.Parent := PanelBands;
|
||||
B.Caption := Cap;
|
||||
B.Left := 2 + (Pos mod 6) * W;
|
||||
B.Top := 16 + (2 + (Pos div 6)) * 27;
|
||||
B.Width := W - 2;
|
||||
B.Height := BTN_H_LOC;
|
||||
B.OnClick := BtnXvtrBandClick;
|
||||
B.Tag := -(i + 1); // отрицательное значение = XVTR индекс
|
||||
BtnXvtrBand[i] := B;
|
||||
StyleButton(B, i = FCurrentXvtr);
|
||||
Inc(Pos);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Обработчик клика по XVTR-кнопке. Tag = -(idx+1).
|
||||
procedure TMainForm.BtnXvtrBandClick(Sender: TObject);
|
||||
var
|
||||
TagVal, Idx: Integer;
|
||||
begin
|
||||
TagVal := (Sender as TFlatButton).Tag;
|
||||
Idx := -TagVal - 1;
|
||||
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
|
||||
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
|
||||
if FCurrentXvtr = Idx then Exit; // уже на этом XVTR
|
||||
// Сохраняем текущий band перед сменой (как BtnBandClick)
|
||||
if FDevConnected then
|
||||
begin
|
||||
SaveCurrentBand;
|
||||
FSettings.Save;
|
||||
end;
|
||||
ActivateXvtrBand(Idx);
|
||||
end;
|
||||
|
||||
// Активирует XVTR-band: устанавливает видимую частоту по LastFreq или
|
||||
// середине [FreqBegin..FreqEnd], применяет network XVTR-режим.
|
||||
procedure TMainForm.ActivateXvtrBand(Idx: Integer);
|
||||
var
|
||||
Vis: Double;
|
||||
i: Integer;
|
||||
begin
|
||||
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
|
||||
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
|
||||
FCurrentXvtr := Idx;
|
||||
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
|
||||
Vis := FXvtrSettings.Entries[Idx].LastFreq;
|
||||
if (Vis < FXvtrSettings.Entries[Idx].FreqBegin) or
|
||||
(Vis > FXvtrSettings.Entries[Idx].FreqEnd) then
|
||||
Vis := (FXvtrSettings.Entries[Idx].FreqBegin +
|
||||
FXvtrSettings.Entries[Idx].FreqEnd) / 2.0;
|
||||
// Отключаем кнопки HF band
|
||||
for i := 0 to BAND_COUNT - 1 do
|
||||
StyleButton(BtnBand[i], False);
|
||||
// Подсвечиваем активную XVTR-кнопку
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if BtnXvtrBand[i] <> nil then
|
||||
StyleButton(BtnXvtrBand[i], i = Idx);
|
||||
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
|
||||
ApplyXvtrToNetwork;
|
||||
// VFO на видимую частоту; FCenterFreq тоже visible (как и для HF — DDC переход
|
||||
// в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate).
|
||||
FVfoA := Vis;
|
||||
FCenterFreq := Vis;
|
||||
FreqDispA.Frequency := Round(FVfoA);
|
||||
// Сбросить CTUN — не имеет смысла в начальном состоянии XVTR-band
|
||||
if FCTun then
|
||||
begin
|
||||
FCTun := False;
|
||||
StyleButton(BtnCTun, False);
|
||||
end;
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
// Применяем драйв (если есть кастомный TX power для этого XVTR)
|
||||
// Сделано на стороне ActiveTXFreqHz / TX path — здесь только обновляем дисплей.
|
||||
// Отправляем в сеть с трансляцией
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
|
||||
FDriveLevel, FTransmitting, True, True);
|
||||
FNetwork.SendFullHP;
|
||||
end;
|
||||
SyncSpecViewFreq;
|
||||
FSpecView.DrawSpectrum;
|
||||
PbSpectrum.Invalidate;
|
||||
FSpecView.DrawWaterfall;
|
||||
PbWaterfall.Invalidate;
|
||||
if PbRuler <> nil then PbRuler.Invalidate;
|
||||
PushXvtrToWeb;
|
||||
end;
|
||||
|
||||
// Деактивирует XVTR — возвращает к HF-режиму.
|
||||
// FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand,
|
||||
// ApplyVfoA, SaveCurrentBand) — он указывает на HF-диапазон, с которого
|
||||
// пользователь зашёл в трансвертер. RestoreBand вернёт VFO/Mode/Filter/AGC
|
||||
// /CTUN/Span этого диапазона и обновит подсветку HF band-кнопки.
|
||||
procedure TMainForm.DeactivateXvtr;
|
||||
var i: Integer;
|
||||
begin
|
||||
if FCurrentXvtr < 0 then Exit;
|
||||
// Запоминаем LastFreq для bandstack
|
||||
if FCurrentXvtr < CFG_XVTR_COUNT then
|
||||
begin
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
if FDevConnected then
|
||||
begin
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
FSettings.Save;
|
||||
end;
|
||||
end;
|
||||
FCurrentXvtr := -1;
|
||||
// Сбрасываем подсветку XVTR-кнопок
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if BtnXvtrBand[i] <> nil then
|
||||
StyleButton(BtnXvtrBand[i], False);
|
||||
ApplyXvtrToNetwork;
|
||||
// Восстанавливаем HF band: VFO/Mode/Filter/AGC/CTUN/Span из кэша.
|
||||
// RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние.
|
||||
if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
|
||||
RestoreBand(FCurrentBand);
|
||||
PushXvtrToWeb;
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnXvtrSettingsChange(const X: TXvtrSettings);
|
||||
begin
|
||||
FXvtrSettings := X;
|
||||
RebuildXvtrButtons;
|
||||
// Если активный XVTR стал disabled — выйти из XVTR режима
|
||||
if (FCurrentXvtr >= 0) and
|
||||
((FCurrentXvtr >= CFG_XVTR_COUNT) or
|
||||
(not FXvtrSettings.Entries[FCurrentXvtr].Enabled)) then
|
||||
DeactivateXvtr;
|
||||
// Перепрошить XVTR в сеть (если параметры текущего изменились)
|
||||
ApplyXvtrToNetwork;
|
||||
PushXvtrToWeb;
|
||||
if FDevConnected then
|
||||
begin
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
FSettings.Save;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.PushXvtrToWeb;
|
||||
var
|
||||
Arr: TWebXvtrArray;
|
||||
i, Cnt: Integer;
|
||||
begin
|
||||
if FWebServer = nil then Exit;
|
||||
Cnt := 0;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if FXvtrSettings.Entries[i].Enabled then Inc(Cnt);
|
||||
SetLength(Arr, Cnt);
|
||||
Cnt := 0;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
if FXvtrSettings.Entries[i].Enabled then
|
||||
begin
|
||||
Arr[Cnt].Idx := i;
|
||||
Arr[Cnt].Name := FXvtrSettings.Entries[i].ButtonText;
|
||||
Inc(Cnt);
|
||||
end;
|
||||
FWebServer.SetXvtrBands(Arr, FCurrentXvtr);
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrkVolumeChange(Sender: TObject);
|
||||
begin
|
||||
FVolume := TrkVolume.Position;
|
||||
@@ -5745,11 +6136,13 @@ begin
|
||||
SF.OnThemeChange := SetLightTheme;
|
||||
SF.OnTXChange := OnTXSettingsChange;
|
||||
SF.OnAlexChange := OnAlexSettingsChange;
|
||||
SF.OnXvtrChange := OnXvtrSettingsChange;
|
||||
end;
|
||||
SF := TSettingsForm(FSettingsForm);
|
||||
SF.LoadTXSettings(FTXSettings);
|
||||
SF.LoadAlexSettings(FAlexSettings,
|
||||
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
|
||||
SF.LoadXvtrSettings(FXvtrSettings);
|
||||
SF.LoadPASettings(FPAMaxPower, FPABandCal);
|
||||
SF.LoadCATSettings(
|
||||
FCATLastGlobal.CATSerialEnabled,
|
||||
@@ -5996,7 +6389,7 @@ begin
|
||||
FCenterFreq := FVfoB;
|
||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||
if FRunning then
|
||||
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
|
||||
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
|
||||
BandIdx := FreqToBandIdx(FVfoB);
|
||||
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||
begin
|
||||
|
||||
+143
@@ -11,6 +11,7 @@ uses SysUtils, Classes, Math, fpJSON, jsonparser, jsonscanner;
|
||||
const
|
||||
SETTINGS_FILE = 'hpsdr_settings.json';
|
||||
CFG_BAND_COUNT = 11;
|
||||
CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов)
|
||||
CFG_BAND_DEFAULT_FREQ: array[0..CFG_BAND_COUNT-1] of Double = (
|
||||
1900000, 3750000, 5357000, 7100000, 10125000,
|
||||
14200000, 18120000, 21200000, 24940000, 28500000, 50150000);
|
||||
@@ -103,6 +104,33 @@ type
|
||||
TXSpecGridStep: Double; // grid step in dB, e.g. 10
|
||||
end;
|
||||
|
||||
// Transverter (XVTR) entry — один трансвертер.
|
||||
// Видимая частота VFO в [FreqBegin..FreqEnd] транслируется в IF-частоту,
|
||||
// которую слышит трансивер: f_IF = f_visible - LOOffset + LOError.
|
||||
// Примеры: 2m XVTR с LO=116 MHz → visible 144.200 ⇒ IF 28.200 MHz.
|
||||
TXvtrEntry = record
|
||||
Enabled: Boolean;
|
||||
ButtonText: string[15]; // подпись на band-кнопке (например, '2m', '70cm')
|
||||
FreqBegin: Double; // Hz
|
||||
FreqEnd: Double; // Hz
|
||||
LOOffset: Double; // Hz, отнимается от видимой частоты для получения IF
|
||||
LOError: Double; // Hz, корректировка LO (положительная — IF растёт)
|
||||
RXGain: Double; // dB, компенсация усиления RX тракта
|
||||
RXOnly: Boolean; // True — запрещает TX на этом трансверторе
|
||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||
XVTRRF: Boolean; // True — трансвертер использует основной HF RF путь
|
||||
// (а не отдельный XVTR DDC IN на Alex)
|
||||
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
|
||||
RXAntenna: Byte; // 0=по-умолчанию(основной band), 1=ANT1, 2=ANT2, 3=ANT3
|
||||
LastFreq: Double; // Hz — последняя tuned visible частота (BandStack)
|
||||
end;
|
||||
|
||||
TXvtrSettings = record
|
||||
Entries: array[0..CFG_XVTR_COUNT-1] of TXvtrEntry;
|
||||
// Use XVTR TUN power — кнопка TUN использует XVTR-specific power.
|
||||
UseXVTRTunePower: Boolean;
|
||||
end;
|
||||
|
||||
// Alex (Antenna + Filter board) settings per-device.
|
||||
// Хранятся в JSON-секции "alex" под MAC.
|
||||
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
|
||||
@@ -133,6 +161,7 @@ type
|
||||
WfAGCEnabled: Boolean;
|
||||
WfNFEnabled: Boolean;
|
||||
LastBand: Integer;
|
||||
LastXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = активный трансвертер при выходе
|
||||
SampleRate: Integer; // глобальный — один для всех диапазонов
|
||||
WindowLeft: Integer;
|
||||
WindowTop: Integer;
|
||||
@@ -231,6 +260,10 @@ type
|
||||
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);
|
||||
// XVTR (Transverter) settings per-device (JSON-секция "xvtr" под MAC).
|
||||
class procedure DefaultXvtr(out X: TXvtrSettings);
|
||||
function LoadXvtr(const MAC: array of Byte; out X: TXvtrSettings): Boolean;
|
||||
procedure SaveXvtr(const MAC: array of Byte; const X: TXvtrSettings);
|
||||
// Тема — глобальная настройка, не привязана к устройству
|
||||
procedure SaveTheme(ALightTheme: Boolean);
|
||||
function LoadTheme: Boolean;
|
||||
@@ -285,6 +318,7 @@ begin
|
||||
G.Volume := 70; G.DriveLevel := 50; G.ActiveVfo := 0;
|
||||
G.NRMode := 0; G.NBMode := 0; G.SNBEnabled := False; G.ANFEnabled := False;
|
||||
G.AGCSlope := 0; G.AGCHangThreshold := 100; G.LastBand := 5;
|
||||
G.LastXvtr := -1;
|
||||
G.WfAGCEnabled := True;
|
||||
G.WfNFEnabled := False;
|
||||
G.SampleRate := 192000;
|
||||
@@ -470,6 +504,7 @@ begin
|
||||
G.WfAGCEnabled := JB(GObj,'wf_agc_enabled',True);
|
||||
G.WfNFEnabled := JB(GObj,'wf_nf_enabled',False);
|
||||
G.LastBand := JI(GObj,'last_band',5);
|
||||
G.LastXvtr := JI(GObj,'last_xvtr',-1);
|
||||
G.SampleRate := JI(GObj,'sample_rate',192000);
|
||||
// Display settings
|
||||
G.FFTSize := JI(GObj,'fft_size',131072);
|
||||
@@ -574,6 +609,7 @@ begin
|
||||
JW(O,'wf_agc_enabled',G.WfAGCEnabled);
|
||||
JW(O,'wf_nf_enabled',G.WfNFEnabled);
|
||||
JW(O,'last_band',G.LastBand);
|
||||
JW(O,'last_xvtr',G.LastXvtr);
|
||||
JW(O,'sample_rate',G.SampleRate);
|
||||
// Display settings
|
||||
JW(O,'fft_size',G.FFTSize);
|
||||
@@ -994,6 +1030,113 @@ begin
|
||||
JW(O, 'enable_xvtr_hf', A.EnableXvtrHf);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// XVTR settings (per-device, JSON-секция "xvtr" под MAC)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class procedure TSettingsManager.DefaultXvtr(out X: TXvtrSettings);
|
||||
var i: Integer;
|
||||
begin
|
||||
FillChar(X, SizeOf(X), 0);
|
||||
X.UseXVTRTunePower := False;
|
||||
// Все слоты по умолчанию disabled. Первые два предзаполнены типичными
|
||||
// значениями (2m с LO=116MHz, 70cm с LO=404MHz) — чтобы пользователь
|
||||
// мог быстро включить.
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
X.Entries[i].Enabled := False;
|
||||
X.Entries[i].ButtonText := '';
|
||||
X.Entries[i].FreqBegin := 0.0;
|
||||
X.Entries[i].FreqEnd := 0.0;
|
||||
X.Entries[i].LOOffset := 0.0;
|
||||
X.Entries[i].LOError := 0.0;
|
||||
X.Entries[i].RXGain := 0.0;
|
||||
X.Entries[i].RXOnly := False;
|
||||
X.Entries[i].TXPower := 100;
|
||||
X.Entries[i].XVTRRF := False;
|
||||
X.Entries[i].DisablePA := False;
|
||||
X.Entries[i].RXAntenna := 0;
|
||||
X.Entries[i].LastFreq := 0.0;
|
||||
end;
|
||||
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
||||
X.Entries[0].ButtonText := '2m';
|
||||
X.Entries[0].FreqBegin := 144000000;
|
||||
X.Entries[0].FreqEnd := 148000000;
|
||||
X.Entries[0].LOOffset := 116000000;
|
||||
X.Entries[0].LastFreq := 144300000;
|
||||
X.Entries[1].ButtonText := '70cm';
|
||||
X.Entries[1].FreqBegin := 430000000;
|
||||
X.Entries[1].FreqEnd := 440000000;
|
||||
X.Entries[1].LOOffset := 404000000;
|
||||
X.Entries[1].LastFreq := 432200000;
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadXvtr(const MAC: array of Byte;
|
||||
out X: TXvtrSettings): Boolean;
|
||||
var
|
||||
MacStr: string;
|
||||
DevObj, Root, EObj: TJSONObject;
|
||||
i: Integer;
|
||||
begin
|
||||
DefaultXvtr(X);
|
||||
MacStr := MacToStr(MAC);
|
||||
Result := FRoot.Find(MacStr) <> nil;
|
||||
if not Result then Exit;
|
||||
DevObj := GetDevObj(MacStr);
|
||||
if DevObj.Find('xvtr') = nil then Exit;
|
||||
Root := EnsureObj(DevObj, 'xvtr');
|
||||
X.UseXVTRTunePower := JB(Root, 'use_tun_power', False);
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
if Root.Find('e' + IntToStr(i)) = nil then Continue;
|
||||
EObj := EnsureObj(Root, 'e' + IntToStr(i));
|
||||
X.Entries[i].Enabled := JB(EObj, 'en', X.Entries[i].Enabled);
|
||||
X.Entries[i].ButtonText := JS(EObj, 'name', X.Entries[i].ButtonText);
|
||||
X.Entries[i].FreqBegin := JD(EObj, 'fbeg', X.Entries[i].FreqBegin);
|
||||
X.Entries[i].FreqEnd := JD(EObj, 'fend', X.Entries[i].FreqEnd);
|
||||
X.Entries[i].LOOffset := JD(EObj, 'lo_off', X.Entries[i].LOOffset);
|
||||
X.Entries[i].LOError := JD(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
X.Entries[i].RXGain := JD(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
X.Entries[i].RXOnly := JB(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
X.Entries[i].TXPower := EnsureRange(JI(EObj, 'tx_pwr', X.Entries[i].TXPower), 0, 100);
|
||||
X.Entries[i].XVTRRF := JB(EObj, 'xvtr_rf', X.Entries[i].XVTRRF);
|
||||
X.Entries[i].DisablePA := JB(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||
X.Entries[i].RXAntenna := EnsureRange(JI(EObj, 'rx_ant', X.Entries[i].RXAntenna), 0, 3);
|
||||
X.Entries[i].LastFreq := JD(EObj, 'last_freq', X.Entries[i].LastFreq);
|
||||
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd]
|
||||
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
||||
X.Entries[i].LastFreq := (X.Entries[i].FreqBegin + X.Entries[i].FreqEnd) / 2.0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveXvtr(const MAC: array of Byte;
|
||||
const X: TXvtrSettings);
|
||||
var
|
||||
Root, EObj: TJSONObject;
|
||||
i: Integer;
|
||||
begin
|
||||
Root := EnsureObj(GetDevObj(MacToStr(MAC)), 'xvtr');
|
||||
JW(Root, 'use_tun_power', X.UseXVTRTunePower);
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
EObj := EnsureObj(Root, 'e' + IntToStr(i));
|
||||
JW(EObj, 'en', X.Entries[i].Enabled);
|
||||
JWS(EObj, 'name', X.Entries[i].ButtonText);
|
||||
JW(EObj, 'fbeg', X.Entries[i].FreqBegin);
|
||||
JW(EObj, 'fend', X.Entries[i].FreqEnd);
|
||||
JW(EObj, 'lo_off', X.Entries[i].LOOffset);
|
||||
JW(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
JW(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
JW(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
||||
JW(EObj, 'xvtr_rf', X.Entries[i].XVTRRF);
|
||||
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||
JW(EObj, 'rx_ant', Integer(X.Entries[i].RXAntenna));
|
||||
JW(EObj, 'last_freq', X.Entries[i].LastFreq);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveTheme(ALightTheme: Boolean);
|
||||
var O: TJSONObject;
|
||||
begin
|
||||
|
||||
+343
-3
@@ -55,6 +55,7 @@ type
|
||||
const SerParity: array of Integer;
|
||||
TcpEnabled: Boolean; TcpPort: Integer) of object;
|
||||
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
||||
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
||||
|
||||
{ TSettingsForm }
|
||||
TSettingsForm = class(TForm)
|
||||
@@ -200,6 +201,25 @@ type
|
||||
// Callback
|
||||
FOnAlexChange: TOnAlexChange;
|
||||
|
||||
// ---- XVTR (Transverter) tab controls ----
|
||||
FPageXvtr: TScrollBox;
|
||||
FNavXvtr: TFlatButton;
|
||||
FXvtrEn: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
|
||||
FXvtrName: array[0..CFG_XVTR_COUNT-1] of TEdit;
|
||||
FXvtrBegin: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
|
||||
FXvtrEnd: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
|
||||
FXvtrLOOff: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
|
||||
FXvtrLOErr: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // Hz (целые)
|
||||
FXvtrRXGain: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // dB
|
||||
FXvtrRXOnly: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
|
||||
FXvtrPower: array[0..CFG_XVTR_COUNT-1] of TSpinEdit; // 0..100
|
||||
FXvtrRF: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
|
||||
FXvtrDisPA: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
|
||||
FXvtrRXAnt: array[0..CFG_XVTR_COUNT-1] of TComboBox; // 0=def..3=ANT3
|
||||
FChkXvtrTunPwr: TCheckBox;
|
||||
FXvtrConfig: TXvtrSettings;
|
||||
FOnXvtrChange: TOnXvtrChange;
|
||||
|
||||
// ---- CAT tab controls ----
|
||||
FCATSerialEn: array[0..3] of TCheckBox;
|
||||
FCATSerialPort: array[0..3] of TEdit;
|
||||
@@ -240,6 +260,11 @@ type
|
||||
procedure FireAlexChange;
|
||||
procedure OnAlexAnyChange(Sender: TObject);
|
||||
procedure OnAlexRxOnlyChange(Sender: TObject);
|
||||
procedure BuildXvtrTab;
|
||||
procedure CollectXvtrFromUI;
|
||||
procedure FireXvtrChange;
|
||||
procedure OnXvtrAnyChange(Sender: TObject);
|
||||
procedure UpdateXvtrRowEnabled(Idx: Integer);
|
||||
procedure CollectTXFromUI;
|
||||
procedure FireTXChange;
|
||||
procedure OnTXAnyChange(Sender: TObject);
|
||||
@@ -318,9 +343,11 @@ type
|
||||
TcpEnabled: Boolean; TcpPort: Integer);
|
||||
procedure LoadTXSettings(const T: TTXSettings);
|
||||
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
|
||||
procedure LoadXvtrSettings(const X: TXvtrSettings);
|
||||
|
||||
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
|
||||
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
||||
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
|
||||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||||
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
|
||||
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
|
||||
@@ -388,8 +415,8 @@ constructor TSettingsForm.Create(AOwner: TComponent);
|
||||
begin
|
||||
inherited CreateNew(AOwner);
|
||||
Caption := 'Settings';
|
||||
Width := 900;
|
||||
Height := 660;
|
||||
Width := 1180;
|
||||
Height := 720;
|
||||
Position := poScreenCenter;
|
||||
BorderStyle := bsSizeable;
|
||||
Color := CLR_BG;
|
||||
@@ -540,6 +567,7 @@ begin
|
||||
FNavAdvanced := MakeNavButton('Advanced', 290);
|
||||
FNavCAT := MakeNavButton('CAT', 326);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 362);
|
||||
FNavXvtr := MakeNavButton('Transverter', 398);
|
||||
|
||||
FContentPanel := TPanel.Create(Self);
|
||||
FContentPanel.Parent := Self;
|
||||
@@ -556,8 +584,10 @@ begin
|
||||
FPageAdvanced := MakeScrollPage;
|
||||
FPageCAT := MakeScrollPage;
|
||||
FPageAlex := MakeScrollPage;
|
||||
FPageXvtr := MakeScrollPage;
|
||||
FAlexBoardType := 0;
|
||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||
TSettingsManager.DefaultXvtr(FXvtrConfig);
|
||||
|
||||
BuildAudioTab;
|
||||
BuildDisplayTab;
|
||||
@@ -566,6 +596,7 @@ begin
|
||||
BuildPATab;
|
||||
BuildCATTab;
|
||||
BuildAlexTab;
|
||||
BuildXvtrTab;
|
||||
|
||||
FBtnClose := TFlatButton.Create(Self);
|
||||
FBtnClose.Parent := Self;
|
||||
@@ -595,7 +626,8 @@ begin
|
||||
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 = FNavAlex then SelectPage(FPageAlex, FNavAlex);
|
||||
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
|
||||
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
|
||||
@@ -609,6 +641,7 @@ begin
|
||||
FPageAdvanced.Visible := APage = FPageAdvanced;
|
||||
FPageCAT.Visible := APage = FPageCAT;
|
||||
FPageAlex.Visible := APage = FPageAlex;
|
||||
FPageXvtr.Visible := APage = FPageXvtr;
|
||||
|
||||
FNavAudio.Active := ANav = FNavAudio;
|
||||
FNavDisplay.Active := ANav = FNavDisplay;
|
||||
@@ -619,6 +652,7 @@ begin
|
||||
FNavAdvanced.Active := ANav = FNavAdvanced;
|
||||
FNavCAT.Active := ANav = FNavCAT;
|
||||
FNavAlex.Active := ANav = FNavAlex;
|
||||
FNavXvtr.Active := ANav = FNavXvtr;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2531,4 +2565,310 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
// XVTR (Transverter) tab
|
||||
// ===========================================================================
|
||||
|
||||
procedure TSettingsForm.BuildXvtrTab;
|
||||
const
|
||||
MARGIN = 22;
|
||||
ROW_H = 30;
|
||||
HDR_H = 30; // место под заголовок столбца (одна строка + отступ)
|
||||
// X-координаты колонок (внутри Grp). Подобраны так чтобы помещаться
|
||||
// в content-area формы (~880px при ширине 1080).
|
||||
EN_X = 14;
|
||||
NAME_X = 40; NAME_W = 104; // подпись на band-кнопке (макс. 15 символов)
|
||||
BEG_X = 152; BEG_W = 80; // 3 десятичных, "144.000"
|
||||
END_X = 240; END_W = 80;
|
||||
LO_X = 328; LO_W = 90; // 6 десятичных, "116.000000"
|
||||
ERR_X = 426; ERR_W = 70; // целое Hz
|
||||
GAIN_X = 504; GAIN_W = 62; // 1 десятичная, dB
|
||||
RXO_X = 574; RXO_W = 56; // chk центрирован
|
||||
PWR_X = 638; PWR_W = 58; // целое %
|
||||
RF_X = 704; RF_W = 56;
|
||||
PA_X = 768; PA_W = 66;
|
||||
ANT_X = 842; ANT_W = 78; // combo default/ANT1/ANT2/ANT3
|
||||
GRP_W = ANT_X + ANT_W + 14; // 934
|
||||
var
|
||||
Grp, GrpOpts: TPanel;
|
||||
i, Y, GrpY: Integer;
|
||||
L: TLabel;
|
||||
|
||||
function MakeFlt(AParent: TWinControl; AX, AY, AW: Integer;
|
||||
AMin, AMax, AStep: Double; ADec: Integer): TFloatSpinEdit;
|
||||
begin
|
||||
Result := TFloatSpinEdit.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.SetBounds(AX, AY, AW, BTN_H);
|
||||
Result.MinValue := AMin;
|
||||
Result.MaxValue := AMax;
|
||||
Result.Increment := AStep;
|
||||
Result.DecimalPlaces := ADec;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
function MakeInt(AParent: TWinControl; AX, AY, AW: Integer;
|
||||
AMin, AMax: Integer): TSpinEdit;
|
||||
begin
|
||||
Result := TSpinEdit.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.SetBounds(AX, AY, AW, BTN_H);
|
||||
Result.MinValue := AMin;
|
||||
Result.MaxValue := AMax;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
// Чекбокс без подписи (только квадрат). Подпись пишем отдельно над колонкой.
|
||||
function MakeChk(AParent: TWinControl; AX, AY: Integer): TCheckBox;
|
||||
begin
|
||||
Result := TCheckBox.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.Caption := '';
|
||||
Result.SetBounds(AX, AY, 22, 20);
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
function MakeEdt(AParent: TWinControl; AX, AY, AW: Integer): TEdit;
|
||||
begin
|
||||
Result := TEdit.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.SetBounds(AX, AY, AW, BTN_H);
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.MaxLength := 15;
|
||||
Result.OnChange := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
function MakeCmb(AParent: TWinControl; AX, AY, AW: Integer): TComboBox;
|
||||
begin
|
||||
Result := TComboBox.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.SetBounds(AX, AY, AW, BTN_H);
|
||||
Result.Style := csDropDownList;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
// Заголовок столбца, центрирован по ширине AW от AX.
|
||||
function MakeHdr(AParent: TWinControl; const Cap: string;
|
||||
AX, AY, AW: Integer): TLabel;
|
||||
begin
|
||||
Result := TLabel.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.Caption := Cap;
|
||||
Result.SetBounds(AX, AY, AW, 14);
|
||||
Result.Alignment := taCenter;
|
||||
Result.Font.Color := CLR_TEXTDIM;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
end;
|
||||
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
// --- Заголовок страницы ---
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := FPageXvtr;
|
||||
L.Caption := 'Transverters';
|
||||
L.SetBounds(MARGIN, 18, 500, 28);
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 16;
|
||||
L.Font.Style := [fsBold]; L.Font.Color := CLR_TEXT;
|
||||
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := FPageXvtr;
|
||||
L.Caption := 'Configure up to 8 transverter (XVTR) bands. When a band is selected ' +
|
||||
'on the main form, the VFO shows the displayed (RF) frequency while ' +
|
||||
'the radio is internally tuned to the IF: f_IF = f_displayed − LO + LO Error.';
|
||||
L.SetBounds(MARGIN, 50, GRP_W, 36);
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||
L.WordWrap := True;
|
||||
|
||||
// --- Group "Slots" ---
|
||||
GrpY := 100;
|
||||
Grp := MakeGroupPanel(FPageXvtr, 'Slots',
|
||||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14);
|
||||
|
||||
// Заголовки колонок (под GRP_TITLE_H + небольшой отступ)
|
||||
Y := GRP_TITLE_H + 10;
|
||||
MakeHdr(Grp, 'EN', EN_X - 2, Y, 28);
|
||||
MakeHdr(Grp, 'Label', NAME_X, Y, NAME_W);
|
||||
MakeHdr(Grp, 'Begin (MHz)',BEG_X, Y, BEG_W);
|
||||
MakeHdr(Grp, 'End (MHz)', END_X, Y, END_W);
|
||||
MakeHdr(Grp, 'LO (MHz)', LO_X, Y, LO_W);
|
||||
MakeHdr(Grp, 'LO Error Hz',ERR_X, Y, ERR_W);
|
||||
MakeHdr(Grp, 'RX Gain dB', GAIN_X, Y, GAIN_W);
|
||||
MakeHdr(Grp, 'RX Only', RXO_X, Y, RXO_W);
|
||||
MakeHdr(Grp, 'TX Pwr %', PWR_X, Y, PWR_W);
|
||||
MakeHdr(Grp, 'XVTR RF', RF_X, Y, RF_W);
|
||||
MakeHdr(Grp, 'Disable PA', PA_X, Y, PA_W);
|
||||
MakeHdr(Grp, 'RX Antenna', ANT_X, Y, ANT_W);
|
||||
|
||||
// Ряды слотов
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
Y := GRP_TITLE_H + HDR_H + i * ROW_H + 2;
|
||||
// Чекбокс EN центрируем по 24px ширине EN-колонки
|
||||
FXvtrEn[i] := MakeChk(Grp, EN_X + 5, Y + 2);
|
||||
FXvtrName[i] := MakeEdt(Grp, NAME_X, Y, NAME_W);
|
||||
FXvtrBegin[i] := MakeFlt(Grp, BEG_X, Y, BEG_W, 0.0, 50000.0, 0.001, 3);
|
||||
FXvtrEnd[i] := MakeFlt(Grp, END_X, Y, END_W, 0.0, 50000.0, 0.001, 3);
|
||||
FXvtrLOOff[i] := MakeFlt(Grp, LO_X, Y, LO_W, -50000.0, 50000.0, 0.001, 6);
|
||||
FXvtrLOErr[i] := MakeFlt(Grp, ERR_X, Y, ERR_W, -99999.0, 99999.0, 1.0, 0);
|
||||
FXvtrRXGain[i]:= MakeFlt(Grp, GAIN_X, Y, GAIN_W, -60.0, 60.0, 0.1, 1);
|
||||
// Чекбокс RX Only центрируем
|
||||
FXvtrRXOnly[i]:= MakeChk(Grp, RXO_X + (RXO_W - 22) div 2, Y + 2);
|
||||
FXvtrPower[i] := MakeInt(Grp, PWR_X, Y, PWR_W, 0, 100);
|
||||
FXvtrRF[i] := MakeChk(Grp, RF_X + (RF_W - 22) div 2, Y + 2);
|
||||
FXvtrDisPA[i] := MakeChk(Grp, PA_X + (PA_W - 22) div 2, Y + 2);
|
||||
FXvtrRXAnt[i] := MakeCmb(Grp, ANT_X, Y, ANT_W);
|
||||
FXvtrRXAnt[i].Items.Add('default');
|
||||
FXvtrRXAnt[i].Items.Add('ANT1');
|
||||
FXvtrRXAnt[i].Items.Add('ANT2');
|
||||
FXvtrRXAnt[i].Items.Add('ANT3');
|
||||
FXvtrRXAnt[i].ItemIndex := 0;
|
||||
FXvtrEn[i].Tag := i;
|
||||
FXvtrEn[i].OnClick := OnXvtrAnyChange;
|
||||
end;
|
||||
|
||||
// --- Group "Options" ---
|
||||
GrpY := GrpY + GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14 + 18;
|
||||
GrpOpts := MakeGroupPanel(FPageXvtr, 'Options',
|
||||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + 36);
|
||||
FChkXvtrTunPwr := TCheckBox.Create(Self);
|
||||
FChkXvtrTunPwr.Parent := GrpOpts;
|
||||
FChkXvtrTunPwr.Caption := 'Use XVTR power for TUN '
|
||||
+ '(TUN button uses the per-slot TX Power instead of the global TUN Level)';
|
||||
FChkXvtrTunPwr.SetBounds(14, GRP_TITLE_H + 8, GRP_W - 28, 20);
|
||||
FChkXvtrTunPwr.Font.Color := CLR_TEXT;
|
||||
FChkXvtrTunPwr.Font.Name := UI_FONT;
|
||||
FChkXvtrTunPwr.Font.Size := 9;
|
||||
FChkXvtrTunPwr.OnChange := OnXvtrAnyChange;
|
||||
|
||||
// --- Подсказка про колонки внизу ---
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := FPageXvtr;
|
||||
L.Caption :=
|
||||
'EN — enable the slot. Label — band button caption on the main form. ' +
|
||||
'Begin/End — VFO range (displayed frequency). LO — local oscillator (subtracted from VFO to get IF). ' +
|
||||
'LO Error — fine correction in Hz. RX Gain — gain offset of the transverter RX side. ' +
|
||||
'RX Only — block TX on this slot. TX Pwr — drive limit (used as TUN level when "Use XVTR power for TUN" is set). ' +
|
||||
'XVTR RF — transverter uses the main HF RF path (do NOT engage Alex XVTR DDC IN pin). ' +
|
||||
'Disable PA — keep the internal PA in RX position during TX (XVTR has its own amplifier). ' +
|
||||
'RX Antenna — override the per-band antenna for this slot.';
|
||||
L.SetBounds(MARGIN, GrpY + GRP_TITLE_H + 36 + 16, GRP_W, 96);
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||
L.WordWrap := True;
|
||||
|
||||
// Применяем начальное состояние disabled-строк
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
UpdateXvtrRowEnabled(i);
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.UpdateXvtrRowEnabled(Idx: Integer);
|
||||
var En: Boolean;
|
||||
begin
|
||||
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
|
||||
if FXvtrEn[Idx] = nil then Exit;
|
||||
En := FXvtrEn[Idx].Checked;
|
||||
if FXvtrName[Idx] <> nil then FXvtrName[Idx].Enabled := En;
|
||||
if FXvtrBegin[Idx] <> nil then FXvtrBegin[Idx].Enabled := En;
|
||||
if FXvtrEnd[Idx] <> nil then FXvtrEnd[Idx].Enabled := En;
|
||||
if FXvtrLOOff[Idx] <> nil then FXvtrLOOff[Idx].Enabled := En;
|
||||
if FXvtrLOErr[Idx] <> nil then FXvtrLOErr[Idx].Enabled := En;
|
||||
if FXvtrRXGain[Idx]<> nil then FXvtrRXGain[Idx].Enabled:= En;
|
||||
if FXvtrRXOnly[Idx]<> nil then FXvtrRXOnly[Idx].Enabled:= En;
|
||||
if FXvtrPower[Idx] <> nil then FXvtrPower[Idx].Enabled := En;
|
||||
if FXvtrRF[Idx] <> nil then FXvtrRF[Idx].Enabled := En;
|
||||
if FXvtrDisPA[Idx] <> nil then FXvtrDisPA[Idx].Enabled := En;
|
||||
if FXvtrRXAnt[Idx] <> nil then FXvtrRXAnt[Idx].Enabled := En;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.CollectXvtrFromUI;
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
if FXvtrEn[i] <> nil then FXvtrConfig.Entries[i].Enabled := FXvtrEn[i].Checked;
|
||||
if FXvtrName[i] <> nil then FXvtrConfig.Entries[i].ButtonText := Copy(FXvtrName[i].Text, 1, 15);
|
||||
if FXvtrBegin[i] <> nil then FXvtrConfig.Entries[i].FreqBegin := FXvtrBegin[i].Value * 1.0e6;
|
||||
if FXvtrEnd[i] <> nil then FXvtrConfig.Entries[i].FreqEnd := FXvtrEnd[i].Value * 1.0e6;
|
||||
if FXvtrLOOff[i] <> nil then FXvtrConfig.Entries[i].LOOffset := FXvtrLOOff[i].Value * 1.0e6;
|
||||
if FXvtrLOErr[i] <> nil then FXvtrConfig.Entries[i].LOError := FXvtrLOErr[i].Value;
|
||||
if FXvtrRXGain[i]<> nil then FXvtrConfig.Entries[i].RXGain := FXvtrRXGain[i].Value;
|
||||
if FXvtrRXOnly[i]<> nil then FXvtrConfig.Entries[i].RXOnly := FXvtrRXOnly[i].Checked;
|
||||
if FXvtrPower[i] <> nil then FXvtrConfig.Entries[i].TXPower := FXvtrPower[i].Value;
|
||||
if FXvtrRF[i] <> nil then FXvtrConfig.Entries[i].XVTRRF := FXvtrRF[i].Checked;
|
||||
if FXvtrDisPA[i] <> nil then FXvtrConfig.Entries[i].DisablePA := FXvtrDisPA[i].Checked;
|
||||
if (FXvtrRXAnt[i]<> nil) and (FXvtrRXAnt[i].ItemIndex >= 0) then
|
||||
FXvtrConfig.Entries[i].RXAntenna := Byte(FXvtrRXAnt[i].ItemIndex);
|
||||
end;
|
||||
if FChkXvtrTunPwr <> nil then
|
||||
FXvtrConfig.UseXVTRTunePower := FChkXvtrTunPwr.Checked;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.FireXvtrChange;
|
||||
var i: Integer;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
CollectXvtrFromUI;
|
||||
// Перерисовать enabled-состояние строк (если изменилась галка En)
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
UpdateXvtrRowEnabled(i);
|
||||
if Assigned(FOnXvtrChange) then
|
||||
FOnXvtrChange(FXvtrConfig);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnXvtrAnyChange(Sender: TObject);
|
||||
begin
|
||||
FireXvtrChange;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadXvtrSettings(const X: TXvtrSettings);
|
||||
var i: Integer;
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
FXvtrConfig := X;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
if FXvtrEn[i] <> nil then FXvtrEn[i].Checked := X.Entries[i].Enabled;
|
||||
if FXvtrName[i] <> nil then FXvtrName[i].Text := X.Entries[i].ButtonText;
|
||||
if FXvtrBegin[i] <> nil then FXvtrBegin[i].Value := X.Entries[i].FreqBegin / 1.0e6;
|
||||
if FXvtrEnd[i] <> nil then FXvtrEnd[i].Value := X.Entries[i].FreqEnd / 1.0e6;
|
||||
if FXvtrLOOff[i] <> nil then FXvtrLOOff[i].Value := X.Entries[i].LOOffset / 1.0e6;
|
||||
if FXvtrLOErr[i] <> nil then FXvtrLOErr[i].Value := X.Entries[i].LOError;
|
||||
if FXvtrRXGain[i]<> nil then FXvtrRXGain[i].Value := X.Entries[i].RXGain;
|
||||
if FXvtrRXOnly[i]<> nil then FXvtrRXOnly[i].Checked:= X.Entries[i].RXOnly;
|
||||
if FXvtrPower[i] <> nil then FXvtrPower[i].Value := X.Entries[i].TXPower;
|
||||
if FXvtrRF[i] <> nil then FXvtrRF[i].Checked := X.Entries[i].XVTRRF;
|
||||
if FXvtrDisPA[i] <> nil then FXvtrDisPA[i].Checked := X.Entries[i].DisablePA;
|
||||
if FXvtrRXAnt[i] <> nil then
|
||||
FXvtrRXAnt[i].ItemIndex := EnsureRange(X.Entries[i].RXAntenna, 0, 3);
|
||||
UpdateXvtrRowEnabled(i);
|
||||
end;
|
||||
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+25
-2
@@ -374,8 +374,21 @@ begin
|
||||
|
||||
'const BAND_N=["160m","80m","60m","40m","30m","20m","17m","15m","12m","10m","6m"];' +
|
||||
'const BAND_F=[1900000,3750000,5357000,7100000,10125000,14200000,18120000,21200000,24940000,28500000,50150000];' +
|
||||
'(function(){const s=q("bandSel");BAND_N.forEach((n,i)=>{const o=document.createElement("option");o.value=i;o.textContent=n;s.appendChild(o);});s.onchange=()=>ws2({cmd:"band",idx:+s.value});})();' +
|
||||
'function updBand(vfo){let bi=0,bd=1e15;BAND_F.forEach((f,i)=>{const d=Math.abs((vfo||0)-f);if(d<bd){bd=d;bi=i;}});q("bandSel").value=bi;}' +
|
||||
// XVTR-band list, обновляется из state ("xvtr_bands":[{idx,name},...]).
|
||||
// Активное значение — "xvtr_current" (-1 = HF). В <select> используем
|
||||
// значения "h<n>" для HF и "x<idx>" для XVTR, чтобы различать.
|
||||
'let XVTR_BANDS=[];let XVTR_CUR=-1;' +
|
||||
'function rebuildBandSel(){const s=q("bandSel");if(!s)return;' +
|
||||
'const saved=s.value;s.innerHTML="";' +
|
||||
'BAND_N.forEach((n,i)=>{const o=document.createElement("option");o.value="h"+i;o.textContent=n;s.appendChild(o);});' +
|
||||
'XVTR_BANDS.forEach(xb=>{const o=document.createElement("option");o.value="x"+xb.idx;o.textContent=xb.name||("X"+(xb.idx+1));s.appendChild(o);});' +
|
||||
// Восстановить выбор: предпочитаем "current XVTR"; иначе сохраним прежнее
|
||||
'if(XVTR_CUR>=0)s.value="x"+XVTR_CUR; else if(saved)s.value=saved;' +
|
||||
'}' +
|
||||
'(function(){const s=q("bandSel");BAND_N.forEach((n,i)=>{const o=document.createElement("option");o.value="h"+i;o.textContent=n;s.appendChild(o);});' +
|
||||
's.onchange=()=>{const v=s.value;if(!v)return;if(v[0]==="x")ws2({cmd:"xvtr_band",idx:+v.slice(1)});else if(v[0]==="h")ws2({cmd:"band",idx:+v.slice(1)});};' +
|
||||
'})();' +
|
||||
'function updBand(vfo){if(XVTR_CUR>=0){q("bandSel").value="x"+XVTR_CUR;return;}let bi=0,bd=1e15;BAND_F.forEach((f,i)=>{const d=Math.abs((vfo||0)-f);if(d<bd){bd=d;bi=i;}});q("bandSel").value="h"+bi;}' +
|
||||
|
||||
'let smAvg=-130,smPeak=-130,smMin=-130;' +
|
||||
'function dbToX(db,bx,bw){const DB_MIN=-127,DB_MAX=-13;return Math.max(bx,Math.min(bx+bw,Math.round(bx+(db-DB_MIN)/(DB_MAX-DB_MIN)*bw)));}' +
|
||||
@@ -438,6 +451,16 @@ begin
|
||||
|
||||
'function applyState(msg){' +
|
||||
'Object.keys(msg).forEach(function(k){var pk=PEND_MAP[k];if(!pk||!isPending(pk))cur[k]=msg[k];});' +
|
||||
// Перестраиваем bandSel если изменился список XVTR или текущий индекс
|
||||
'if(msg.xvtr_bands!=null||msg.xvtr_current!=null){' +
|
||||
'const newBands=msg.xvtr_bands||XVTR_BANDS;' +
|
||||
'const newCur=(msg.xvtr_current!=null)?msg.xvtr_current:XVTR_CUR;' +
|
||||
'const bandsChanged=JSON.stringify(newBands)!==JSON.stringify(XVTR_BANDS);' +
|
||||
'const curChanged=newCur!==XVTR_CUR;' +
|
||||
'XVTR_BANDS=newBands;XVTR_CUR=newCur;' +
|
||||
'if(bandsChanged)rebuildBandSel();' +
|
||||
'else if(curChanged){const s=q("bandSel");if(s&&XVTR_CUR>=0)s.value="x"+XVTR_CUR;}' +
|
||||
'}' +
|
||||
// Обновляем localCenter/localSpan из сервера только когда пользователь не тянет
|
||||
'if(!dragging&&Date.now()-wheelActiveTs>300){' +
|
||||
// В CTUN без DUP: TX-спектр WDSP центрирован по vfo_a_hz, а не по DDC LO.
|
||||
|
||||
+53
-3
@@ -136,6 +136,15 @@ type
|
||||
TWebCmdFreqA = procedure(Hz: Double) of object;
|
||||
TWebCmdAttn = procedure(Idx: Integer) of object;
|
||||
TWebCmdTun = procedure(On_: Boolean) of object;
|
||||
// XVTR-band: web клиент кликнул кнопку трансвертера (Idx 0..CFG_XVTR_COUNT-1).
|
||||
// Idx=-1 — выход в HF.
|
||||
TWebCmdXvtrBand = procedure(Idx: Integer) of object;
|
||||
// Один XVTR-слот для статуса (для отображения в web-bandSel).
|
||||
TWebXvtrInfo = record
|
||||
Idx: Integer;
|
||||
Name: string;
|
||||
end;
|
||||
TWebXvtrArray = array of TWebXvtrInfo;
|
||||
TWebMicCB = procedure(Samples: PSingle; Count: Integer) of object;
|
||||
|
||||
// ── Главный класс сервера ─────────────────────────────────────────────────
|
||||
@@ -233,6 +242,9 @@ type
|
||||
FOnTun: TWebCmdTun;
|
||||
FTuning: Boolean;
|
||||
FDuplex: Boolean;
|
||||
FXvtrBands: TWebXvtrArray; // список enabled XVTR (для web-UI)
|
||||
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
|
||||
FOnXvtrBand: TWebCmdXvtrBand;
|
||||
|
||||
FWebClientActive: Boolean;
|
||||
|
||||
@@ -286,7 +298,9 @@ type
|
||||
property OnFilter: TWebCmdFilter read FOnFilter write FOnFilter;
|
||||
property OnAGC: TWebCmdAGC read FOnAGC write FOnAGC;
|
||||
property OnAGCTop: TWebCmdAGCTop read FOnAGCTop write FOnAGCTop;
|
||||
property OnBand: TWebCmdBand read FOnBand write FOnBand;
|
||||
property OnBand: TWebCmdBand read FOnBand write FOnBand;
|
||||
property OnXvtrBand: TWebCmdXvtrBand read FOnXvtrBand write FOnXvtrBand;
|
||||
procedure SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
||||
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
|
||||
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
|
||||
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
|
||||
@@ -405,6 +419,22 @@ begin
|
||||
FVolume := 70;
|
||||
FSMeter := -120;
|
||||
FBandIdx := 5;
|
||||
FCurrentXvtr := -1;
|
||||
SetLength(FXvtrBands, 0);
|
||||
end;
|
||||
|
||||
procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
||||
var i: Integer;
|
||||
begin
|
||||
FStateLock.Enter;
|
||||
try
|
||||
SetLength(FXvtrBands, Length(ABands));
|
||||
for i := 0 to High(ABands) do
|
||||
FXvtrBands[i] := ABands[i];
|
||||
FCurrentXvtr := ACurrent;
|
||||
finally
|
||||
FStateLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
destructor TWebServer.Destroy;
|
||||
@@ -946,6 +976,12 @@ begin
|
||||
FStateLock.Enter; FBandIdx := Idx; FStateLock.Leave;
|
||||
if Assigned(FOnBand) then FOnBand(Idx);
|
||||
end
|
||||
else if Cmd = 'xvtr_band' then
|
||||
begin
|
||||
// Idx 0..CFG_XVTR_COUNT-1 — активировать XVTR; -1 — выйти в HF
|
||||
Idx := JsonGetInt(Json, 'idx', -1);
|
||||
if Assigned(FOnXvtrBand) then FOnXvtrBand(Idx);
|
||||
end
|
||||
else if Cmd = 'span' then
|
||||
begin
|
||||
HzI := JsonGetInt(Json, 'hz', Round(FSpanHz));
|
||||
@@ -1170,11 +1206,23 @@ const
|
||||
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
||||
var
|
||||
FS: TFormatSettings;
|
||||
XvtrJson: string;
|
||||
i: Integer;
|
||||
begin
|
||||
FS := DefaultFormatSettings;
|
||||
FS.DecimalSeparator := '.';
|
||||
FStateLock.Enter;
|
||||
try
|
||||
// Сборка массива xvtr_bands: [{"idx":0,"name":"2m"},...]
|
||||
XvtrJson := '[';
|
||||
for i := 0 to High(FXvtrBands) do
|
||||
begin
|
||||
if i > 0 then XvtrJson := XvtrJson + ',';
|
||||
XvtrJson := XvtrJson +
|
||||
Format('{"idx":%d,"name":"%s"}',
|
||||
[FXvtrBands[i].Idx, FXvtrBands[i].Name], FS);
|
||||
end;
|
||||
XvtrJson := XvtrJson + ']';
|
||||
Result := Format(
|
||||
'{"vfo_a_hz":%.0f,"vfo_b_hz":%.0f,"active_vfo":%d,' +
|
||||
'"mode":%d,"mode_name":"%s",' +
|
||||
@@ -1184,7 +1232,8 @@ begin
|
||||
'"wf_agc":%s,"wf_nf":%s,"band_idx":%d,"smeter_dbm":%.1f,' +
|
||||
'"running":%s,"mute":%s,"ctun":%s,' +
|
||||
'"nr_mode":%d,"nr":%s,"nb_mode":%d,"nb":%s,"snb":%s,"anf":%s,"connected":%s,' +
|
||||
'"transmitting":%s,"drive":%d,"attn_idx":%d,"tuning":%s,"dup":%s}',
|
||||
'"transmitting":%s,"drive":%d,"attn_idx":%d,"tuning":%s,"dup":%s,' +
|
||||
'"xvtr_current":%d,"xvtr_bands":%s}',
|
||||
[FFreq, FVfoB, FActiveVfo,
|
||||
FMode, MODE_N[FMode mod 8],
|
||||
FFilterIdx, FFilterBW,
|
||||
@@ -1207,7 +1256,8 @@ begin
|
||||
FDriveLevel,
|
||||
FAttnIdx,
|
||||
BoolToStr(FTuning, 'true', 'false'),
|
||||
BoolToStr(FDuplex, 'true', 'false')
|
||||
BoolToStr(FDuplex, 'true', 'false'),
|
||||
FCurrentXvtr, XvtrJson
|
||||
], FS);
|
||||
finally
|
||||
FStateLock.Leave;
|
||||
|
||||
Reference in New Issue
Block a user