mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat: QO-100 beacon lock in web UI + headless daemon
Mirror the desktop QO-100 beacon lock into the web interface and make it work in the headless daemon. Backend: - WebServer: set_beacon/beacon_seed commands (+OnBeacon/OnBeaconSeed events), beacon state fields, SetBeaconStatus, and beacon_visible/on/ text/ref_hz/track_hz in BuildStateJson. beacon_seed carries 'frac' (0..1 click position); absolute Hz is computed controller-side from live FCenterFreq/FSpanHz (mirrors desktop PixelToFreq). - WebAdapter: OnBeacon->SetBeaconLock, OnBeaconSeed->BeaconSeedAtHz; PushState mirrors compact status (matches MainForm.BeaconStatusText) into the PLL status cell when visible (Pluto + active XVTR). - MainForm/ewsdrd: wire the two callbacks. - ewsdrd: call ServiceBeaconLock in the main loop @~10Hz (FRunning+ FWDSPReady gated). Without this the lock loop never ran headless. Frontend (WebPageHtml): - BCN button (visible only on Pluto+XVTR), highlighted while locked. - Seed-on-mousedown with immediate return (like desktop FormMouseDown): no drag/set_center so the gesture never moves the center / tears IQ. Armed (first click after BCN) or Shift, mirroring desktop arm/Shift. - PLL status cell shows beacon status (field-7 parity with desktop). - Ref (green) + tracked (orange) beacon markers on spectrum/waterfall. Known issue (unresolved): after lock the beacon can slowly drift off and drop to "BCN sync". Suspected residual-sign anti-drift or retune-timing on the web/daemon path; needs on-air diagnostics. See memory project_web_beacon for the investigation state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+59
-2
@@ -151,6 +151,9 @@ type
|
||||
// XVTR-band: web клиент кликнул кнопку трансвертера (Idx 0..CFG_XVTR_COUNT-1).
|
||||
// Idx=-1 — выход в HF.
|
||||
TWebCmdXvtrBand = procedure(Idx: Integer) of object;
|
||||
// QO-100 beacon lock: вкл/выкл лок и наведение (клик по маяку, доля 0..1 по ширине).
|
||||
TWebCmdBeacon = procedure(On_: Boolean) of object;
|
||||
TWebCmdBeaconSeed = procedure(Frac: Double) of object;
|
||||
// Активность web-клиента изменилась (подключился/отключился последний клиент).
|
||||
// Хозяин зеркалит это в ядро (FController.FWebClientActive) для выбора mic-source.
|
||||
TWebClientActiveEvent = procedure(Active: Boolean) of object;
|
||||
@@ -317,6 +320,16 @@ type
|
||||
FTXText: string;
|
||||
FSeqText: string;
|
||||
|
||||
// QO-100 beacon lock (Pluto в трансвертере). Зеркалится в state как
|
||||
// beacon_visible/on/text/ref_hz/track_hz; команды set_beacon/beacon_seed.
|
||||
FBeaconVisible: Boolean; // кнопка/статус активны (Pluto + XVTR)
|
||||
FBeaconOn: Boolean; // лок включён (подсветка кнопки)
|
||||
FBeaconText: string; // компактный статус (ячейка PLL, как десктоп)
|
||||
FBeaconRefHz: Double; // опорная частота маяка (маркер)
|
||||
FBeaconTrackHz: Double; // измеренная частота маяка (маркер)
|
||||
FOnBeacon: TWebCmdBeacon;
|
||||
FOnBeaconSeed: TWebCmdBeaconSeed;
|
||||
|
||||
FWebClientActive: Boolean;
|
||||
FOnClientActiveChanged: TWebClientActiveEvent;
|
||||
|
||||
@@ -400,6 +413,11 @@ type
|
||||
procedure SetRatePresets(const ARates: array of Integer);
|
||||
procedure SetBands(const ANames: array of string; const AFreqs: array of Double);
|
||||
procedure SetFreqRange(AMin, AMax: Double);
|
||||
// QO-100 beacon lock: зеркало статуса в state (зовётся из PushState хозяина).
|
||||
procedure SetBeaconStatus(AVisible, AOn: Boolean; const AText: string;
|
||||
ARefHz, ATrackHz: Double);
|
||||
property OnBeacon: TWebCmdBeacon read FOnBeacon write FOnBeacon;
|
||||
property OnBeaconSeed: TWebCmdBeaconSeed read FOnBeaconSeed write FOnBeaconSeed;
|
||||
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
|
||||
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
|
||||
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
|
||||
@@ -542,6 +560,11 @@ begin
|
||||
FBandIdx := 5;
|
||||
FCurrentXvtr := -1;
|
||||
FFreqMhzDigits := 3;
|
||||
FBeaconVisible := False;
|
||||
FBeaconOn := False;
|
||||
FBeaconText := '';
|
||||
FBeaconRefHz := 0;
|
||||
FBeaconTrackHz := 0;
|
||||
SetLength(FXvtrBands, 0);
|
||||
// Bootstrap-набор рейтов (до connect): хост перезапишет под бэкенд (HPSDR/Pluto).
|
||||
SetRatePresets([48000, 96000, 192000, 384000, 768000, 1536000]);
|
||||
@@ -566,6 +589,21 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWebServer.SetBeaconStatus(AVisible, AOn: Boolean; const AText: string;
|
||||
ARefHz, ATrackHz: Double);
|
||||
begin
|
||||
FStateLock.Enter;
|
||||
try
|
||||
FBeaconVisible := AVisible;
|
||||
FBeaconOn := AOn;
|
||||
FBeaconText := AText;
|
||||
FBeaconRefHz := ARefHz;
|
||||
FBeaconTrackHz := ATrackHz;
|
||||
finally
|
||||
FStateLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWebServer.SetDeviceList(const ADevices: TWebDeviceArray);
|
||||
var i: Integer;
|
||||
begin
|
||||
@@ -1444,6 +1482,20 @@ begin
|
||||
FStateLock.Enter; FTuning := On_; FStateLock.Leave;
|
||||
if Assigned(FOnTun) then FOnTun(On_);
|
||||
end
|
||||
else if Cmd = 'set_beacon' then
|
||||
begin
|
||||
On_ := JsonGetBool(Json, 'on', FBeaconOn);
|
||||
FStateLock.Enter; FBeaconOn := On_; FStateLock.Leave;
|
||||
if Assigned(FOnBeacon) then FOnBeacon(On_);
|
||||
end
|
||||
else if Cmd = 'beacon_seed' then
|
||||
begin
|
||||
// frac = доля позиции клика по ширине спектра (0..1). Абсолютную display-частоту
|
||||
// считает контроллер из ЖИВЫХ FCenterFreq/FSpanHz (как десктоп PixelToFreq) —
|
||||
// клиентский localCenter может отставать при ретюне LO во время лока.
|
||||
HzF := JsonGetFloat(Json, 'frac', -1);
|
||||
if (HzF >= 0) and Assigned(FOnBeaconSeed) then FOnBeaconSeed(HzF);
|
||||
end
|
||||
else if Cmd = 'freq_a' then
|
||||
begin
|
||||
HzF := JsonGetFloat(Json, 'hz', FFreq);
|
||||
@@ -1664,7 +1716,9 @@ begin
|
||||
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
|
||||
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' +
|
||||
'"fmstep_idx":%d,"devices":%s,"rate_presets":%s,"bands":%s,' +
|
||||
'"freq_min":%.0f,"freq_max":%.0f}',
|
||||
'"freq_min":%.0f,"freq_max":%.0f,' +
|
||||
'"beacon_visible":%s,"beacon_on":%s,"beacon_text":"%s",' +
|
||||
'"beacon_ref_hz":%.0f,"beacon_track_hz":%.0f}',
|
||||
[FFreq, FVfoB, FActiveVfo,
|
||||
FMode, MODE_N[FMode mod 8],
|
||||
FFilterIdx, FFilterBW,
|
||||
@@ -1694,7 +1748,10 @@ begin
|
||||
JsonEscape(FTXText), JsonEscape(FSeqText),
|
||||
FCurrentXvtr, XvtrJson, FFreqMhzDigits,
|
||||
FFMStepIdx, DevJson, RateJson, BandJson,
|
||||
FFreqMin, FFreqMax
|
||||
FFreqMin, FFreqMax,
|
||||
BoolToStr(FBeaconVisible, 'true', 'false'),
|
||||
BoolToStr(FBeaconOn, 'true', 'false'),
|
||||
JsonEscape(FBeaconText), FBeaconRefHz, FBeaconTrackHz
|
||||
], FS);
|
||||
finally
|
||||
FStateLock.Leave;
|
||||
|
||||
Reference in New Issue
Block a user