feature(rx): плавный аттенюатор 0..31 дБ в UI, память на диапазон, компенсация уровней

Аттенюатор был только в web и только тремя ступенями (0/-10/-20), а его дБ
никуда не возвращались — при ATT 20 дБ S-метр и спектр уезжали на 20 дБ, и
свежая калибровка уровней была верна лишь при ATT=0.

- RadioController: SetAtten(Db) 0..31 (как умеет железо) вместо индекса *10.
  AttenCalDB входит в SMeterCalFor и DispCalFor — стрелка, спектр, водопад и
  паны компенсируются, шкала dBm остаётся абсолютной (как Thetis/piHPSDR).
  У AD936x аттенюатора нет, там поправка 0 (усиление тракта — hardwaregain).
- Память на диапазон/слот трансвертера по образцу RxGainMode/RxGainDb:
  TBandSettings.AttenDB (band 'atten_db') и TXvtrEntry.LastAttenDB
  ('last_atten_db'); восстановление и отправка в железо — в ApplyBandDSP,
  сбор — в MakeBandSettings/SaveCurrentBand, вход в слот — через B.AttenDB.
  SetAtten сразу кладёт значение в кэш диапазона/слота (JSON пишется общим
  сохранением, а не на каждый шаг ползунка — как у drive).
- MainForm: ряд «ATT [ползунок] NNdB» в RX-блоке левой панели, отступы как у
  VOL; на Pluto ряд скрыт и RX-блок ужимается (LayoutLeftPanel двигает
  панели ниже). Внешние изменения приходят по rfAtten.
- Web: select → ползунок 0..31 с цифрой, attn_idx -> attn_db, cmd attn {db:}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 11:41:21 +03:00
co-authored by Claude Opus 4.8
parent 366d4c9821
commit 6de8c00689
5 changed files with 127 additions and 22 deletions
+68 -2
View File
@@ -347,6 +347,11 @@ type
FPSPopup: TObject; // TPureSignalPopup (cast при использовании)
PanelRXBlock: TPanel;
// Шаговый аттенюатор ADC (openHPSDR): плавные 0..31 дБ. На Pluto ряд
// скрыт — там усиление тракта живёт в блоке RF AGC.
LblAtt: TLabel;
LblAttVal: TLabel;
TrkAtt: TFlatSlider;
BtnChannels: TFlatButton;
FChannelsDropDown: TFlatDropDown;
FChannelsForm: TObject; // TChannelsForm (cast при использовании)
@@ -706,6 +711,8 @@ type
procedure BtnAGCModeClick(Sender: TObject);
procedure TrkAGCChange(Sender: TObject);
procedure TrkRxGainChange(Sender: TObject);
procedure TrkAttChange(Sender: TObject);
procedure UpdateAttLabel;
// (BtnPlutoGainClick объявлен выше)
procedure MeterTimerTick(Sender: TObject);
procedure SpectrumTimerTick(Sender: TObject);
@@ -762,6 +769,13 @@ uses SettingsForm;
{$R *.lfm}
const
// Высота RX-блока левой панели: с рядом ATT (openHPSDR) и без него (Pluto —
// там усиление тракта в блоке RF AGC). Переключает LayoutLeftPanel, он же
// двигает панели ниже.
RX_BLOCK_H = 96;
RX_BLOCK_H_ATT = 120;
function TMainForm.LeftPanelButtonWidth(PanelWidth, ColCount,
ColIndex: Integer): Integer;
var
@@ -1505,6 +1519,12 @@ begin
TrkVolume.Position := FController.ActiveVolume; // RX или TX-монитор
rfRxGain:
UpdateRxGainUI;
rfAtten:
if TrkAtt <> nil then
begin
TrkAtt.Position := FController.FAtten;
UpdateAttLabel;
end;
rfDrive:
begin
TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive%
@@ -2028,7 +2048,7 @@ begin
// RX block: CTUN + VOL + NR/NB/SNB/ANF/MUTE
PanelRXBlock := TPanel.Create(Self);
PanelRXBlock.Parent := PanelLeft;
PanelRXBlock.SetBounds(0, Y, LEFT_W, 96);
PanelRXBlock.SetBounds(0, Y, LEFT_W, RX_BLOCK_H_ATT);
PanelRXBlock.BevelOuter := bvNone;
MakeLbl(PanelRXBlock, 'RX', 4, 2);
@@ -2084,7 +2104,29 @@ begin
LeftPanelButtonLeft(LEFT_W, 5, 4), 70,
LeftPanelButtonWidth(LEFT_W, 5, 4), BTN_SM, BtnMuteClick);
Inc(Y, 100);
// Шаговый аттенюатор ADC — плавный 0..31 дБ (железо openHPSDR так и умеет,
// ступени 0/10/20 были только в web-UI). Показания S-метра/спектра на эти
// дБ компенсируются в контроллере, шкала dBm остаётся абсолютной.
LblAtt := TLabel.Create(Self);
LblAtt.Parent := PanelRXBlock; LblAtt.Left := 4; LblAtt.Top := 106;
LblAtt.Caption := 'ATT'; LblAtt.Font.Color := CLR_TEXTDIM;
LblAtt.Font.Name := 'Courier New'; LblAtt.Font.Size := 7;
TrkAtt := TFlatSlider.Create(Self);
TrkAtt.Parent := PanelRXBlock; TrkAtt.Left := 34; TrkAtt.Top := 102;
TrkAtt.Width := LEFT_W - 76; TrkAtt.Height := 16;
TrkAtt.Min := 0; TrkAtt.Max := 31; // ADC step attenuator, дБ
TrkAtt.Position := FController.FAtten;
TrkAtt.OnChange := TrkAttChange;
LblAttVal := TLabel.Create(Self);
LblAttVal.Parent := PanelRXBlock;
LblAttVal.Left := LEFT_W - 38; LblAttVal.Top := 106;
LblAttVal.Font.Color := CLR_TEXT; LblAttVal.Font.Name := 'Courier New';
LblAttVal.Font.Size := 7;
UpdateAttLabel;
Inc(Y, RX_BLOCK_H_ATT + 4);
// AGC controls
PanelAGC := TPanel.Create(Self);
@@ -7933,6 +7975,19 @@ begin
FController.SetRxGain(TrkRxGain.Position); // переводит в manual
end;
procedure TMainForm.TrkAttChange(Sender: TObject);
begin
if FSyncingFromController then Exit;
FController.SetAtten(TrkAtt.Position);
UpdateAttLabel;
end;
procedure TMainForm.UpdateAttLabel;
begin
if LblAttVal <> nil then
LblAttVal.Caption := Format('%ddB', [FController.FAtten]);
end;
procedure TMainForm.BtnPlutoGainClick(Sender: TObject);
begin
if FSyncingFromController then Exit;
@@ -7966,6 +8021,17 @@ procedure TMainForm.LayoutLeftPanel(Pluto: Boolean);
var baseTop: Integer;
begin
if (PanelPlutoAGC = nil) or (PanelAGC = nil) or (PanelBands = nil) then Exit;
// Шаговый аттенюатор — только у openHPSDR; у AD936x усиление тракта в блоке
// RF AGC ниже, поэтому ряд ATT убираем и RX-блок ужимаем.
if (TrkAtt <> nil) and (LblAtt <> nil) and (LblAttVal <> nil) then
begin
TrkAtt.Visible := not Pluto;
LblAtt.Visible := not Pluto;
LblAttVal.Visible := not Pluto;
if Pluto then PanelRXBlock.Height := RX_BLOCK_H
else PanelRXBlock.Height := RX_BLOCK_H_ATT;
PanelAGC.Top := PanelRXBlock.Top + PanelRXBlock.Height + 4;
end;
baseTop := PanelAGC.Top + PanelAGC.Height + 4;
PanelPlutoAGC.Top := baseTop;
PanelPlutoAGC.Visible := Pluto;
+35 -7
View File
@@ -601,7 +601,7 @@ type
procedure ToggleMute;
procedure SetDrive(V: Integer);
procedure DriveBy(Delta: Integer);
procedure SetAtten(Idx: Integer);
procedure SetAtten(Db: Integer); // ADC step attenuator, дБ (0..31)
// ---- Мультислайсы (софт-fan-out) ----
// Создаёт слайс на TargetHz: заводит WDSP-канал в движке + свой аудио-выход
@@ -635,6 +635,8 @@ type
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
// Колбэк движка (display-поток), см. TOnDispCalQuery.
function DispCalFor(PanId: Integer): Double;
// Поправка на шаговый аттенюатор ADC (дБ) — входит в оба офсета выше.
function AttenCalDB: Double;
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
function SliceCount: Integer;
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
@@ -2094,7 +2096,17 @@ end;
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
begin
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB);
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB)
+ AttenCalDB;
end;
function TRadioController.AttenCalDB: Double;
// Шаговый аттенюатор ADC режет вход — чтобы шкала dBm оставалась абсолютной,
// его дБ возвращаются в показания (так же делают Thetis и piHPSDR). У AD936x
// такого аттенюатора нет: там усиление тракта — hardwaregain.
begin
if IsPluto then Result := 0.0
else Result := FAtten;
end;
function TRadioController.DispCalFor(PanId: Integer): Double;
@@ -2105,7 +2117,7 @@ begin
if (PanId > 0) and (PanId < MAX_PANS) then Hz := FPanFreqHz[PanId]
else Hz := FCenterFreq;
if Hz <= 0 then Exit(0.0);
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB);
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + AttenCalDB;
end;
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
@@ -2808,6 +2820,7 @@ begin
// Аппаратный AGC AD936x — на диапазон (усиление тракта разное на 2м и QO-100).
Result.RxGainMode := FRxGainMode;
Result.RxGainDb := FRxGainDb;
Result.AttenDB := FAtten; // шаговый аттенюатор ADC — тоже на диапазон
// Waterfall AGC/NF — глобальные (не диапазонные)
end;
@@ -2846,6 +2859,7 @@ begin
FXvtrSettings.Entries[i].LastPanSlider := FPanSlider;
FXvtrSettings.Entries[i].LastRxGainMode := FRxGainMode;
FXvtrSettings.Entries[i].LastRxGainDb := FRxGainDb;
FXvtrSettings.Entries[i].LastAttenDB := FAtten;
end;
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
Exit;
@@ -2975,6 +2989,11 @@ begin
ApplyRxGainToBackend;
Changed(rfRxGain); // UI: кнопки FAST/SLOW/HYB/MAN + ползунок
// --- Шаговый аттенюатор ADC (openHPSDR) — на диапазон/слот трансвертера.
FAtten := EnsureRange(B.AttenDB, 0, 31);
if Assigned(FNetwork) then FNetwork.SetStepAtten(Byte(FAtten));
Changed(rfAtten);
// --- FM squelch / CTCSS / step / repeater ---
FFMSQOn := B.FMSQOn;
FFMSQLevel := B.FMSQLevel;
@@ -3646,12 +3665,20 @@ begin
else FDSPEngine.SetDriveLevel(EnsureRange(Pct, 0, 100) / 100.0);
end;
procedure TRadioController.SetAtten(Idx: Integer);
procedure TRadioController.SetAtten(Db: Integer);
// Шаговый аттенюатор ADC — плавный 0..31 дБ (как в железе openHPSDR), а не
// три фиксированные ступени. Уровни S-метра/спектра компенсируются на ту же
// величину (CalLookup), поэтому шкала dBm остаётся абсолютной.
begin
if Idx < 0 then Idx := 0 else if Idx > 2 then Idx := 2;
FAtten := Idx;
if Assigned(FNetwork) then FNetwork.SetStepAtten(FAtten * 10);
FAtten := EnsureRange(Db, 0, 31);
if Assigned(FNetwork) then FNetwork.SetStepAtten(Byte(FAtten));
Changed(rfAtten);
// Персист — вместе с диапазоном/слотом (SaveCurrentBand на выходе из них),
// писать JSON на каждый шаг ползунка незачем, как и у drive.
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastAttenDB := FAtten
else if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
FBandCache[FCurrentBand].AttenDB := FAtten;
end;
procedure TRadioController.SetBand(Idx: Integer);
@@ -3742,6 +3769,7 @@ begin
B.FMRptOffsetHz := E.LastFMRptOffsetHz;
B.RxGainMode := E.LastRxGainMode; // hw-AGC слота (AD936x)
B.RxGainDb := E.LastRxGainDb;
B.AttenDB := E.LastAttenDB; // аттенюатор ADC слота
B.VfoA := Vis;
B.VfoB := FVfoB;
ApplyBandDSP(B);
+11
View File
@@ -103,6 +103,9 @@ type
// сильно разное на 2м и на QO-100, поэтому режим и manual-гейн per-band.
RxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid
RxGainDb: Integer; // manual gain, дБ (0..73)
// Шаговый аттенюатор ADC (openHPSDR) — на диапазон: на 40м ночью нужен,
// на 10м нет. 0..31 дБ.
AttenDB: Integer;
end;
// Персист панадаптеров и слайсов (секция "pans" под MAC). Восстанавливается
@@ -295,6 +298,7 @@ type
// Аппаратный AGC AD936x на слот трансвертера (у QO-100 своё усиление тракта).
LastRxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid
LastRxGainDb: Integer; // manual gain, дБ
LastAttenDB: Integer; // шаговый аттенюатор ADC слота, дБ (0..31)
end;
TXvtrSettings = record
@@ -558,6 +562,7 @@ begin
B.SpanHz := 192000;
B.RxGainMode := 2; // slow_attack (как глобальный дефолт Pluto)
B.RxGainDb := 40;
B.AttenDB := 0;
B.FMSQOn := False;
B.FMSQLevel := 30;
B.CTCSSOn := False;
@@ -944,6 +949,7 @@ begin
Bands[i].FMRptOffsetHz := JD(BObj,'fmrpt_offset', Bands[i].FMRptOffsetHz);
Bands[i].RxGainMode := EnsureRange(JI(BObj,'rx_gain_mode', Bands[i].RxGainMode), 0, 3);
Bands[i].RxGainDb := EnsureRange(JI(BObj,'rx_gain_db', Bands[i].RxGainDb), 0, 73);
Bands[i].AttenDB := EnsureRange(JI(BObj,'atten_db', Bands[i].AttenDB), 0, 31);
end;
end;
@@ -1060,6 +1066,7 @@ begin
JW(O,'fmrpt_dir',B.FMRptDir); JW(O,'fmrpt_offset',B.FMRptOffsetHz);
JW(O,'zoom_factor',B.ZoomFactor); JW(O,'pan_slider',B.PanSlider);
JW(O,'rx_gain_mode',B.RxGainMode); JW(O,'rx_gain_db',B.RxGainDb);
JW(O,'atten_db',B.AttenDB);
end;
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
@@ -1091,6 +1098,7 @@ begin
B.FMRptOffsetHz := JD(O,'fmrpt_offset', B.FMRptOffsetHz);
B.RxGainMode := EnsureRange(JI(O,'rx_gain_mode', B.RxGainMode), 0, 3);
B.RxGainDb := EnsureRange(JI(O,'rx_gain_db', B.RxGainDb), 0, 73);
B.AttenDB := EnsureRange(JI(O,'atten_db', B.AttenDB), 0, 31);
B.ZoomFactor := EnsureRange(JD(O,'zoom_factor', B.ZoomFactor), 0.0, 1.0);
B.PanSlider := EnsureRange(JD(O,'pan_slider', B.PanSlider), 0.0, 1.0);
end;
@@ -1617,6 +1625,7 @@ begin
X.Entries[i].LastPanSlider := 0.5;
X.Entries[i].LastRxGainMode := 2; // slow_attack
X.Entries[i].LastRxGainDb := 40;
X.Entries[i].LastAttenDB := 0;
end;
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
X.Entries[0].ButtonText := '2m';
@@ -1698,6 +1707,7 @@ begin
X.Entries[i].LastPanSlider := EnsureRange(JD(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider), 0.0, 1.0);
X.Entries[i].LastRxGainMode := EnsureRange(JI(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode), 0, 3);
X.Entries[i].LastRxGainDb := EnsureRange(JI(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb), 0, 73);
X.Entries[i].LastAttenDB := EnsureRange(JI(EObj, 'last_atten_db', X.Entries[i].LastAttenDB), 0, 31);
// Валидация: Last freqs должны быть в [FreqBegin..FreqEnd]
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
@@ -1798,6 +1808,7 @@ begin
JW(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider);
JW(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode);
JW(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb);
JW(EObj, 'last_atten_db', X.Entries[i].LastAttenDB);
end;
end;
+4 -4
View File
@@ -199,7 +199,7 @@ begin
'<option value="10">10 Hz</option><option value="100" selected>100 Hz</option>' +
'<option value="1000">1 kHz</option><option value="10000">10 kHz</option>' +
'</select>' +
'<select id="attSel"><option value="0" selected>0 dB</option><option value="1">-10 dB</option><option value="2">-20 dB</option></select>' +
'<div class="sl-g"><span class="sl-lb">ATT</span><input type="range" id="attSl" min="0" max="31" value="0"><span class="sl-val" id="attVal">0</span></div>' +
'<button id="nbBtn">NB</button>' +
'<button id="snbBtn">SNB</button>' +
'<button id="anfBtn">ANF</button>' +
@@ -311,7 +311,7 @@ begin
'q("modGrpSel").onchange=function(){const grp=GRP_MAP[modGrp()];const defaultMode=grp[0];cur.mode=defaultMode;syncModGrp(defaultMode);pend("mode");ws2({cmd:"mode",mode:defaultMode});};' +
'q("modTglBtn").onclick=function(){audioKick();const next=modTglTarget();cur.mode=next;syncModGrp(next);pend("mode");ws2({cmd:"mode",mode:next});};' +
'q("agcSel").onchange=function(e){pend("agc");ws2({cmd:"agc",mode:+e.target.value});};' +
'q("attSel").onchange=function(e){pend("attn");ws2({cmd:"attn",idx:+e.target.value});};' +
'q("attSl").oninput=function(e){var v=+e.target.value;var lbl=q("attVal");if(lbl)lbl.textContent=v;pend("attn");ws2({cmd:"attn",db:v});};' +
// FNP — фильтровый оверлей
'var FNP_N={ssb:["5.0k","4.4k","3.8k","3.3k","2.9k","2.7k","2.4k","2.1k","1.8k","1.0k"],' +
@@ -732,7 +732,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",rx_mute_on:"rxmute",rx_gain_mode:"rxgain",rx_gain_db:"rxgain"};' +
'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_db:"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];});' +
@@ -786,7 +786,7 @@ begin
'if(!isPending("mute"))updMuteIc();' +
'if(!isPending("drive")){const ts=q("txSl");if(ts&&!ts.matches(":active")){ts.value=cur.drive||0;var tv=q("txVal");if(tv)tv.textContent=cur.drive||0;}}' +
'if(!isPending("mode")){syncModGrp(cur.mode||0);syncStepSel();}' +
'if(!isPending("attn")){const at=q("attSel");if(at)at.value=cur.attn_idx||0;}' +
'if(!isPending("attn")){const at=q("attSl");if(at&&!at.matches(":active")){at.value=cur.attn_db||0;var av=q("attVal");if(av)av.textContent=cur.attn_db||0;}}' +
'updBand(activeVfo==="A"?(cur.vfo_a_hz||0):(vfoB||0));updSpan(cur.span_hz||192000);' +
'paintSM(Number(cur.smeter_dbm||-130));' +
'updStatusBar();' +
+9 -9
View File
@@ -296,7 +296,7 @@ type
FDriveLevel: Integer;
FOnFreqA: TWebCmdFreqA;
FOnAttn: TWebCmdAttn;
FAttnIdx: Integer;
FAttnDB: Integer; // шаговый аттенюатор ADC, дБ (0..31)
FOnTun: TWebCmdTun;
FOnFMStep: TWebCmdFMStep;
FTuning: Boolean;
@@ -406,7 +406,7 @@ type
CenterHz: Double; FilterIdx: Integer;
VfoB: Double; ActiveVfo: Integer;
Transmitting: Boolean; DriveLevel: Integer;
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
AttnDB: Integer; Tuning: Boolean; Duplex: Boolean;
FwdW, SWRV, PAMaxPower: Double;
const StatusText, BoardText, IPText, SupplyText, PLLText,
RXText, TXText, SeqText: string);
@@ -1572,10 +1572,10 @@ begin
end
else if Cmd = 'attn' then
begin
Idx := JsonGetInt(Json, 'idx', FAttnIdx);
Idx := JsonGetInt(Json, 'db', FAttnDB);
if Idx < 0 then Idx := 0;
if Idx > 2 then Idx := 2;
FStateLock.Enter; FAttnIdx := Idx; FStateLock.Leave;
if Idx > 31 then Idx := 31;
FStateLock.Enter; FAttnDB := Idx; FStateLock.Leave;
if Assigned(FOnAttn) then FOnAttn(Idx);
end
else if Cmd = 'fm_step' then
@@ -1778,7 +1778,7 @@ begin
'"wf_agc":%s,"wf_nf":%s,"band_idx":%d,"smeter_dbm":%.1f,' +
'"running":%s,"mute":%s,"ctun":%s,' +
'"nr_mode":%d,"nr":%s,"nb_mode":%d,"nb":%s,"snb":%s,"anf":%s,"connected":%s,' +
'"transmitting":%s,"drive":%d,"attn_idx":%d,"tuning":%s,"dup":%s,' +
'"transmitting":%s,"drive":%d,"attn_db":%d,"tuning":%s,"dup":%s,' +
'"fwd_w":%.1f,"swr":%.2f,"pa_max_power":%.0f,' +
'"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
@@ -1809,7 +1809,7 @@ begin
BoolToStr(FConnected, 'true', 'false'),
BoolToStr(FTransmitting, 'true', 'false'),
FDriveLevel,
FAttnIdx,
FAttnDB,
BoolToStr(FTuning, 'true', 'false'),
BoolToStr(FDuplex, 'true', 'false'),
FFwdW, FSWR, FPAMaxPower,
@@ -1889,7 +1889,7 @@ procedure TWebServer.PushSpectrum(
CenterHz: Double; FilterIdx: Integer;
VfoB: Double; ActiveVfo: Integer;
Transmitting: Boolean; DriveLevel: Integer;
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
AttnDB: Integer; Tuning: Boolean; Duplex: Boolean;
FwdW, SWRV, PAMaxPower: Double;
const StatusText, BoardText, IPText, SupplyText, PLLText,
RXText, TXText, SeqText: string);
@@ -1926,7 +1926,7 @@ begin
FActiveVfo := ActiveVfo;
FTransmitting := Transmitting;
FDriveLevel := DriveLevel;
FAttnIdx := AttnIdx;
FAttnDB := AttnDB;
FTuning := Tuning;
FDuplex := Duplex;
FFwdW := FwdW;