Add web status bar fields

This commit is contained in:
2026-05-21 21:37:25 +03:00
parent 3bf8c1c162
commit dede74d0a5
3 changed files with 151 additions and 14 deletions
+67 -1
View File
@@ -2943,6 +2943,8 @@ procedure TMainForm.SpectrumTimerTick(Sender: TObject);
var var
NowTick: QWord; NowTick: QWord;
ElapsedTick: QWord; ElapsedTick: QWord;
WebStatusText, WebBoardText, WebIPText, WebSupplyText: string;
WebPLLText, WebRXText, WebTXText, WebSeqText: string;
begin begin
// Синхронизируем размеры если размер панели изменился (после ресайза). // Синхронизируем размеры если размер панели изменился (после ресайза).
// Важно: проверяем только видимые панели — когда спектр скрыт, // Важно: проверяем только видимые панели — когда спектр скрыт,
@@ -3072,6 +3074,67 @@ begin
// Пушим текущее состояние в веб-клиенты (если есть) // Пушим текущее состояние в веб-клиенты (если есть)
if Assigned(FWebServer) then if Assigned(FWebServer) then
begin
if FNetwork.Connected then
begin
if FRunning then
WebStatusText := 'Running'
else
WebStatusText := 'Connected';
WebBoardText := 'Board: ' + BoardTypeName(FNetwork.Device.BoardType);
WebIPText := 'IP: ' + FNetwork.Device.IPAddress;
end
else
begin
WebStatusText := 'Disconnected';
WebBoardText := 'Board --';
WebIPText := 'IP --';
end;
if not FRunning then
begin
WebSupplyText := 'Supply --';
WebPLLText := 'PLL --';
end
else
begin
if FLastPLLLock then
WebPLLText := 'PLL OK'
else
WebPLLText := 'PLL?';
if not BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then
WebSupplyText := 'Supply n/a'
else if FLastSupplyV >= 0 then
begin
if FLastSupplyA >= 0 then
WebSupplyText := Format('Supply %.1fV %.1fA', [FLastSupplyV, FLastSupplyA])
else
WebSupplyText := Format('Supply %.1fV', [FLastSupplyV]);
end
else
WebSupplyText := 'Supply --';
end;
if FRunning then
WebRXText := 'RX running'
else
WebRXText := 'RX idle';
if FTuning then
WebTXText := 'TX tune'
else if FTransmitting then
WebTXText := 'TX active'
else
WebTXText := 'TX idle';
if FSeqErrorCount > 0 then
WebSeqText := Format('SEQ ERR DDC%d', [FLastSeqErrorDDC])
else if FRunning then
WebSeqText := 'SEQ OK'
else
WebSeqText := 'SEQ --';
FWebServer.PushSpectrum( FWebServer.PushSpectrum(
FSpectrumBuf, 1024, FSpectrumBuf, 1024,
FWaterfallBuf, FWaterfallBuf,
@@ -3087,7 +3150,10 @@ begin
FVfoB, FActiveVfo, FVfoB, FActiveVfo,
FTransmitting, TrkDrive.Position, FTransmitting, TrkDrive.Position,
FAtten, FTuning, FDisplayDuplex, FAtten, FTuning, FDisplayDuplex,
FLastFwdW, FLastSWR, FPAMaxPower); FLastFwdW, FLastSWR, FPAMaxPower,
WebStatusText, WebBoardText, WebIPText, WebSupplyText,
WebPLLText, WebRXText, WebTXText, WebSeqText);
end;
end; end;
// =========================================================================== // ===========================================================================
+24 -5
View File
@@ -95,8 +95,11 @@ begin
'.ruler-area canvas{position:absolute;top:0;left:0;width:100%;height:100%}' + '.ruler-area canvas{position:absolute;top:0;left:0;width:100%;height:100%}' +
'.wf-area{flex:0 0 405px;position:relative;background:#000;border:1px solid #252525;border-radius:3px;overflow:hidden}' + '.wf-area{flex:0 0 405px;position:relative;background:#000;border:1px solid #252525;border-radius:3px;overflow:hidden}' +
'.wf-area canvas{position:absolute;top:0;left:0;width:100%;height:100%}' + '.wf-area canvas{position:absolute;top:0;left:0;width:100%;height:100%}' +
'.sbar{flex:0 0 16px;display:flex;align-items:center;padding:0 6px;gap:10px;background:#0e0e0e;border-top:1px solid #222;font-size:10px;color:#555}' + '.sbar{flex:0 0 24px;display:grid;grid-template-columns:minmax(120px,170px) minmax(190px,280px) minmax(120px,160px) minmax(135px,170px) minmax(70px,90px) minmax(120px,190px) minmax(120px,180px) minmax(90px,1fr) minmax(82px,96px) minmax(92px,112px);align-items:stretch;background:#0e0e0e;border-top:1px solid #222;font:10px Courier New,monospace;color:#ccc;overflow-x:auto;overflow-y:hidden}' +
'#latEl{margin-left:auto}' + '.sb-cell{min-width:0;display:flex;align-items:center;padding:0 8px;border-left:1px solid #222;background:#181818;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
'.sb-cell:first-child{border-left:0}' +
'.sb-dim{color:#777}.sb-seq{color:#ffaa00}.sb-status{color:#90c090}.sb-time{justify-content:center;color:#bbb}.sb-lat{display:none}' +
'@media screen and (max-width:640px){.sbar{grid-template-columns:minmax(72px,1fr) minmax(104px,1.25fr) minmax(74px,1fr) minmax(78px,1fr);overflow:hidden}.sbar #sbBoard,.sbar #sbIP,.sbar #sbPLL,.sbar #sbRX,.sbar #sbTX,.sbar #sbLocal{display:none}.sb-cell{padding:0 5px;font-size:9px}}' +
'@media screen and (max-width:1300px) and (min-width:641px){' + '@media screen and (max-width:1300px) and (min-width:641px){' +
'.tbar{gap:3px;padding-left:4px;padding-right:4px;}' + '.tbar{gap:3px;padding-left:4px;padding-right:4px;}' +
'button{padding:0 6px;font-size:10px;}' + 'button{padding:0 6px;font-size:10px;}' +
@@ -227,7 +230,19 @@ begin
'<div class="spec-area"><canvas id="spec"></canvas><div class="span-ov" id="spanOv"></div></div>' + '<div class="spec-area"><canvas id="spec"></canvas><div class="span-ov" id="spanOv"></div></div>' +
'<div class="ruler-area"><canvas id="ruler"></canvas></div>' + '<div class="ruler-area"><canvas id="ruler"></canvas></div>' +
'<div class="wf-area"><canvas id="wf"></canvas></div>' + '<div class="wf-area"><canvas id="wf"></canvas></div>' +
'<div class="sbar"><span id="stEl">connecting...</span><span id="latEl"></span></div>' + '<div class="sbar">' +
'<span class="sb-cell sb-status" id="stEl">connecting...</span>' +
'<span class="sb-cell" id="sbBoard">Board --</span>' +
'<span class="sb-cell" id="sbIP">IP --</span>' +
'<span class="sb-cell" id="sbSupply">Supply --</span>' +
'<span class="sb-cell" id="sbPLL">PLL --</span>' +
'<span class="sb-cell sb-dim" id="sbRX">RX idle</span>' +
'<span class="sb-cell sb-dim" id="sbTX">TX idle</span>' +
'<span class="sb-cell sb-seq" id="sbSEQ">SEQ --</span>' +
'<span class="sb-cell sb-time" id="sbUTC">UTC --:--:--</span>' +
'<span class="sb-cell sb-time" id="sbLocal">Local --:--:--</span>' +
'<span class="sb-lat" id="latEl"></span>' +
'</div>' +
'</div>' + '</div>' +
'<script>' + '<script>' +
@@ -257,6 +272,11 @@ begin
'const cl=(v,a,b)=>Math.max(a,Math.min(b,v));' + 'const cl=(v,a,b)=>Math.max(a,Math.min(b,v));' +
'const q=id=>document.getElementById(id);' + 'const q=id=>document.getElementById(id);' +
'function sw(id,on){const b=q(id);if(b)b.classList.toggle("on",!!on);}' + 'function sw(id,on){const b=q(id);if(b)b.classList.toggle("on",!!on);}' +
'function sb(id,txt){const e=q(id);if(e){e.textContent=txt||"";e.title=txt||"";}}' +
'function fmtTime(d,utc){const p=n=>String(n).padStart(2,"0");return(utc?p(d.getUTCHours()):p(d.getHours()))+":"+p(utc?d.getUTCMinutes():d.getMinutes())+":"+p(utc?d.getUTCSeconds():d.getSeconds());}' +
'function updClock(){const d=new Date();sb("sbUTC","UTC "+fmtTime(d,true));sb("sbLocal","Local "+fmtTime(d,false));}' +
'function updStatusBar(){sb("stEl",cur.status_text||((cur.connected?"OK":"OFF")+" | "+(cur.running?"RUN":"STOP")));sb("sbBoard",cur.board_text||"Board --");sb("sbIP",cur.ip_text||"IP --");sb("sbSupply",cur.supply_text||"Supply --");sb("sbPLL",cur.pll_text||"PLL --");sb("sbRX",cur.rx_text||"RX idle");sb("sbTX",cur.tx_text||"TX idle");sb("sbSEQ",cur.seq_text||"SEQ --");}' +
'updClock();setInterval(updClock,1000);' +
'function ws2(obj){if(ws&&ws.readyState===1)ws.send(JSON.stringify(obj));}' + 'function ws2(obj){if(ws&&ws.readyState===1)ws.send(JSON.stringify(obj));}' +
'function cmd(c,v){ws2({cmd:c,on:v});}' + 'function cmd(c,v){ws2({cmd:c,on:v});}' +
'function updNrBtn(){const b=q("nrBtn");if(!b)return;const m=cur.nr_mode|0;b.textContent=(m===2)?"NR2":(m===3)?"NR3":(m===4)?"NR4":"NR";sw("nrBtn",m!==0);}' + 'function updNrBtn(){const b=q("nrBtn");if(!b)return;const m=cur.nr_mode|0;b.textContent=(m===2)?"NR2":(m===3)?"NR3":(m===4)?"NR4":"NR";sw("nrBtn",m!==0);}' +
@@ -622,8 +642,7 @@ begin
'if(!isPending("attn")){const at=q("attSel");if(at)at.value=cur.attn_idx||0;}' + 'if(!isPending("attn")){const at=q("attSel");if(at)at.value=cur.attn_idx||0;}' +
'updBand(activeVfo==="A"?(cur.vfo_a_hz||0):(vfoB||0));updSpan(cur.span_hz||192000);' + 'updBand(activeVfo==="A"?(cur.vfo_a_hz||0):(vfoB||0));updSpan(cur.span_hz||192000);' +
'paintSM(Number(cur.smeter_dbm||-130));' + 'paintSM(Number(cur.smeter_dbm||-130));' +
'const mn=cur.mode_name||(MODE_N[cur.mode||0]||"?");' + 'updStatusBar();' +
'stEl.textContent=(cur.connected?"OK":"OFF")+" | "+(cur.running?"RUN":"STOP")+" | "+(cur.transmitting?"TX":"RX")+" | "+mn+" | "+((activeVfoHz()/1e6).toFixed(3))+" MHz";' +
'const nowTx=!!cur.transmitting;if(nowTx!==prevTx){prevTx=nowTx;if(nowTx)startTXMic();else stopTXMic();}}' + 'const nowTx=!!cur.transmitting;if(nowTx!==prevTx){prevTx=nowTx;if(nowTx)startTXMic();else stopTXMic();}}' +
'function pal(db){const v=Math.round(cl((db-wfLo)/Math.max(1,wfHi-wfLo),0,1)*255);const c=PAL[v]|0;return[(c>>16)&255,(c>>8)&255,c&255];}' + 'function pal(db){const v=Math.round(cl((db-wfLo)/Math.max(1,wfHi-wfLo),0,1)*255);const c=PAL[v]|0;return[(c>>16)&255,(c>>8)&255,c&255];}' +
+60 -8
View File
@@ -284,6 +284,14 @@ type
FFwdW: Double; FFwdW: Double;
FSWR: Double; FSWR: Double;
FPAMaxPower: Double; FPAMaxPower: Double;
FStatusText: string;
FBoardText: string;
FIPText: string;
FSupplyText: string;
FPLLText: string;
FRXText: string;
FTXText: string;
FSeqText: string;
FWebClientActive: Boolean; FWebClientActive: Boolean;
@@ -333,7 +341,9 @@ type
VfoB: Double; ActiveVfo: Integer; VfoB: Double; ActiveVfo: Integer;
Transmitting: Boolean; DriveLevel: Integer; Transmitting: Boolean; DriveLevel: Integer;
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean; AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
FwdW, SWRV, PAMaxPower: Double); FwdW, SWRV, PAMaxPower: Double;
const StatusText, BoardText, IPText, SupplyText, PLLText,
RXText, TXText, SeqText: string);
property WebClientActive: Boolean read FWebClientActive; property WebClientActive: Boolean read FWebClientActive;
property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits; property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits;
@@ -472,6 +482,14 @@ begin
FFwdW := 0; FFwdW := 0;
FSWR := 1.0; FSWR := 1.0;
FPAMaxPower := 100.0; FPAMaxPower := 100.0;
FStatusText := 'Disconnected';
FBoardText := 'Board --';
FIPText := 'IP --';
FSupplyText := 'Supply --';
FPLLText := 'PLL --';
FRXText := 'RX idle';
FTXText := 'TX idle';
FSeqText := 'SEQ --';
FBandIdx := 5; FBandIdx := 5;
FCurrentXvtr := -1; FCurrentXvtr := -1;
FFreqMhzDigits := 3; FFreqMhzDigits := 3;
@@ -1406,6 +1424,25 @@ end;
Формирование JSON-состояния Формирование JSON-состояния
═══════════════════════════════════════════════════════════════════════════ } ═══════════════════════════════════════════════════════════════════════════ }
function JsonEscape(const S: string): string;
var
I: Integer;
begin
Result := '';
for I := 1 to Length(S) do
case S[I] of
'\': Result := Result + '\\';
'"': Result := Result + '\"';
#8: Result := Result + '\b';
#9: Result := Result + '\t';
#10: Result := Result + '\n';
#12: Result := Result + '\f';
#13: Result := Result + '\r';
else
Result := Result + S[I];
end;
end;
function TWebServer.BuildStateJson: string; function TWebServer.BuildStateJson: string;
const const
MODE_N: array[0..7] of string = MODE_N: array[0..7] of string =
@@ -1438,10 +1475,12 @@ begin
'"wf_agc":%s,"wf_nf":%s,"band_idx":%d,"smeter_dbm":%.1f,' + '"wf_agc":%s,"wf_nf":%s,"band_idx":%d,"smeter_dbm":%.1f,' +
'"running":%s,"mute":%s,"ctun":%s,' + '"running":%s,"mute":%s,"ctun":%s,' +
'"nr_mode":%d,"nr":%s,"nb_mode":%d,"nb":%s,"snb":%s,"anf":%s,"connected":%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_idx":%d,"tuning":%s,"dup":%s,' +
'"fwd_w":%.1f,"swr":%.2f,"pa_max_power":%.0f,' + '"fwd_w":%.1f,"swr":%.2f,"pa_max_power":%.0f,' +
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' + '"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
'"fmstep_idx":%d}', '"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}',
[FFreq, FVfoB, FActiveVfo, [FFreq, FVfoB, FActiveVfo,
FMode, MODE_N[FMode mod 8], FMode, MODE_N[FMode mod 8],
FFilterIdx, FFilterBW, FFilterIdx, FFilterBW,
@@ -1465,8 +1504,11 @@ begin
FAttnIdx, FAttnIdx,
BoolToStr(FTuning, 'true', 'false'), BoolToStr(FTuning, 'true', 'false'),
BoolToStr(FDuplex, 'true', 'false'), BoolToStr(FDuplex, 'true', 'false'),
FFwdW, FSWR, FPAMaxPower, FFwdW, FSWR, FPAMaxPower,
FCurrentXvtr, XvtrJson, FFreqMhzDigits, JsonEscape(FStatusText), JsonEscape(FBoardText), JsonEscape(FIPText),
JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText),
JsonEscape(FTXText), JsonEscape(FSeqText),
FCurrentXvtr, XvtrJson, FFreqMhzDigits,
FFMStepIdx FFMStepIdx
], FS); ], FS);
finally finally
@@ -1533,7 +1575,9 @@ procedure TWebServer.PushSpectrum(
VfoB: Double; ActiveVfo: Integer; VfoB: Double; ActiveVfo: Integer;
Transmitting: Boolean; DriveLevel: Integer; Transmitting: Boolean; DriveLevel: Integer;
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean; AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
FwdW, SWRV, PAMaxPower: Double); FwdW, SWRV, PAMaxPower: Double;
const StatusText, BoardText, IPText, SupplyText, PLLText,
RXText, TXText, SeqText: string);
var N, i: Integer; var N, i: Integer;
begin begin
if not FRunning then Exit; if not FRunning then Exit;
@@ -1573,6 +1617,14 @@ begin
FFwdW := FwdW; FFwdW := FwdW;
FSWR := SWRV; FSWR := SWRV;
FPAMaxPower := PAMaxPower; FPAMaxPower := PAMaxPower;
FStatusText := StatusText;
FBoardText := BoardText;
FIPText := IPText;
FSupplyText := SupplyText;
FPLLText := PLLText;
FRXText := RXText;
FTXText := TXText;
FSeqText := SeqText;
finally finally
FStateLock.Leave; FStateLock.Leave;
end; end;