mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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-
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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) — на демон-хост (маршалит в главный поток).
|
||||
|
||||
Reference in New Issue
Block a user