mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 20:57:32 +00:00
feat: QO-100 self-monitor (RX MUTE) + gate beacon/RX-mute buttons to QO-100
Add a per-operator toggle to hear your own downlink off the satellite while transmitting (full-duplex self-monitor), and restrict QO-100-only controls to the QO-100 mode. - WDSPEngine: new MonitorRXDuringTX flag; audio gate becomes `not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX)` so RX audio can pass to speakers during TX in full-duplex. - Controller: FRxMuteOnTx (default True = mute RX on TX), SetRxMuteOnTx/ ApplyRxMuteToEngine, rfRxMuteOnTx notify; new InQO100 predicate (Pluto + full-duplex XVTR slot) as the single gate for QO-100 UI. - Settings: global RxMuteOnTx (rx_mute_on_tx, default True), persisted. - MainForm: new RX MUTE button in TX panel next to DUP, visible only in QO-100; BEACON button visibility tightened from "any transverter" to InQO100 so it no longer shows in plain VHF/UHF modes. - doc: plan updated (RF-AGC done, two temps, TX/RX locked split, self-monitor, phase 6 done; remaining TODOs incl. web wiring). Web intentionally left unchanged for now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+35
-1
@@ -237,6 +237,7 @@ type
|
|||||||
BtnCTun: TFlatButton;
|
BtnCTun: TFlatButton;
|
||||||
BtnBeacon: TFlatButton; // QO-100 beacon lock (видим только на Pluto в трансвертере)
|
BtnBeacon: TFlatButton; // QO-100 beacon lock (видим только на Pluto в трансвертере)
|
||||||
BtnDUP: TFlatButton;
|
BtnDUP: TFlatButton;
|
||||||
|
BtnRxMute: TFlatButton; // QO-100 self-monitor: RX-аудио на TX (видим только Pluto+full-duplex)
|
||||||
PanelFMSQ: TPanel;
|
PanelFMSQ: TPanel;
|
||||||
BtnFMSQ: TFlatButton;
|
BtnFMSQ: TFlatButton;
|
||||||
TrkFMSQ: TFlatSlider;
|
TrkFMSQ: TFlatSlider;
|
||||||
@@ -387,6 +388,8 @@ type
|
|||||||
procedure UpdateBeaconButton;
|
procedure UpdateBeaconButton;
|
||||||
procedure BtnDUPClick(Sender: TObject);
|
procedure BtnDUPClick(Sender: TObject);
|
||||||
procedure ApplyDUP(Active: Boolean);
|
procedure ApplyDUP(Active: Boolean);
|
||||||
|
procedure BtnRxMuteClick(Sender: TObject);
|
||||||
|
procedure UpdateRxMuteButton; // видимость (Pluto+full-duplex) + стиль кнопки RX MUTE
|
||||||
procedure BtnTUNClick(Sender: TObject);
|
procedure BtnTUNClick(Sender: TObject);
|
||||||
procedure ApplyTUN(Active: Boolean);
|
procedure ApplyTUN(Active: Boolean);
|
||||||
procedure UpdateFilterButtons;
|
procedure UpdateFilterButtons;
|
||||||
@@ -1212,9 +1215,12 @@ begin
|
|||||||
// openHPSDR смысла не имеет (нет дрейфующего LNB → прячем).
|
// openHPSDR смысла не имеет (нет дрейфующего LNB → прячем).
|
||||||
if BtnBeacon <> nil then
|
if BtnBeacon <> nil then
|
||||||
begin
|
begin
|
||||||
BtnBeacon.Visible := FController.IsPluto and (FController.FCurrentXvtr >= 0);
|
// Только Pluto + full-duplex транспондер (QO-100) — beacon-lock завязан
|
||||||
|
// на дрейф LNB, в обычных диапазонах/трансвертерах смысла нет.
|
||||||
|
BtnBeacon.Visible := FController.InQO100;
|
||||||
UpdateBeaconButton;
|
UpdateBeaconButton;
|
||||||
end;
|
end;
|
||||||
|
UpdateRxMuteButton; // RX MUTE — только Pluto + full-duplex (QO-100)
|
||||||
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
||||||
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
||||||
UpdateWidebandFrequencyView;
|
UpdateWidebandFrequencyView;
|
||||||
@@ -1313,6 +1319,8 @@ begin
|
|||||||
PbWaterfall.Invalidate;
|
PbWaterfall.Invalidate;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
rfRxMuteOnTx:
|
||||||
|
UpdateRxMuteButton; // QO-100 self-monitor toggle: подсветка кнопки RX MUTE
|
||||||
rfWfAGC:
|
rfWfAGC:
|
||||||
FSpecView.WfAGCEnabled := FController.FWfAGCEnabled;
|
FSpecView.WfAGCEnabled := FController.FWfAGCEnabled;
|
||||||
rfWfNF:
|
rfWfNF:
|
||||||
@@ -1707,6 +1715,15 @@ begin
|
|||||||
BtnDUP.Tag := 0;
|
BtnDUP.Tag := 0;
|
||||||
StyleButton(BtnDUP, FController.FDisplayDuplex);
|
StyleButton(BtnDUP, FController.FDisplayDuplex);
|
||||||
|
|
||||||
|
// QO-100 self-monitor: рядом с DUP, col1. Виден только на Pluto в full-duplex
|
||||||
|
// транспондере. ON (подсвечена, умолч.) = RX-аудио глушится на TX; OFF = слышим
|
||||||
|
// свой downlink со спутника.
|
||||||
|
BtnRxMute := MakeBtn(PanelTX, 'RX MUTE',
|
||||||
|
LeftPanelButtonLeft(LEFT_W, 4, 1), 44,
|
||||||
|
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnRxMuteClick);
|
||||||
|
BtnRxMute.Visible := False;
|
||||||
|
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||||
|
|
||||||
LblDrv := TLabel.Create(Self);
|
LblDrv := TLabel.Create(Self);
|
||||||
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
|
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
|
||||||
LblDrv.Caption := 'DRV'; LblDrv.Font.Color := CLR_TEXTDIM;
|
LblDrv.Caption := 'DRV'; LblDrv.Font.Color := CLR_TEXTDIM;
|
||||||
@@ -3697,6 +3714,7 @@ begin
|
|||||||
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
|
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
|
||||||
LayoutLeftPanel(Caps.HasHWGain);
|
LayoutLeftPanel(Caps.HasHWGain);
|
||||||
if Caps.HasHWGain then UpdateRxGainUI;
|
if Caps.HasHWGain then UpdateRxGainUI;
|
||||||
|
UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
||||||
@@ -4032,6 +4050,22 @@ begin
|
|||||||
ApplyDUP(not FController.FDisplayDuplex);
|
ApplyDUP(not FController.FDisplayDuplex);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.BtnRxMuteClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
FController.SetRxMuteOnTx(not FController.FRxMuteOnTx); // рендер через rfRxMuteOnTx
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.UpdateRxMuteButton;
|
||||||
|
// Кнопка RX MUTE видна только на Pluto в full-duplex транспондере (QO-100).
|
||||||
|
// ON (подсвечена) = RX-аудио глушится на TX; OFF = self-monitor (слышим себя).
|
||||||
|
var Vis: Boolean;
|
||||||
|
begin
|
||||||
|
if BtnRxMute = nil then Exit;
|
||||||
|
Vis := FController.InQO100;
|
||||||
|
BtnRxMute.Visible := Vis;
|
||||||
|
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||||
|
end;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
|
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+37
-1
@@ -67,7 +67,7 @@ type
|
|||||||
rfVolume, rfMute, rfDrive, rfAtten,
|
rfVolume, rfMute, rfDrive, rfAtten,
|
||||||
rfBand, rfXvtr, rfBandRestore, // rfBandRestore: явная смена диапазона (сброс Wf-avg в UI)
|
rfBand, rfXvtr, rfBandRestore, // rfBandRestore: явная смена диапазона (сброс Wf-avg в UI)
|
||||||
rfCenterFreq, rfSpan, rfSampleRate,
|
rfCenterFreq, rfSpan, rfSampleRate,
|
||||||
rfCTun, rfDuplex,
|
rfCTun, rfDuplex, rfRxMuteOnTx, // rfRxMuteOnTx: QO-100 self-monitor toggle
|
||||||
rfRunning, rfConnected, rfTransmitting, rfTuning,
|
rfRunning, rfConnected, rfTransmitting, rfTuning,
|
||||||
rfNR, rfNB, rfSNB, rfANF,
|
rfNR, rfNB, rfSNB, rfANF,
|
||||||
rfWfAGC, rfWfNF, rfDither, rfRandom,
|
rfWfAGC, rfWfNF, rfDither, rfRandom,
|
||||||
@@ -217,6 +217,9 @@ type
|
|||||||
FTuning: Boolean;
|
FTuning: Boolean;
|
||||||
FSplitTxB: Boolean;
|
FSplitTxB: Boolean;
|
||||||
FDisplayDuplex: Boolean;
|
FDisplayDuplex: Boolean;
|
||||||
|
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
|
||||||
|
// False — слышим свой downlink со спутника при передаче. Кнопка RX MUTE.
|
||||||
|
FRxMuteOnTx: Boolean;
|
||||||
FHWPTTActive: Boolean;
|
FHWPTTActive: Boolean;
|
||||||
FHWPTTStartedTX: Boolean;
|
FHWPTTStartedTX: Boolean;
|
||||||
FWebClientActive: Boolean; // выставляет web-слой (connect/disconnect клиента)
|
FWebClientActive: Boolean; // выставляет web-слой (connect/disconnect клиента)
|
||||||
@@ -496,6 +499,8 @@ type
|
|||||||
// Отображение / приём
|
// Отображение / приём
|
||||||
procedure SetCTun(On_: Boolean);
|
procedure SetCTun(On_: Boolean);
|
||||||
procedure SetDuplex(On_: Boolean);
|
procedure SetDuplex(On_: Boolean);
|
||||||
|
procedure SetRxMuteOnTx(On_: Boolean); // QO-100 self-monitor (кнопка RX MUTE)
|
||||||
|
procedure ApplyRxMuteToEngine; // FRxMuteOnTx → FDSPEngine.MonitorRXDuringTX
|
||||||
procedure SetWfAGC(On_: Boolean);
|
procedure SetWfAGC(On_: Boolean);
|
||||||
procedure SetWfNF(On_: Boolean);
|
procedure SetWfNF(On_: Boolean);
|
||||||
|
|
||||||
@@ -552,6 +557,8 @@ type
|
|||||||
function BandPlan: TBandPlanArray;
|
function BandPlan: TBandPlanArray;
|
||||||
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
||||||
function IsPluto: Boolean;
|
function IsPluto: Boolean;
|
||||||
|
// True — активен QO-100 (Pluto + full-duplex XVTR-слот); гейт UI beacon/RX MUTE.
|
||||||
|
function InQO100: Boolean;
|
||||||
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
||||||
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
||||||
function BoardDisplayName: string;
|
function BoardDisplayName: string;
|
||||||
@@ -593,6 +600,7 @@ begin
|
|||||||
FMode := 1; FFilter := 5; FFilterBW := 2700;
|
FMode := 1; FFilter := 5; FFilterBW := 2700;
|
||||||
FAGCMode := 1; FAGCTop := 90;
|
FAGCMode := 1; FAGCTop := 90;
|
||||||
FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию
|
FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию
|
||||||
|
FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX)
|
||||||
FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
|
FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
|
||||||
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
|
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
|
||||||
FCurrentBand := 5; FCurrentXvtr := -1;
|
FCurrentBand := 5; FCurrentXvtr := -1;
|
||||||
@@ -1552,6 +1560,7 @@ begin
|
|||||||
Result.WidebandFill := FWidebandFill;
|
Result.WidebandFill := FWidebandFill;
|
||||||
Result.SpectrumFill := FSpectrumFill;
|
Result.SpectrumFill := FSpectrumFill;
|
||||||
Result.DisplayDuplex := FDisplayDuplex;
|
Result.DisplayDuplex := FDisplayDuplex;
|
||||||
|
Result.RxMuteOnTx := FRxMuteOnTx;
|
||||||
Result.AudioOutDevice := FAudioOutDevName;
|
Result.AudioOutDevice := FAudioOutDevName;
|
||||||
Result.AudioInDevice := FAudioInDevName;
|
Result.AudioInDevice := FAudioInDevName;
|
||||||
Result.SendAudioToRadio := FSendAudioToRadio;
|
Result.SendAudioToRadio := FSendAudioToRadio;
|
||||||
@@ -2371,6 +2380,21 @@ begin
|
|||||||
Changed(rfDuplex);
|
Changed(rfDuplex);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.ApplyRxMuteToEngine;
|
||||||
|
begin
|
||||||
|
// Монитор имеет смысл только в full-duplex (KeepRXDuringTX); сам гейт в движке
|
||||||
|
// это учитывает. Здесь просто транслируем инверсию флага RX MUTE.
|
||||||
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
FDSPEngine.MonitorRXDuringTX := not FRxMuteOnTx;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetRxMuteOnTx(On_: Boolean);
|
||||||
|
begin
|
||||||
|
FRxMuteOnTx := On_;
|
||||||
|
ApplyRxMuteToEngine;
|
||||||
|
Changed(rfRxMuteOnTx);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetWfAGC(On_: Boolean);
|
procedure TRadioController.SetWfAGC(On_: Boolean);
|
||||||
begin FWfAGCEnabled := On_; Changed(rfWfAGC); end;
|
begin FWfAGCEnabled := On_; Changed(rfWfAGC); end;
|
||||||
|
|
||||||
@@ -2783,6 +2807,8 @@ begin
|
|||||||
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
||||||
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
||||||
ApplyRxGainToBackend;
|
ApplyRxGainToBackend;
|
||||||
|
// QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено.
|
||||||
|
ApplyRxMuteToEngine;
|
||||||
SendDUCSpecificFromSettings;
|
SendDUCSpecificFromSettings;
|
||||||
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
||||||
ApplyTXSettingsToDSP;
|
ApplyTXSettingsToDSP;
|
||||||
@@ -2949,6 +2975,7 @@ begin
|
|||||||
FWidebandFill := FLoadedGlobal.WidebandFill;
|
FWidebandFill := FLoadedGlobal.WidebandFill;
|
||||||
FSpectrumFill := FLoadedGlobal.SpectrumFill;
|
FSpectrumFill := FLoadedGlobal.SpectrumFill;
|
||||||
FDisplayDuplex := FLoadedGlobal.DisplayDuplex;
|
FDisplayDuplex := FLoadedGlobal.DisplayDuplex;
|
||||||
|
FRxMuteOnTx := FLoadedGlobal.RxMuteOnTx;
|
||||||
// Настройки дисплея/сетки + DSP display + аудио-устройства
|
// Настройки дисплея/сетки + DSP display + аудио-устройства
|
||||||
if FLoadedGlobal.FFTSize > 0 then
|
if FLoadedGlobal.FFTSize > 0 then
|
||||||
begin
|
begin
|
||||||
@@ -3177,6 +3204,15 @@ begin
|
|||||||
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.InQO100: Boolean;
|
||||||
|
// True — активен full-duplex транспондер (QO-100): Pluto + выбран XVTR-слот с
|
||||||
|
// флагом FullDuplexTx. Гейт для UI-элементов, осмысленных только в этом режиме
|
||||||
|
// (beacon-lock, RX MUTE self-monitor).
|
||||||
|
begin
|
||||||
|
Result := IsPluto and (FCurrentXvtr >= 0)
|
||||||
|
and FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx;
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.BoardDisplayName: string;
|
function TRadioController.BoardDisplayName: string;
|
||||||
var
|
var
|
||||||
Dev: THPSDRDevice;
|
Dev: THPSDRDevice;
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ type
|
|||||||
WidebandFill: Boolean; // True = fill wideband spectrum with gradient
|
WidebandFill: Boolean; // True = fill wideband spectrum with gradient
|
||||||
SpectrumFill: Boolean; // True = fill main spectrum under the curve with gradient
|
SpectrumFill: Boolean; // True = fill main spectrum under the curve with gradient
|
||||||
DisplayDuplex: Boolean; // DUP: при TX показывать RX-водопад, TX-фильтр overlay'ем
|
DisplayDuplex: Boolean; // DUP: при TX показывать RX-водопад, TX-фильтр overlay'ем
|
||||||
|
RxMuteOnTx: Boolean; // QO-100 self-monitor: True=RX-аудио глушится на TX (умолч.)
|
||||||
// --- Display FPS ---
|
// --- Display FPS ---
|
||||||
DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100)
|
DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100)
|
||||||
// --- Theme ---
|
// --- Theme ---
|
||||||
@@ -448,6 +449,7 @@ begin
|
|||||||
G.WidebandFill := False;
|
G.WidebandFill := False;
|
||||||
G.SpectrumFill := True;
|
G.SpectrumFill := True;
|
||||||
G.DisplayDuplex := False;
|
G.DisplayDuplex := False;
|
||||||
|
G.RxMuteOnTx := True;
|
||||||
G.DisplayFPS := 60;
|
G.DisplayFPS := 60;
|
||||||
G.FreqMhzDigits := 3;
|
G.FreqMhzDigits := 3;
|
||||||
// CAT defaults
|
// CAT defaults
|
||||||
@@ -671,6 +673,7 @@ begin
|
|||||||
G.WidebandFill := JB(GObj,'wideband_fill',False);
|
G.WidebandFill := JB(GObj,'wideband_fill',False);
|
||||||
G.SpectrumFill := JB(GObj,'spectrum_fill',True);
|
G.SpectrumFill := JB(GObj,'spectrum_fill',True);
|
||||||
G.DisplayDuplex := JB(GObj,'display_duplex',False);
|
G.DisplayDuplex := JB(GObj,'display_duplex',False);
|
||||||
|
G.RxMuteOnTx := JB(GObj,'rx_mute_on_tx',True);
|
||||||
G.DisplayFPS := JI(GObj,'display_fps',60);
|
G.DisplayFPS := JI(GObj,'display_fps',60);
|
||||||
G.LightTheme := JB(GObj,'light_theme',False);
|
G.LightTheme := JB(GObj,'light_theme',False);
|
||||||
G.FreqMhzDigits := EnsureRange(JI(GObj,'freq_mhz_digits',3), 3, 5);
|
G.FreqMhzDigits := EnsureRange(JI(GObj,'freq_mhz_digits',3), 3, 5);
|
||||||
@@ -774,6 +777,7 @@ begin
|
|||||||
JW(O,'wideband_fill',G.WidebandFill);
|
JW(O,'wideband_fill',G.WidebandFill);
|
||||||
JW(O,'spectrum_fill',G.SpectrumFill);
|
JW(O,'spectrum_fill',G.SpectrumFill);
|
||||||
JW(O,'display_duplex',G.DisplayDuplex);
|
JW(O,'display_duplex',G.DisplayDuplex);
|
||||||
|
JW(O,'rx_mute_on_tx',G.RxMuteOnTx);
|
||||||
JW(O,'display_fps',G.DisplayFPS);
|
JW(O,'display_fps',G.DisplayFPS);
|
||||||
JW(O,'light_theme',G.LightTheme);
|
JW(O,'light_theme',G.LightTheme);
|
||||||
JW(O,'freq_mhz_digits',G.FreqMhzDigits);
|
JW(O,'freq_mhz_digits',G.FreqMhzDigits);
|
||||||
|
|||||||
+14
-1
@@ -223,6 +223,10 @@ type
|
|||||||
// (мы выяснили на практике что стоп/старт RXA добавляет задержки на
|
// (мы выяснили на практике что стоп/старт RXA добавляет задержки на
|
||||||
// Windows без видимого выигрыша).
|
// Windows без видимого выигрыша).
|
||||||
FKeepRXDuringTX: Boolean;
|
FKeepRXDuringTX: Boolean;
|
||||||
|
// Полный дуплекс QO-100: пропускать RX-аудио на колонки и во время TX (слышим
|
||||||
|
// свой downlink через транспондер). Имеет смысл только при FKeepRXDuringTX.
|
||||||
|
// По умолчанию False — обычное поведение (RX-аудио глушится на TX).
|
||||||
|
FMonitorRXDuringTX: Boolean;
|
||||||
// Дедлайн (GetTickCount64), до которого RX-аудио не пишется в FOnAudio.
|
// Дедлайн (GetTickCount64), до которого RX-аудио не пишется в FOnAudio.
|
||||||
// Используется на Windows для маскировки «хвоста» TX leakage (radio
|
// Используется на Windows для маскировки «хвоста» TX leakage (radio
|
||||||
// FPGA TX-buffer + PA slew-down → RX1 IQ → audio) после MOX-off.
|
// FPGA TX-buffer + PA slew-down → RX1 IQ → audio) после MOX-off.
|
||||||
@@ -461,6 +465,10 @@ type
|
|||||||
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
||||||
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
||||||
write FKeepRXDuringTX;
|
write FKeepRXDuringTX;
|
||||||
|
// Full-duplex self-monitor: при TX пропускать RX-аудио на колонки (QO-100 —
|
||||||
|
// слышим себя со спутника). Действует только вместе с KeepRXDuringTX.
|
||||||
|
property MonitorRXDuringTX: Boolean read FMonitorRXDuringTX
|
||||||
|
write FMonitorRXDuringTX;
|
||||||
// Активный display ID — RX_DISP_ID или TX_DISP_ID (зависит от FTXActive).
|
// Активный display ID — RX_DISP_ID или TX_DISP_ID (зависит от FTXActive).
|
||||||
// SpectrumView/Waterfall должны читать пиксели именно из этого ID.
|
// SpectrumView/Waterfall должны читать пиксели именно из этого ID.
|
||||||
property ActiveDisplayID: Integer read FActiveDisplayID;
|
property ActiveDisplayID: Integer read FActiveDisplayID;
|
||||||
@@ -788,6 +796,7 @@ begin
|
|||||||
FTXMicTail := 0;
|
FTXMicTail := 0;
|
||||||
FTXActive := False;
|
FTXActive := False;
|
||||||
FKeepRXDuringTX := False;
|
FKeepRXDuringTX := False;
|
||||||
|
FMonitorRXDuringTX := False;
|
||||||
FPostTXMuteUntil := 0;
|
FPostTXMuteUntil := 0;
|
||||||
FActiveDisplayID := RX_DISP_ID;
|
FActiveDisplayID := RX_DISP_ID;
|
||||||
FTXMicSource := txmsRadio;
|
FTXMicSource := txmsRadio;
|
||||||
@@ -1302,7 +1311,11 @@ begin
|
|||||||
// через unsigned-сравнение, GetTickCount64 не зовётся когда дедлайн = 0.
|
// через unsigned-сравнение, GetTickCount64 не зовётся когда дедлайн = 0.
|
||||||
if (FPostTXMuteUntil <> 0) and (GetTickCount64 >= FPostTXMuteUntil) then
|
if (FPostTXMuteUntil <> 0) and (GetTickCount64 >= FPostTXMuteUntil) then
|
||||||
FPostTXMuteUntil := 0;
|
FPostTXMuteUntil := 0;
|
||||||
if Assigned(FOnAudio) and not FMuted and not FTXActive
|
// Full-duplex self-monitor (QO-100): при FMonitorRXDuringTX не глушим RX-аудио
|
||||||
|
// на TX — слышим свой downlink со спутника. Иначе (по умолчанию) глушим, как
|
||||||
|
// Thetis/piHPSDR при не-duplex MOX. FPostTXMuteUntil-хвост уважаем в любом случае.
|
||||||
|
if Assigned(FOnAudio) and not FMuted
|
||||||
|
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
||||||
and (FPostTXMuteUntil = 0) then
|
and (FPostTXMuteUntil = 0) then
|
||||||
begin
|
begin
|
||||||
for i := 0 to FAudioBufSize - 1 do
|
for i := 0 to FAudioBufSize - 1 do
|
||||||
|
|||||||
@@ -258,12 +258,16 @@ end;
|
|||||||
|
|
||||||
### 4.7 Усиление / телеметрия
|
### 4.7 Усиление / телеметрия
|
||||||
|
|
||||||
- RX gain: контрол manual gain (0–73 дБ) / режим hw-AGC при `Caps.HasHWGain`
|
- ✅ RX gain: блок `RF AGC` (десктоп) — кнопки FAST/SLOW/HYB/MAN (4 режима AD9361,
|
||||||
(можно расширить UI step-atten). WDSP AGC поверх.
|
подтверждены на железе `gain_control_mode_available`) + слайдер manual 0–73 дБ при
|
||||||
|
`Caps.HasHWGain`; персист per-device. WDSP AGC поверх. TODO: проброс RF-AGC в web
|
||||||
|
(сейчас в web только отображение, без управления).
|
||||||
- Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
|
- Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
|
||||||
`not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) →
|
`not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) →
|
||||||
`ServicePlutoTelemetry` (контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3
|
`ServicePlutoTelemetry` (контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3
|
||||||
статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA).
|
статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA). ✅ **Две температуры**:
|
||||||
|
трансивер AD9361 (`ad9361-phy temp0`) + Zynq SoC/FPGA (`xadc temp0`,
|
||||||
|
`(raw+offset)*scale/1000`) — формат `Temp 37/53C` в десктопе и web.
|
||||||
|
|
||||||
### 4.8 TX-мощность / drive (Pluto)
|
### 4.8 TX-мощность / drive (Pluto)
|
||||||
|
|
||||||
@@ -303,7 +307,8 @@ end;
|
|||||||
|
|
||||||
**Фаза 3 — Управление/калибровка**
|
**Фаза 3 — Управление/калибровка**
|
||||||
- ✅ RX gain / hw-AGC, транспорт (USB/IP), persist per-device по synthetic-MAC.
|
- ✅ RX gain / hw-AGC, транспорт (USB/IP), persist per-device по synthetic-MAC.
|
||||||
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия (температура/RSSI) в статус-бар.
|
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия в статус-бар: RSSI + **две
|
||||||
|
температуры** (трансивер AD9361 + Zynq SoC `xadc`, формат `Temp 37/53C`).
|
||||||
- TODO: XO ppm-калибровка, buffer/timeout-контролы.
|
- TODO: XO ppm-калибровка, buffer/timeout-контролы.
|
||||||
|
|
||||||
**Фаза 4 — TX-тракт Pluto** ✅
|
**Фаза 4 — TX-тракт Pluto** ✅
|
||||||
@@ -329,11 +334,25 @@ end;
|
|||||||
- ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset /
|
- ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset /
|
||||||
границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот
|
границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот
|
||||||
(`QO100_SLOT`), не засоряет таблицу трансвертеров openHPSDR.
|
(`QO100_SLOT`), не засоряет таблицу трансвертеров openHPSDR.
|
||||||
- TODO: проверка на железе.
|
- ✅ **TX↔RX связка с фиксированным офсетом**: TX-VFO в downlink-домене,
|
||||||
|
`XvtrTranslateTX` = `visible − TxLOOffset` (≈8089.5 МГц) — крутишь приём, аплинк
|
||||||
|
едет следом, офсет держится сам (через общий VFO). Split — через VFO-B.
|
||||||
|
- ✅ **Аудио self-monitor** («слышу себя со спутника»): кнопка `RX MUTE` в TX-панели
|
||||||
|
рядом с DUP — видна только Pluto + full-duplex (QO-100). По умолчанию ON (RX-аудио
|
||||||
|
глушится на TX, как раньше); OFF → RX-аудио идёт на колонки и во время TX (свой
|
||||||
|
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
|
||||||
|
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
|
||||||
|
персист в глобале (`rx_mute_on_tx`). Web — не сделано (пока вне области).
|
||||||
|
- TODO: проверка на железе; RIT/XIT-клариффайер (опц., на геостационаре редко нужен);
|
||||||
|
TX-маркер на RX-спектре в DUP (визуальный self-monitor — `FSpecView.TXMode` сейчас
|
||||||
|
в DUP выключен).
|
||||||
|
|
||||||
**Фаза 6 — Полировка**
|
**Фаза 6 — Полировка** ✅ (в основном)
|
||||||
- Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
||||||
(discovery/connect Pluto headless); кросс-платформенная сборка с/без libiio; доки.
|
(discovery/connect Pluto headless, beacon-lock @10 Гц); кросс-платформенная сборка
|
||||||
|
с/без libiio.
|
||||||
|
- TODO: проброс в web того, чего там ещё нет — RF-AGC (gain/режимы) и QO-100
|
||||||
|
`RX MUTE` (self-monitor). Доки актуализируются по ходу.
|
||||||
|
|
||||||
**Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):**
|
**Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):**
|
||||||
- ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-
|
- ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-
|
||||||
|
|||||||
Reference in New Issue
Block a user