mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat: QO-100 beacon lock — stabilise LNB drift to NB transponder beacon
Click the beacon on the spectrum; the controller tracks it and continuously trims the transverter LO so the whole downlink stays put as the LNB drifts. - BeaconLock.pas: TBeaconTracker scaffold (narrowband NCO/FLL) — kept but no longer in the control path (FLL diverged on the suppressed-carrier BPSK beacon + Pluto DC offset). Measurement now uses the proven display-FFT path. - RadioController: FBeaconLockHz folded into XvtrTranslate (on top of LOError). MeasureBeaconFFT finds the beacon by POWER-WEIGHTED CENTROID of the lobe (BPSK has a suppressed carrier → broad symmetric ~2.4 kHz lobe, not a line; a peak-finder jitters, the centroid sits on the carrier). Track position in ABSOLUTE display Hz (FBeaconTrackHz) so scrolling the waterfall/centre never moves the search window off the beacon. Closed loop: e = tracked - ref, d(e)/d(LockHz) = -1 -> LockHz += gain*e; settle cooldown after each correction (display-FFT lags the LO retune -> avoids overshoot); deadband 75 Hz + centroid/prominence smoothing + lock hysteresis for a steady lock. Correction applied only after the user clicks (manual seed), so a crowded band can't pull a neighbour to the reference. - WDSPEngine: SetBeaconTracker + RX-IQ tap (tracker idle now, ~1 check/sample). - SpectrumView + GL: beacon markers — green = reference (10489.750), orange = tracked centroid. - MainForm: BEACON button (RX block, XVTR-only). Flow: BEACON -> "CLICK BCN" -> click beacon -> "L<corr> /<prom>". ServiceBeaconLock driven by MeterTimer. Reference defaults to the middle BPSK beacon 10489.750. Tested on-air against Es'hail-2: locks and holds, survives waterfall drag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+86
-1
@@ -43,7 +43,7 @@ uses
|
||||
PowerInhibit,
|
||||
DeviceStore,
|
||||
RadioBackend, PlutoBackend,
|
||||
RadioController, ChannelController;
|
||||
RadioController, ChannelController, BeaconLock;
|
||||
|
||||
const
|
||||
CLR_BG = TColor($00101010);
|
||||
@@ -134,6 +134,7 @@ type
|
||||
FSpecDrag: Boolean;
|
||||
FSpecDragX0: Integer; // X при нажатии
|
||||
FSpecDragFreq: Double; // FCenterFreq при нажатии
|
||||
FBeaconArming: Boolean; // ждём клик по маяку (после включения BEACON)
|
||||
FSpectrumDirty: Boolean; // таймер должен перерисовать спектр/водопад
|
||||
// TX DUC-очередь (FDUCPending*) переехала в TRadioController (OnTXIQ).
|
||||
FSMeterPeak: Double; // верхняя граница светлой зоны
|
||||
@@ -237,6 +238,7 @@ type
|
||||
PanelFilter: TPanel;
|
||||
BtnFilter: array[0..FILT_COUNT-1] of TFlatButton;
|
||||
BtnCTun: TFlatButton;
|
||||
BtnBeacon: TFlatButton; // QO-100 beacon lock (видим только в XVTR-режиме)
|
||||
BtnDUP: TFlatButton;
|
||||
PanelFMSQ: TPanel;
|
||||
BtnFMSQ: TFlatButton;
|
||||
@@ -377,6 +379,8 @@ type
|
||||
procedure BtnModeClick(Sender: TObject);
|
||||
procedure BtnFilterClick(Sender: TObject);
|
||||
procedure BtnCTunClick(Sender: TObject);
|
||||
procedure BtnBeaconClick(Sender: TObject);
|
||||
procedure UpdateBeaconButton;
|
||||
procedure BtnDUPClick(Sender: TObject);
|
||||
procedure ApplyDUP(Active: Boolean);
|
||||
procedure BtnTUNClick(Sender: TObject);
|
||||
@@ -1190,6 +1194,12 @@ begin
|
||||
if BtnXvtrBand[i] <> nil then
|
||||
StyleButton(BtnXvtrBand[i], i = FController.FCurrentXvtr);
|
||||
UpdateFreqDisplayMax; // трансвертер может быть >6 ГГц (QO-100)
|
||||
// BEACON-кнопка актуальна только в трансвертер-режиме (QO-100 lock).
|
||||
if BtnBeacon <> nil then
|
||||
begin
|
||||
BtnBeacon.Visible := FController.FCurrentXvtr >= 0;
|
||||
UpdateBeaconButton;
|
||||
end;
|
||||
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
||||
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
||||
UpdateWidebandFrequencyView;
|
||||
@@ -1252,6 +1262,8 @@ begin
|
||||
FSpecView.DrawSpectrum;
|
||||
PbSpectrum.Invalidate;
|
||||
end;
|
||||
rfBeaconLock:
|
||||
UpdateBeaconButton; // статус/коррекция QO-100 beacon lock
|
||||
rfCTun:
|
||||
begin
|
||||
StyleButton(BtnCTun, FController.FCTun);
|
||||
@@ -1694,6 +1706,15 @@ begin
|
||||
FChannelsDropDown.OnSelect := OnChannelsDropDownSelect;
|
||||
RefreshChannelsDropDown;
|
||||
|
||||
// QO-100 beacon lock — 3-я колонка ряда. Видима только в XVTR-режиме
|
||||
// (см. рендер rfXvtr ниже). Подпись/подсветка обновляются по rfBeaconLock.
|
||||
BtnBeacon := MakeBtn(PanelRXBlock, 'BEACON',
|
||||
LeftPanelButtonLeft(LEFT_W, 3, 2), 16,
|
||||
LeftPanelButtonWidth(LEFT_W, 3, 2),
|
||||
BTN_H, BtnBeaconClick);
|
||||
BtnBeacon.Visible := False;
|
||||
StyleButton(BtnBeacon, False);
|
||||
|
||||
MakeLbl(PanelRXBlock, 'VOL', 4, 50);
|
||||
TrkVolume := TFlatSlider.Create(Self);
|
||||
TrkVolume.Parent := PanelRXBlock; TrkVolume.Left := 34; TrkVolume.Top := 46;
|
||||
@@ -2946,6 +2967,8 @@ begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// QO-100 beacon lock: одна итерация контура (no-op если лок выключен).
|
||||
FController.ServiceBeaconLock;
|
||||
|
||||
// Сглаженное среднее (EMA)
|
||||
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA;
|
||||
@@ -3864,6 +3887,59 @@ begin
|
||||
FController.SetCTun(not FController.FCTun);
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnBeaconClick(Sender: TObject);
|
||||
begin
|
||||
FController.SetBeaconLock(not FController.BeaconLockEnabled);
|
||||
// При включении — «вооружаем» клик: следующий ЛКМ по спектру укажет маяк
|
||||
// (надёжнее автопоиска в забитой полосе). Авто-сид работает до клика.
|
||||
FBeaconArming := FController.BeaconLockEnabled;
|
||||
UpdateBeaconButton;
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateBeaconButton;
|
||||
// Подпись + подсветка кнопки BEACON по состоянию лока. Зовётся из BtnBeaconClick
|
||||
// и из рендера rfBeaconLock (контур обновляет статус каждый тик).
|
||||
var
|
||||
cap: string;
|
||||
corr: Double;
|
||||
begin
|
||||
if BtnBeacon = nil then Exit;
|
||||
// Маркеры на спектре: опорная частота (зелёная) + позиция трекера (оранж).
|
||||
if FSpecView <> nil then
|
||||
FSpecView.SetBeaconMarkers(FController.BeaconLockEnabled,
|
||||
FController.BeaconRefHz,
|
||||
FController.BeaconTrackedFreqHz);
|
||||
if not FController.BeaconLockEnabled then
|
||||
begin
|
||||
BtnBeacon.Caption := 'BEACON';
|
||||
StyleButton(BtnBeacon, False);
|
||||
end
|
||||
else if FBeaconArming then
|
||||
begin
|
||||
BtnBeacon.Caption := 'CLICK BCN'; // ждём клик пользователя по маяку
|
||||
StyleButton(BtnBeacon, True);
|
||||
end
|
||||
else
|
||||
begin
|
||||
case FController.BeaconState of
|
||||
bsLock:
|
||||
begin
|
||||
corr := FController.BeaconCorrectionHz;
|
||||
// LOCK + коррекция LO (Гц) + SNR — видно запас.
|
||||
cap := Format('L%.0f /%.0f', [corr, FController.BeaconSNRdB]);
|
||||
StyleButton(BtnBeacon, True);
|
||||
end;
|
||||
bsSearch:
|
||||
// Показываем SNR при поиске — диагностика: растёт → маяк ловится.
|
||||
begin cap := Format('BCN ?%.0f', [FController.BeaconSNRdB]); StyleButton(BtnBeacon, True); end;
|
||||
else
|
||||
begin cap := 'BCN --'; StyleButton(BtnBeacon, False); end;
|
||||
end;
|
||||
BtnBeacon.Caption := cap;
|
||||
end;
|
||||
BtnBeacon.Repaint;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DUP toggle (Thetis-style local display mode — без аппаратной команды)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -4057,6 +4133,15 @@ begin
|
||||
|
||||
if Button = mbLeft then
|
||||
begin
|
||||
// Наведение beacon-lock: ЛКМ указывает на маяк (вместо перестройки VFO).
|
||||
if FBeaconArming and (PbSpectrum.Width > 0) then
|
||||
begin
|
||||
FController.BeaconSeedAtHz(
|
||||
PixelToFreq(X, PbSpectrum.Width, FController.FCenterFreq, FController.FSpanHz));
|
||||
FBeaconArming := False;
|
||||
UpdateBeaconButton;
|
||||
Exit;
|
||||
end;
|
||||
// Левая кнопка: сбрасываем маркер если активен
|
||||
if FSpecView.MarkerActive then
|
||||
begin
|
||||
|
||||
Reference in New Issue
Block a user