diff --git a/MainForm.pas b/MainForm.pas index 48dbaec..867c731 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -237,6 +237,7 @@ type BtnCTun: TFlatButton; BtnBeacon: TFlatButton; // QO-100 beacon lock (видим только на Pluto в трансвертере) BtnDUP: TFlatButton; + BtnRxMute: TFlatButton; // QO-100 self-monitor: RX-аудио на TX (видим только Pluto+full-duplex) PanelFMSQ: TPanel; BtnFMSQ: TFlatButton; TrkFMSQ: TFlatSlider; @@ -387,6 +388,8 @@ type procedure UpdateBeaconButton; procedure BtnDUPClick(Sender: TObject); procedure ApplyDUP(Active: Boolean); + procedure BtnRxMuteClick(Sender: TObject); + procedure UpdateRxMuteButton; // видимость (Pluto+full-duplex) + стиль кнопки RX MUTE procedure BtnTUNClick(Sender: TObject); procedure ApplyTUN(Active: Boolean); procedure UpdateFilterButtons; @@ -1212,9 +1215,12 @@ begin // openHPSDR смысла не имеет (нет дрейфующего LNB → прячем). if BtnBeacon <> nil then begin - BtnBeacon.Visible := FController.IsPluto and (FController.FCurrentXvtr >= 0); + // Только Pluto + full-duplex транспондер (QO-100) — beacon-lock завязан + // на дрейф LNB, в обычных диапазонах/трансвертерах смысла нет. + BtnBeacon.Visible := FController.InQO100; UpdateBeaconButton; end; + UpdateRxMuteButton; // RX MUTE — только Pluto + full-duplex (QO-100) // Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate). // rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное. UpdateWidebandFrequencyView; @@ -1313,6 +1319,8 @@ begin PbWaterfall.Invalidate; end; end; + rfRxMuteOnTx: + UpdateRxMuteButton; // QO-100 self-monitor toggle: подсветка кнопки RX MUTE rfWfAGC: FSpecView.WfAGCEnabled := FController.FWfAGCEnabled; rfWfNF: @@ -1707,6 +1715,15 @@ begin BtnDUP.Tag := 0; 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.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77; LblDrv.Caption := 'DRV'; LblDrv.Font.Color := CLR_TEXTDIM; @@ -3697,6 +3714,7 @@ begin // Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже. LayoutLeftPanel(Caps.HasHWGain); if Caps.HasHWGain then UpdateRxGainUI; + UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен) end; procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice); @@ -4032,6 +4050,22 @@ begin ApplyDUP(not FController.FDisplayDuplex); 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 // --------------------------------------------------------------------------- diff --git a/RadioController.pas b/RadioController.pas index dd318ad..72a6ee0 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -67,7 +67,7 @@ type rfVolume, rfMute, rfDrive, rfAtten, rfBand, rfXvtr, rfBandRestore, // rfBandRestore: явная смена диапазона (сброс Wf-avg в UI) rfCenterFreq, rfSpan, rfSampleRate, - rfCTun, rfDuplex, + rfCTun, rfDuplex, rfRxMuteOnTx, // rfRxMuteOnTx: QO-100 self-monitor toggle rfRunning, rfConnected, rfTransmitting, rfTuning, rfNR, rfNB, rfSNB, rfANF, rfWfAGC, rfWfNF, rfDither, rfRandom, @@ -217,6 +217,9 @@ type FTuning: Boolean; FSplitTxB: Boolean; FDisplayDuplex: Boolean; + // QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.), + // False — слышим свой downlink со спутника при передаче. Кнопка RX MUTE. + FRxMuteOnTx: Boolean; FHWPTTActive: Boolean; FHWPTTStartedTX: Boolean; FWebClientActive: Boolean; // выставляет web-слой (connect/disconnect клиента) @@ -496,6 +499,8 @@ type // Отображение / приём procedure SetCTun(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 SetWfNF(On_: Boolean); @@ -552,6 +557,8 @@ type function BandPlan: TBandPlanArray; // True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF). function IsPluto: Boolean; + // True — активен QO-100 (Pluto + full-duplex XVTR-слот); гейт UI beacon/RX MUTE. + function InQO100: Boolean; // Человекочитаемое имя подключённой платы для статус-строки (UI/Web). // HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть). function BoardDisplayName: string; @@ -593,6 +600,7 @@ begin FMode := 1; FFilter := 5; FFilterBW := 2700; FAGCMode := 1; FAGCTop := 90; FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию + FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX) FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0; FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000; FCurrentBand := 5; FCurrentXvtr := -1; @@ -1552,6 +1560,7 @@ begin Result.WidebandFill := FWidebandFill; Result.SpectrumFill := FSpectrumFill; Result.DisplayDuplex := FDisplayDuplex; + Result.RxMuteOnTx := FRxMuteOnTx; Result.AudioOutDevice := FAudioOutDevName; Result.AudioInDevice := FAudioInDevName; Result.SendAudioToRadio := FSendAudioToRadio; @@ -2371,6 +2380,21 @@ begin Changed(rfDuplex); 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); begin FWfAGCEnabled := On_; Changed(rfWfAGC); end; @@ -2783,6 +2807,8 @@ begin FNetwork.SetSpeakerAudio(FSendAudioToRadio); // RX hw-gain в бэкенд (Pluto). HPSDR — no-op. ApplyRxGainToBackend; + // QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено. + ApplyRxMuteToEngine; SendDUCSpecificFromSettings; // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; @@ -2949,6 +2975,7 @@ begin FWidebandFill := FLoadedGlobal.WidebandFill; FSpectrumFill := FLoadedGlobal.SpectrumFill; FDisplayDuplex := FLoadedGlobal.DisplayDuplex; + FRxMuteOnTx := FLoadedGlobal.RxMuteOnTx; // Настройки дисплея/сетки + DSP display + аудио-устройства if FLoadedGlobal.FFTSize > 0 then begin @@ -3177,6 +3204,15 @@ begin Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto); 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; var Dev: THPSDRDevice; diff --git a/Settings.pas b/Settings.pas index 2418d1d..afdd832 100644 --- a/Settings.pas +++ b/Settings.pas @@ -261,6 +261,7 @@ type WidebandFill: Boolean; // True = fill wideband spectrum with gradient SpectrumFill: Boolean; // True = fill main spectrum under the curve with gradient DisplayDuplex: Boolean; // DUP: при TX показывать RX-водопад, TX-фильтр overlay'ем + RxMuteOnTx: Boolean; // QO-100 self-monitor: True=RX-аудио глушится на TX (умолч.) // --- Display FPS --- DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100) // --- Theme --- @@ -448,6 +449,7 @@ begin G.WidebandFill := False; G.SpectrumFill := True; G.DisplayDuplex := False; + G.RxMuteOnTx := True; G.DisplayFPS := 60; G.FreqMhzDigits := 3; // CAT defaults @@ -671,6 +673,7 @@ begin G.WidebandFill := JB(GObj,'wideband_fill',False); G.SpectrumFill := JB(GObj,'spectrum_fill',True); G.DisplayDuplex := JB(GObj,'display_duplex',False); + G.RxMuteOnTx := JB(GObj,'rx_mute_on_tx',True); G.DisplayFPS := JI(GObj,'display_fps',60); G.LightTheme := JB(GObj,'light_theme',False); G.FreqMhzDigits := EnsureRange(JI(GObj,'freq_mhz_digits',3), 3, 5); @@ -774,6 +777,7 @@ begin JW(O,'wideband_fill',G.WidebandFill); JW(O,'spectrum_fill',G.SpectrumFill); JW(O,'display_duplex',G.DisplayDuplex); + JW(O,'rx_mute_on_tx',G.RxMuteOnTx); JW(O,'display_fps',G.DisplayFPS); JW(O,'light_theme',G.LightTheme); JW(O,'freq_mhz_digits',G.FreqMhzDigits); diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 92ffcf9..1a0b188 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -223,6 +223,10 @@ type // (мы выяснили на практике что стоп/старт RXA добавляет задержки на // Windows без видимого выигрыша). FKeepRXDuringTX: Boolean; + // Полный дуплекс QO-100: пропускать RX-аудио на колонки и во время TX (слышим + // свой downlink через транспондер). Имеет смысл только при FKeepRXDuringTX. + // По умолчанию False — обычное поведение (RX-аудио глушится на TX). + FMonitorRXDuringTX: Boolean; // Дедлайн (GetTickCount64), до которого RX-аудио не пишется в FOnAudio. // Используется на Windows для маскировки «хвоста» TX leakage (radio // FPGA TX-buffer + PA slew-down → RX1 IQ → audio) после MOX-off. @@ -461,6 +465,10 @@ type // RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun. property KeepRXDuringTX: Boolean read 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). // SpectrumView/Waterfall должны читать пиксели именно из этого ID. property ActiveDisplayID: Integer read FActiveDisplayID; @@ -788,6 +796,7 @@ begin FTXMicTail := 0; FTXActive := False; FKeepRXDuringTX := False; + FMonitorRXDuringTX := False; FPostTXMuteUntil := 0; FActiveDisplayID := RX_DISP_ID; FTXMicSource := txmsRadio; @@ -1302,7 +1311,11 @@ begin // через unsigned-сравнение, GetTickCount64 не зовётся когда дедлайн = 0. if (FPostTXMuteUntil <> 0) and (GetTickCount64 >= FPostTXMuteUntil) then 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 begin for i := 0 to FAudioBufSize - 1 do diff --git a/doc/PLUTO_INTEGRATION_PLAN.md b/doc/PLUTO_INTEGRATION_PLAN.md index 1b5c8f9..c5dd129 100644 --- a/doc/PLUTO_INTEGRATION_PLAN.md +++ b/doc/PLUTO_INTEGRATION_PLAN.md @@ -258,12 +258,16 @@ end; ### 4.7 Усиление / телеметрия -- RX gain: контрол manual gain (0–73 дБ) / режим hw-AGC при `Caps.HasHWGain` - (можно расширить UI step-atten). WDSP AGC поверх. +- ✅ RX gain: блок `RF AGC` (десктоп) — кнопки FAST/SLOW/HYB/MAN (4 режима AD9361, + подтверждены на железе `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 — скрыть при `not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) → `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) @@ -303,7 +307,8 @@ end; **Фаза 3 — Управление/калибровка** - ✅ 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-контролы. **Фаза 4 — TX-тракт Pluto** ✅ @@ -329,11 +334,25 @@ end; - ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset / границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот (`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 — Полировка** -- Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr` - (discovery/connect Pluto headless); кросс-платформенная сборка с/без libiio; доки. +**Фаза 6 — Полировка** ✅ (в основном) +- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr` + (discovery/connect Pluto headless, beacon-lock @10 Гц); кросс-платформенная сборка + с/без libiio. +- TODO: проброс в web того, чего там ещё нет — RF-AGC (gain/режимы) и QO-100 + `RX MUTE` (self-monitor). Доки актуализируются по ходу. **Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):** - ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-