feat: QO-100 central beacon BPSK decoder (Milestone 1 — stable demod)

Декодер центрального маяка QO-100 (10489.750, AO-40 BPSK 400 бод): фронтенд
до мягких символов + констелляция, с устойчивым захватом несущей/символа.

Цепочка (BeaconDecoder.pas): NCO-снос смещения → boxcar-децим до 9600 →
RRC матч-фильтр → AGC-нормировка → Gardner timing → Costas BPSK с frequency
offload в пред-RRC NCO. squaring-FFT оставлена индикатором несущей (prom).

Ключевые решения по итогам эфирной отладки:
- invert=True для тракта QO-100 (LNB+трансвертер зеркалит спектр).
- Декодер развязан от beacon-lock (тот ретюнил железо, рвал IQ).
- AGC-нормировка петель: сигнал ~-60 dBFS замораживал Costas/Gardner.
- Frequency offload: накопл. снос Costas стекает в FResidInc (без потолка
  ±π/символ) → держит ошибку наведения (1пкс≈300Гц) + дрейф LNB.

Интеграция: тап IQ в WDSPEngine.PushIQItemToDSP; RadioController владеет
декодером, клик-наведение (ПКМ BEACON + ЛКМ/Shift+ЛКМ по спектру); узкий
фильтр-маркер в SpectrumView/GL; окно констелляции BeaconScopeForm; файловый
лог HOME/ewsdr_beacon.log для диагностики.

Milestone 2 (далее): FEC — Viterbi k=7 + RS(160,128) + интерливер + дескремблер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 18:15:00 +03:00
co-authored by Claude Opus 4.8
parent 28adf9508a
commit c063d7a197
8 changed files with 1134 additions and 11 deletions
+49 -1
View File
@@ -39,7 +39,7 @@ uses
WinFirewall,
BoardUtils, WisdomBuilder, UISync,
FlatEdit, FMRepeater,
ChannelStore, ChannelsForm,
ChannelStore, ChannelsForm, BeaconScopeForm,
PowerInhibit,
DeviceStore,
RadioBackend, PlutoBackend,
@@ -135,6 +135,7 @@ type
FSpecDragX0: Integer; // X при нажатии
FSpecDragFreq: Double; // FCenterFreq при нажатии
FBeaconArming: Boolean; // ждём клик по маяку (после включения BEACON)
FBeaconDecArming: Boolean; // ждём клик: навести узкий фильтр декодера на маяк
FSpectrumDirty: Boolean; // таймер должен перерисовать спектр/водопад
// TX DUC-очередь (FDUCPending*) переехала в TRadioController (OnTXIQ).
FSMeterPeak: Double; // верхняя граница светлой зоны
@@ -287,6 +288,7 @@ type
BtnChannels: TFlatButton;
FChannelsDropDown: TFlatDropDown;
FChannelsForm: TObject; // TChannelsForm (cast при использовании)
FBeaconScopeForm: TBeaconScopeForm; // окно констелляции маяка (ПКМ по BEACON)
// ---- Right panel ----
PanelRight: TPanel;
@@ -384,6 +386,8 @@ type
procedure BtnFilterClick(Sender: TObject);
procedure BtnCTunClick(Sender: TObject);
procedure BtnBeaconClick(Sender: TObject);
procedure BtnBeaconMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure UpdateBeaconButton;
procedure BtnDUPClick(Sender: TObject);
procedure ApplyDUP(Active: Boolean);
@@ -795,6 +799,7 @@ begin
FController.FTXSpecGridStep := 10.0;
FSettingsForm := nil;
FChannelsForm := nil;
FBeaconScopeForm := nil;
FillChar(FController.FDevMAC, SizeOf(FController.FDevMAC), 0);
// Инициализируем кэш диапазонов умолчаниями
@@ -1752,6 +1757,7 @@ begin
LeftPanelButtonWidth(LEFT_W, 3, 2),
BTN_H, BtnBeaconClick);
BtnBeacon.Visible := False;
BtnBeacon.OnMouseDown := BtnBeaconMouseDown; // ПКМ → декодер + окно констелляции
StyleButton(BtnBeacon, False);
MakeLbl(PanelRXBlock, 'VOL', 4, 50);
@@ -3272,6 +3278,9 @@ function TMainForm.BeaconStatusText: string;
// Компактный статус QO-100 beacon lock для поля 7 статус-бара (Pluto). Courier 8pt,
// поле ~200px → держим ≤ ~24 символов.
begin
if FBeaconDecArming then Exit('DEC: click beacon');
if FController.BeaconDecodeEnabled and (FController.BeaconDecodeFreqHz > 0) then
Exit('DEC: shift-click=re-aim');
if not FController.BeaconLockEnabled then Exit('BCN: off');
if FBeaconArming then Exit('BCN: click beacon');
case FController.BeaconState of
@@ -3988,6 +3997,28 @@ begin
UpdateBeaconButton;
end;
procedure TMainForm.BtnBeaconMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
// ПКМ по BEACON: включает фронтенд-декодер маяка (BPSK 400 бод) и открывает окно
// констелляции для диагностики демодуляции (Milestone 1 — до подключения FEC).
begin
if Button <> mbRight then Exit;
FController.SetBeaconDecode(not FController.BeaconDecodeEnabled);
if FController.BeaconDecodeEnabled then
begin
// «Вооружаем» наведение: следующий ЛКМ по спектру поставит узкий фильтр
// декодера на маяк. Потом перенавести — Shift+ЛКМ по спектру.
FBeaconDecArming := True;
if FBeaconScopeForm = nil then
FBeaconScopeForm := TBeaconScopeForm.CreateWith(Self, FController);
FBeaconScopeForm.ApplyTheme(DarkTheme);
FBeaconScopeForm.Show;
end
else
FBeaconDecArming := False;
UpdateBeaconButton;
end;
procedure TMainForm.UpdateBeaconButton;
// Beacon-маркеры + кнопка BEACON. Кнопка видима только на Pluto в трансвертере
// (см. rfXvtr) — это ПРОСТО кнопка: подпись 'BEACON', подсветка = лок включён.
@@ -3996,9 +4027,15 @@ procedure TMainForm.UpdateBeaconButton;
begin
// Маркеры на спектре: опорная частота (зелёная) + отслеживаемый центроид (оранж).
if FSpecView <> nil then
begin
FSpecView.SetBeaconMarkers(FController.BeaconLockEnabled,
FController.BeaconRefHz,
FController.BeaconTrackedFreqHz);
// Узкий фильтр-маркер декодера (±450 Гц ≈ полоса BPSK-маяка).
FSpecView.SetBeaconDecMarker(
FController.BeaconDecodeEnabled and (FController.BeaconDecodeFreqHz > 0),
FController.BeaconDecodeFreqHz, 450.0);
end;
UpdateStatusField7; // моментальный апдейт статус-бара (не ждём MeterTimer)
if BtnBeacon = nil then Exit;
BtnBeacon.Caption := 'BEACON';
@@ -4186,6 +4223,17 @@ begin
if Button = mbLeft then
begin
// Наведение узкого фильтра ДЕКОДЕРА на маяк: после ПКМ BEACON (вооружено)
// либо Shift+ЛКМ в любой момент при включённом декодере. Не трогает VFO.
if FController.BeaconDecodeEnabled and (PbSpectrum.Width > 0) and
(FBeaconDecArming or (ssShift in Shift)) then
begin
FController.SetBeaconDecodeAtHz(
PixelToFreq(X, PbSpectrum.Width, FController.FCenterFreq, FController.FSpanHz));
FBeaconDecArming := False;
UpdateBeaconButton;
Exit;
end;
// Наведение beacon-lock: ЛКМ указывает на маяк (вместо перестройки VFO).
if FBeaconArming and (PbSpectrum.Width > 0) then
begin