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
+3
View File
@@ -879,6 +879,9 @@ begin
FWebServer.OnXvtrBand := FWebAdapter.OnXvtrBand;
FWebServer.OnBeacon := FWebAdapter.OnBeacon;
FWebServer.OnBeaconSeed := FWebAdapter.OnBeaconSeed;
FWebServer.OnRxGainMode := FWebAdapter.OnRxGainMode;
FWebServer.OnRxGain := FWebAdapter.OnRxGain;
FWebServer.OnRxMute := FWebAdapter.OnRxMute;
FWebServer.OnConnect := WebOnConnect;
FWebServer.OnDiscoverDev := WebOnDiscover;
FWebServer.OnDisconnectDev := WebOnDisconnect;
+41
View File
@@ -68,6 +68,9 @@ type
procedure SyncFMStep;
procedure SyncBeacon;
procedure SyncBeaconSeed;
procedure SyncRxGainMode;
procedure SyncRxGain;
procedure SyncRxMute;
function BeaconStatusText: string; // компактный статус (как десктоп поле 7)
public
constructor Create(AController: TRadioController; AServer: TWebServer;
@@ -103,6 +106,9 @@ type
// QO-100 beacon: вкл/выкл лок и наведение по клику в спектре (frac 0..1).
procedure OnBeacon(On_: Boolean);
procedure OnBeaconSeed(Frac: Double);
procedure OnRxGainMode(Mode: Integer);
procedure OnRxGain(Db: Integer);
procedure OnRxMute(On_: Boolean);
procedure OnClientActiveChanged(Active: Boolean);
// TX-mic от web-клиента → WDSP. Вызывается из WS-потока; без маршалинга —
// подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
@@ -373,6 +379,33 @@ begin
FController.BeaconSeedAtHz(Hz);
end;
procedure TWebAdapter.OnRxGainMode(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncRxGainMode); end;
procedure TWebAdapter.SyncRxGainMode;
begin
// Pluto hw-AGC: 0=manual,1=fast,2=slow,3=hybrid (рендер через rfRxGain).
FController.SetRxGainMode(FSyncInt);
end;
procedure TWebAdapter.OnRxGain(Db: Integer);
begin FSyncInt := Db; FController.Invoke(@SyncRxGain); end;
procedure TWebAdapter.SyncRxGain;
begin
// Ручной hw-gain (0..73 дБ); SetRxGain переводит режим в manual.
FController.SetRxGain(FSyncInt);
end;
procedure TWebAdapter.OnRxMute(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncRxMute); end;
procedure TWebAdapter.SyncRxMute;
begin
// QO-100 self-monitor: RX-аудио на TX (toggle, рендер через rfRxMuteOnTx).
FController.SetRxMuteOnTx(FSyncBool);
end;
procedure TWebAdapter.OnClientActiveChanged(Active: Boolean);
// Из потока веб-сервера при connect/disconnect клиента. Простая запись флага —
// ядро (SetMOX, в т.ч. HWPTT-путь) выбирает по нему mic-source. Без маршалинга.
@@ -512,6 +545,14 @@ begin
FController.BeaconRefHz,
FController.BeaconTrackedFreqHz);
// Pluto RF-AGC (виден при HasHWGain = Pluto подключён) + QO-100 RX MUTE (InQO100).
FServer.SetRxGainStatus(
FController.BackendCaps.HasHWGain,
FController.FRxGainMode, FController.FRxGainDb);
FServer.SetRxMuteStatus(
FController.InQO100,
FController.FRxMuteOnTx);
FServer.PushSpectrum(
FController.FSpectrumBuf, 1024,
FController.FWaterfallBuf,
+14 -1
View File
@@ -204,6 +204,13 @@ begin
'<button id="anfBtn">ANF</button>' +
'<button id="nrBtn">NR</button>' +
'<button id="bcnBtn" title="QO-100 beacon lock" style="display:none">BCN</button>' +
'<button id="rxMuteBtn" title="QO-100 RX self-monitor mute on TX" style="display:none">RX MUTE</button>' +
// Pluto RF-AGC (hw gain): режим FAST/SLOW/HYB/MAN + ручной gain (активен в MAN). Виден только на Pluto.
'<select id="rfagcSel" title="Pluto RF AGC mode" style="display:none">' +
'<option value="1">RFagc-F</option><option value="2" selected>RFagc-S</option>' +
'<option value="3">RFagc-H</option><option value="0">RFagc-M</option>' +
'</select>' +
'<div class="sl-g" id="rfgainG" style="display:none"><span class="sl-lb">GAIN</span><input type="range" id="rfgainSl" min="0" max="73" value="40"><span class="sl-val" id="rfgainVal">40dB</span></div>' +
'<span class="sl-sep">|</span>' +
'<button id="devBtn" title="Devices">DEV</button>' +
'<button id="startStopBtn" class="on">START</button>' +
@@ -290,6 +297,8 @@ begin
'function updSnbBtn(){sw("snbBtn",!!cur.snb);}' +
'function updAnfBtn(){sw("anfBtn",!!cur.anf);}' +
'function updBcnBtn(){const b=q("bcnBtn");if(!b)return;b.style.display=cur.beacon_visible?"":"none";sw("bcnBtn",!!cur.beacon_on);}' +
'function updRxMute(){const b=q("rxMuteBtn");if(!b)return;b.style.display=cur.rx_mute_visible?"":"none";sw("rxMuteBtn",!!cur.rx_mute_on);}' +
'function updRfAgc(){const vis=!!cur.rf_agc_visible;const s=q("rfagcSel"),g=q("rfgainG");if(s)s.style.display=vis?"":"none";if(g)g.style.display=vis?"":"none";if(!vis)return;const m=cur.rx_gain_mode|0;if(s&&!isPending("rxgain"))s.value=String(m);const sl=q("rfgainSl"),l=q("rfgainVal");if(sl&&!sl.matches(":active")){sl.value=cur.rx_gain_db|0;sl.disabled=(m!==0);}if(l)l.textContent=(cur.rx_gain_db|0)+"dB";}' +
'const MODE_N=["LSB","USB","DSB","CWL","CWU","FM","AM","SAM"];' +
@@ -447,6 +456,9 @@ begin
'q("nrBtn").onclick=function(){cur.nr_mode=((cur.nr_mode|0)+1)%5;cur.nr=cur.nr_mode!==0;updNrBtn();pend("nr");ws2({cmd:"set_nr_mode",mode:cur.nr_mode});};' +
'q("nbBtn").onclick=function(){cur.nb_mode=((cur.nb_mode|0)+1)%3;cur.nb=cur.nb_mode!==0;updNbBtn();pend("nb");ws2({cmd:"set_nb_mode",mode:cur.nb_mode});};' +
'q("bcnBtn").onclick=function(){cur.beacon_on=!cur.beacon_on;beaconArm=cur.beacon_on;updBcnBtn();pend("beacon");cmd("set_beacon",cur.beacon_on);};' +
'q("rxMuteBtn").onclick=function(){cur.rx_mute_on=!cur.rx_mute_on;updRxMute();pend("rxmute");cmd("set_rx_mute",cur.rx_mute_on);};' +
'q("rfagcSel").onchange=function(e){const m=+e.target.value;cur.rx_gain_mode=m;pend("rxgain");updRfAgc();ws2({cmd:"set_rx_gain_mode",mode:m});};' +
'q("rfgainSl").oninput=function(e){const v=+e.target.value;cur.rx_gain_db=v;cur.rx_gain_mode=0;const l=q("rfgainVal");if(l)l.textContent=v+"dB";const s=q("rfagcSel");if(s)s.value="0";pend("rxgain");ws2({cmd:"set_rx_gain",db:v});};' +
'q("snbBtn").onclick=function(){cur.snb=!cur.snb;updSnbBtn();pend("snb");cmd("set_snb",cur.snb);};' +
'q("anfBtn").onclick=function(){cur.anf=!cur.anf;updAnfBtn();pend("anf");cmd("set_anf",cur.anf);};' +
@@ -681,7 +693,7 @@ begin
'function isPending(field){return !!(pendingCmds[field]&&pendingCmds[field]>Date.now());}' +
// Маппинг: server JSON field → pend key
'var PEND_MAP={vfo_a_hz:"freq",vfo_b_hz:"freq",nr:"nr",nr_mode:"nr",nb:"nb",nb_mode:"nb",snb:"snb",anf:"anf",running:"run",mode:"mode",agc_mode:"agc",transmitting:"mox",drive:"drive",attn_idx:"attn",tuning:"tun",filter_bw:"filter",mute:"mute",beacon_on:"beacon"};' +
'var PEND_MAP={vfo_a_hz:"freq",vfo_b_hz:"freq",nr:"nr",nr_mode:"nr",nb:"nb",nb_mode:"nb",snb:"snb",anf:"anf",running:"run",mode:"mode",agc_mode:"agc",transmitting:"mox",drive:"drive",attn_idx:"attn",tuning:"tun",filter_bw:"filter",mute:"mute",beacon_on:"beacon",rx_mute_on:"rxmute",rx_gain_mode:"rxgain",rx_gain_db:"rxgain"};' +
'function applyState(msg){' +
'Object.keys(msg).forEach(function(k){var pk=PEND_MAP[k];if(!pk||!isPending(pk))cur[k]=msg[k];});' +
@@ -728,6 +740,7 @@ begin
'if(!isPending("nr"))updNrBtn();if(!isPending("nb"))updNbBtn();' +
'if(!isPending("snb"))updSnbBtn();if(!isPending("anf"))updAnfBtn();' +
'if(!isPending("beacon"))updBcnBtn();' +
'if(!isPending("rxmute"))updRxMute();updRfAgc();' +
'if(!isPending("run"))updRunBtn(!!cur.running);' +
'if(!isPending("mox"))updMoxBtn(!!cur.transmitting);' +
'if(!isPending("tun"))updToneBtn(!!cur.tuning);' +
+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;
+25 -10
View File
@@ -260,8 +260,12 @@ end;
- ✅ 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 только отображение, без управления).
`Caps.HasHWGain`; персист per-device. WDSP AGC поверх.
- ✅ **RF-AGC в web** (управление): дропдаун режима RFagc-F/S/H/M + слайдер GAIN
0–73 дБ (активен только в MAN), виден при `BackendCaps.HasHWGain` (Pluto подключён).
Команды `set_rx_gain_mode`/`set_rx_gain``SetRxGainMode`/`SetRxGain`; state-поля
`rf_agc_visible`/`rx_gain_mode`/`rx_gain_db`. GUI-web-сервер + headless-демон. Собрано,
live-проверка на железе за пользователем.
- Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
`not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) →
`ServicePlutoTelemetry` (контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3
@@ -309,7 +313,12 @@ end;
- ✅ RX gain / hw-AGC, транспорт (USB/IP), persist per-device по synthetic-MAC.
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия в статус-бар: RSSI + **две
температуры** (трансивер AD9361 + Zynq SoC `xadc`, формат `Temp 37/53C`).
- TODO: XO ppm-калибровка, buffer/timeout-контролы.
- TODO: **XO ppm-калибровка** — корень частотной ошибки опорника Pluto. Сейчас
обходится вручную через `TxLOOffset` QO-100-слота (подстройка под фактический
uplink, см. Фаза 5). Правильный фикс — `xo_correction` AD9361 (правит RX **и** TX
в ppm на всех частотах разом) или внешний 40 МГц GPSDO-реф (золотой стандарт для
QO-100). На 2.4 ГГц 1 ppm ≈ 2.4 кГц, поэтому TX без GPSDO плывёт с прогревом.
- TODO: buffer/timeout-контролы.
**Фаза 4 — TX-тракт Pluto** ✅
- ✅ `SendDUCIQ` 24→16 + апсемпл 192k→device-rate + пейсинг (FIFO + push-поток, §4.6).
@@ -319,9 +328,10 @@ end;
- ✅ Управление мощностью через TX-аттенюацию (ADI-модель), маппинг **power-linear**
+ полная цифра WDSP (§4.8). Без PA → нет drive-байта/SWR/Alex/keyer.
- ✅ **TX LO powerdown по PTT** (§4.8) — нет утечки несущей TX в RX-спектре в простое.
- TODO: проверка на железе на rate ≥1536k (ориентация SSB по Q-инверсии, уровни);
опц. headroom 6 дБ для FM (full-scale const-envelope → нелинейность ЦАП); опц.
зануление IQ на приёме. Опц. поддержка F5OEO-прошивки (PTT внешнего PA через
- **Проверено на железе** (передача QO-100 через спутник, downlink виден в
full-duplex self-monitor; SSB-ориентация и уровни в норме).
- TODO: опц. headroom 6 дБ для FM (full-scale const-envelope → нелинейность ЦАП);
опц. зануление IQ на приёме. Опц. поддержка F5OEO-прошивки (PTT внешнего PA через
`hardwaregain=40`) — отложено, нужно только под конкретное железо.
**Фаза 5 — Режим QO-100** ✅
@@ -342,7 +352,9 @@ end;
глушится на TX, как раньше); OFF → RX-аудио идёт на колонки и во время TX (свой
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
персист в глобале (`rx_mute_on_tx`). Web — не сделано (пока вне области).
персист в глобале (`rx_mute_on_tx`). **В web**: toggle-кнопка `RX MUTE` рядом с
BCN, видна по `InQO100`, подсветка по `rx_mute_on`; команда `set_rx_mute`
`SetRxMuteOnTx`. GUI-web-сервер + headless-демон.
- ✅ **Bandplan-оверлей NB-транспондера** (`BandPlanOverlay.pas`): полоска внизу
спектра (над ruler) с сегментами CW ONLY / NB DIGI / DIGI / SSB / MIXED MODES +
маяки CW/PSK/EXP. Модель `VfoOverlay` (кэш-битмап + const-alpha), CPU и GL, виден
@@ -351,7 +363,10 @@ end;
* **Индикатор «не громче маяка»** — сравнение уровня своего downlink с маяком
(этикет QO-100); амплитуда маяка уже известна декодеру.
* RIT/XIT-клариффайер — опц., на геостационаре нужен редко.
- TODO: проверка на железе.
- **Проверено на железе** (QSO через QO-100: TX uplink 2400 + RX downlink 739.5 МГц,
full-duplex self-monitor). Нюанс калибровки: `TxLOOffset` пришлось подстроить руками
под фактический uplink — это ошибка опорника Pluto (TCXO), а не геометрия (геом.
offset точно 8089.5 МГц). Постоянное решение — XO ppm / GPSDO, см. Фаза 3 TODO.
- ОТВЕРГНУТО: TX-маркер на RX-спектре в DUP — в обычной работе RX=TX (один VFO),
маркер ложится на RX-курсор, информации не даёт; полезен только в split (редко).
@@ -359,8 +374,8 @@ end;
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
(discovery/connect Pluto headless, beacon-lock @10 Гц); кросс-платформенная сборка
с/без libiio.
- TODO: проброс в web того, чего там ещё нет — RF-AGC (gain/режимы) и QO-100
`RX MUTE` (self-monitor). Доки актуализируются по ходу.
- ✅ Проброс в web RF-AGC (gain/режимы) и QO-100 `RX MUTE` (self-monitor) — см.
§4.7 / Фаза 5. Доки актуализируются по ходу.
**Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):**
- ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-
+3 -2
View File
@@ -17,9 +17,9 @@
<UseVersionInfo Value="True"/>
<AutoIncrementBuild Value="True"/>
<MinorVersionNr Value="9"/>
<BuildNr Value="214"/>
<BuildNr Value="215"/>
</VersionInfo>
<MacroValues Count="26">
<MacroValues Count="27">
<Macro1 Name="LCLWidgetType" Value="qt6"/>
<Macro2 Name="LCLWidgetType" Value="qt6"/>
<Macro3 Name="LCLWidgetType" Value="qt6"/>
@@ -46,6 +46,7 @@
<Macro24 Name="LCLWidgetType" Value="qt6"/>
<Macro25 Name="LCLWidgetType" Value="qt6"/>
<Macro26 Name="LCLWidgetType" Value="qt6"/>
<Macro27 Name="LCLWidgetType" Value="qt6"/>
</MacroValues>
<BuildModes>
<Item Name="Debug" Default="True"/>
+2
View File
@@ -168,6 +168,8 @@ begin
FWeb.OnMOX := @FWebAd.OnMOX; FWeb.OnTun := @FWebAd.OnTun;
FWeb.OnFMStep := @FWebAd.OnFMStep; FWeb.OnXvtrBand := @FWebAd.OnXvtrBand;
FWeb.OnBeacon := @FWebAd.OnBeacon; FWeb.OnBeaconSeed := @FWebAd.OnBeaconSeed;
FWeb.OnRxGainMode := @FWebAd.OnRxGainMode;
FWeb.OnRxGain := @FWebAd.OnRxGain; FWeb.OnRxMute := @FWebAd.OnRxMute;
FWeb.OnWebMic := @FWebAd.OnMic;
FWeb.OnClientActiveChanged := @FWebAd.OnClientActiveChanged;
// Host-coupled (lifecycle) — на демон-хост (маршалит в главный поток).