feat: web RF-AGC + QO-100 RX MUTE controls (Pluto)

Прокинул в web-интерфейс управление, которого там не хватало (раньше
только отображение):

- RF-AGC (Pluto hw-gain): дропдаун режима FAST/SLOW/HYB/MAN + слайдер
  GAIN 0–73 дБ (активен только в MAN, драг переводит в manual). Виден
  по BackendCaps.HasHWGain (= Pluto подключён). Команды set_rx_gain_mode
  / set_rx_gain → SetRxGainMode / SetRxGain.
- RX MUTE (QO-100 self-monitor): toggle-кнопка рядом с BCN, видна по
  InQO100, подсветка по состоянию. Команда set_rx_mute → SetRxMuteOnTx.

Гейтинг зеркалит десктоп. Работает в GUI-web-сервере и headless-демоне.
Проверено на железе пользователем.

WebServer: команды + state-поля (rf_agc_visible/rx_gain_mode/rx_gain_db,
rx_mute_visible/rx_mute_on) + SetRxGainStatus/SetRxMuteStatus.
WebAdapter: колбэки OnRxGain*/OnRxMute (Invoke на поток контроллера) +
публикация в PushState. MainForm + ewsdrd.lpr: привязка колбэков.
WebPageHtml: виджеты, JS-обработчики, updRfAgc/updRxMute, PEND_MAP.

Также актуализирован doc/PLUTO_INTEGRATION_PLAN.md: TX-тракт и режим
QO-100 помечены проверенными на железе; раскрыт TODO «XO ppm-калибровка»
(ручной TxLOOffset как текущий обходной путь, xo_correction/GPSDO как
постоянное решение).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:02:34 +03:00
co-authored by Claude Opus 4.8
parent 8bb8714a25
commit a885488243
7 changed files with 163 additions and 15 deletions
+75 -2
View File
@@ -154,6 +154,10 @@ type
// QO-100 beacon lock: вкл/выкл лок и наведение (клик по маяку, доля 0..1 по ширине).
TWebCmdBeacon = procedure(On_: Boolean) of object;
TWebCmdBeaconSeed = procedure(Frac: Double) of object;
// Pluto RF-AGC (hw gain): режим FAST/SLOW/HYB/MAN + ручной gain дБ. RX MUTE — QO-100 self-monitor.
TWebCmdRxGainMode = procedure(Mode: Integer) of object;
TWebCmdRxGain = procedure(Db: Integer) of object;
TWebCmdRxMute = procedure(On_: Boolean) of object;
// Активность web-клиента изменилась (подключился/отключился последний клиент).
// Хозяин зеркалит это в ядро (FController.FWebClientActive) для выбора mic-source.
TWebClientActiveEvent = procedure(Active: Boolean) of object;
@@ -330,6 +334,18 @@ type
FOnBeacon: TWebCmdBeacon;
FOnBeaconSeed: TWebCmdBeaconSeed;
// Pluto RF-AGC (hw gain) + QO-100 RX MUTE. Зеркалится в state как
// rf_agc_visible/rx_gain_mode/rx_gain_db и rx_mute_visible/rx_mute_on;
// команды set_rx_gain_mode/set_rx_gain/set_rx_mute.
FRfAgcVisible: Boolean; // RF-AGC доступен (Pluto подключён, HasHWGain)
FRxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid
FRxGainDb: Integer; // ручной gain дБ (0..73)
FRxMuteVisible: Boolean; // RX MUTE доступна (Pluto + QO-100)
FRxMuteOn: Boolean; // self-monitor mute активен
FOnRxGainMode: TWebCmdRxGainMode;
FOnRxGain: TWebCmdRxGain;
FOnRxMute: TWebCmdRxMute;
FWebClientActive: Boolean;
FOnClientActiveChanged: TWebClientActiveEvent;
@@ -416,8 +432,14 @@ type
// QO-100 beacon lock: зеркало статуса в state (зовётся из PushState хозяина).
procedure SetBeaconStatus(AVisible, AOn: Boolean; const AText: string;
ARefHz, ATrackHz: Double);
// Pluto RF-AGC + QO-100 RX MUTE: зеркало в state (зовётся из PushState хозяина).
procedure SetRxGainStatus(AVisible: Boolean; AMode, ADb: Integer);
procedure SetRxMuteStatus(AVisible, AOn: Boolean);
property OnBeacon: TWebCmdBeacon read FOnBeacon write FOnBeacon;
property OnBeaconSeed: TWebCmdBeaconSeed read FOnBeaconSeed write FOnBeaconSeed;
property OnRxGainMode: TWebCmdRxGainMode read FOnRxGainMode write FOnRxGainMode;
property OnRxGain: TWebCmdRxGain read FOnRxGain write FOnRxGain;
property OnRxMute: TWebCmdRxMute read FOnRxMute write FOnRxMute;
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
@@ -565,6 +587,11 @@ begin
FBeaconText := '';
FBeaconRefHz := 0;
FBeaconTrackHz := 0;
FRfAgcVisible := False;
FRxGainMode := 2; // slow_attack по умолчанию (как контроллер)
FRxGainDb := 40;
FRxMuteVisible := False;
FRxMuteOn := False;
SetLength(FXvtrBands, 0);
// Bootstrap-набор рейтов (до connect): хост перезапишет под бэкенд (HPSDR/Pluto).
SetRatePresets([48000, 96000, 192000, 384000, 768000, 1536000]);
@@ -604,6 +631,29 @@ begin
end;
end;
procedure TWebServer.SetRxGainStatus(AVisible: Boolean; AMode, ADb: Integer);
begin
FStateLock.Enter;
try
FRfAgcVisible := AVisible;
FRxGainMode := AMode;
FRxGainDb := ADb;
finally
FStateLock.Leave;
end;
end;
procedure TWebServer.SetRxMuteStatus(AVisible, AOn: Boolean);
begin
FStateLock.Enter;
try
FRxMuteVisible := AVisible;
FRxMuteOn := AOn;
finally
FStateLock.Leave;
end;
end;
procedure TWebServer.SetDeviceList(const ADevices: TWebDeviceArray);
var i: Integer;
begin
@@ -1496,6 +1546,24 @@ begin
HzF := JsonGetFloat(Json, 'frac', -1);
if (HzF >= 0) and Assigned(FOnBeaconSeed) then FOnBeaconSeed(HzF);
end
else if Cmd = 'set_rx_gain_mode' then
begin
Idx := JsonGetInt(Json, 'mode', FRxGainMode); // 0=man,1=fast,2=slow,3=hybrid
FStateLock.Enter; FRxGainMode := Idx; FStateLock.Leave;
if Assigned(FOnRxGainMode) then FOnRxGainMode(Idx);
end
else if Cmd = 'set_rx_gain' then
begin
DB := JsonGetInt(Json, 'db', FRxGainDb); // ручной gain 0..73 дБ
FStateLock.Enter; FRxGainDb := DB; FStateLock.Leave;
if Assigned(FOnRxGain) then FOnRxGain(DB);
end
else if Cmd = 'set_rx_mute' then
begin
On_ := JsonGetBool(Json, 'on', FRxMuteOn);
FStateLock.Enter; FRxMuteOn := On_; FStateLock.Leave;
if Assigned(FOnRxMute) then FOnRxMute(On_);
end
else if Cmd = 'freq_a' then
begin
HzF := JsonGetFloat(Json, 'hz', FFreq);
@@ -1718,7 +1786,9 @@ begin
'"fmstep_idx":%d,"devices":%s,"rate_presets":%s,"bands":%s,' +
'"freq_min":%.0f,"freq_max":%.0f,' +
'"beacon_visible":%s,"beacon_on":%s,"beacon_text":"%s",' +
'"beacon_ref_hz":%.0f,"beacon_track_hz":%.0f}',
'"beacon_ref_hz":%.0f,"beacon_track_hz":%.0f,' +
'"rf_agc_visible":%s,"rx_gain_mode":%d,"rx_gain_db":%d,' +
'"rx_mute_visible":%s,"rx_mute_on":%s}',
[FFreq, FVfoB, FActiveVfo,
FMode, MODE_N[FMode mod 8],
FFilterIdx, FFilterBW,
@@ -1751,7 +1821,10 @@ begin
FFreqMin, FFreqMax,
BoolToStr(FBeaconVisible, 'true', 'false'),
BoolToStr(FBeaconOn, 'true', 'false'),
JsonEscape(FBeaconText), FBeaconRefHz, FBeaconTrackHz
JsonEscape(FBeaconText), FBeaconRefHz, FBeaconTrackHz,
BoolToStr(FRfAgcVisible, 'true', 'false'), FRxGainMode, FRxGainDb,
BoolToStr(FRxMuteVisible, 'true', 'false'),
BoolToStr(FRxMuteOn, 'true', 'false')
], FS);
finally
FStateLock.Leave;