mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 19:57:33 +00:00
Web TX meter: show power/SWR in S-meter canvas during transmit
- WebServer: add fwd_w, swr, pa_max_power to state JSON and PushSpectrum - MainForm: pass FLastFwdW, FLastSWR, FPAMaxPower to PushSpectrum - Web UI: paintSM switches to paintTXMeter when transmitting/tuning; same layout as desktop — two sub-bars at 44-58% height, power ticks above, SWR ticks below, SWR>2.5 turns red with "SWR High" label Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -2804,7 +2804,8 @@ begin
|
||||
FCenterFreq, FFilter,
|
||||
FVfoB, FActiveVfo,
|
||||
FTransmitting, TrkDrive.Position,
|
||||
FAtten, FTuning, FDisplayDuplex);
|
||||
FAtten, FTuning, FDisplayDuplex,
|
||||
FLastFwdW, FLastSWR, FPAMaxPower);
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
@@ -412,6 +412,7 @@ begin
|
||||
'let smAvg=-130,smPeak=-130,smMin=-130;' +
|
||||
'function dbToX(db,bx,bw){const DB_MIN=-127,DB_MAX=-13;return Math.max(bx,Math.min(bx+bw,Math.round(bx+(db-DB_MIN)/(DB_MAX-DB_MIN)*bw)));}' +
|
||||
'function paintSM(dbm){' +
|
||||
'if(cur.transmitting||cur.tuning){paintTXMeter();return;}' +
|
||||
'const w=smCv.clientWidth||340,h=smCv.clientHeight||64;' +
|
||||
'const AVG_ALPHA=0.40,ZONE_DB=6,PEAK_BASE=0.12,MIN_BASE=0.12,ACCEL=0.030,MAX_A=0.92;' +
|
||||
'const DBM_MARKS=[-120,-100,-80,-60,-40,-20];' +
|
||||
@@ -460,6 +461,71 @@ begin
|
||||
'mctx.font="8px Courier New";mctx.fillStyle="#90A070";mctx.fillText("dBm",lblZoneL+4+twDbm+2,YT-10);' +
|
||||
'mctx.fillStyle="#F0F0F0";mctx.font="bold 12px Courier New";mctx.fillText(sLbl,lblZoneL+4,YB+14);}' +
|
||||
|
||||
'function paintTXMeter(){' +
|
||||
'const w=smCv.clientWidth||340,h=smCv.clientHeight||64;' +
|
||||
'const fwdW=cur.fwd_w||0,swr=Math.max(1,cur.swr||1),maxW=cur.pa_max_power||100;' +
|
||||
'const SWR_HIGH=2.5,SWR_MAX=10.0,TICK_LONG=5;' +
|
||||
'const swrHigh=swr>SWR_HIGH;' +
|
||||
'mctx.font="bold 12px Courier New";' +
|
||||
'const lblPwr=fwdW.toFixed(0)+"W",lblSWR="SWR:"+swr.toFixed(1);' +
|
||||
'const maxTWPwr=mctx.measureText(maxW.toFixed(0)+"W").width;' +
|
||||
'const maxTWSWR=mctx.measureText("SWR:10.0").width;' +
|
||||
'mctx.font="8px Courier New";' +
|
||||
'const labelNeed=Math.max(maxTWPwr,maxTWSWR)+8;' +
|
||||
'const leftInfo=Math.max(62,labelNeed+4),BX=leftInfo,BW=Math.max(10,w-leftInfo-8),lblZoneL=BX-labelNeed;' +
|
||||
'const YT=Math.round(h*44/100),YB=Math.round(h*58/100);' +
|
||||
'const YM=Math.round((YT+YB)/2);' +
|
||||
'const PY1=YT,PY2=YM-1,SY1=YM+1,SY2=YB;' +
|
||||
'const swarnX=BX+Math.round(Math.max(0,Math.min(1,(SWR_HIGH-1)/(SWR_MAX-1)))*BW);' +
|
||||
'function pwrX(p){return BX+Math.round(Math.max(0,Math.min(1,p))*BW);}' +
|
||||
'function swrX(s){return BX+Math.round(Math.max(0,Math.min(1,(s-1)/(SWR_MAX-1)))*BW);}' +
|
||||
'mctx.clearRect(0,0,w,h);' +
|
||||
// фоновые зоны
|
||||
'mctx.fillStyle="#001F09";mctx.fillRect(BX,PY1,BW,PY2-PY1);' +
|
||||
'mctx.fillStyle="#001F09";mctx.fillRect(BX,SY1,swarnX-BX,SY2-SY1);' +
|
||||
'mctx.fillStyle="#281509";mctx.fillRect(swarnX,SY1,BX+BW-swarnX,SY2-SY1);' +
|
||||
// бар мощности
|
||||
'const pEnd=pwrX(maxW>0?fwdW/maxW:0);' +
|
||||
'if(pEnd>BX){mctx.fillStyle="#44CC00";mctx.fillRect(BX,PY1+1,pEnd-BX,PY2-PY1-2);}' +
|
||||
// бар КСВ
|
||||
'const sEnd=swrX(swr);' +
|
||||
'if(sEnd>BX){' +
|
||||
'if(!swrHigh){mctx.fillStyle="#FFAA00";mctx.fillRect(BX,SY1+1,sEnd-BX,SY2-SY1-2);}' +
|
||||
'else{const safeEnd=Math.min(swarnX,sEnd);' +
|
||||
'if(safeEnd>BX){mctx.fillStyle="#FFAA00";mctx.fillRect(BX,SY1+1,safeEnd-BX,SY2-SY1-2);}' +
|
||||
'if(sEnd>swarnX){mctx.fillStyle="#CC0000";mctx.fillRect(swarnX,SY1+1,sEnd-swarnX,SY2-SY1-2);}}}' +
|
||||
// делитель 2.5
|
||||
'mctx.strokeStyle="#404040";mctx.lineWidth=1;mctx.beginPath();mctx.moveTo(swarnX,SY1);mctx.lineTo(swarnX,SY2);mctx.stroke();' +
|
||||
// рамки
|
||||
'mctx.lineWidth=1;mctx.strokeStyle="#283030";mctx.strokeRect(lblZoneL,PY1,BX+BW-lblZoneL,PY2-PY1);' +
|
||||
'mctx.strokeStyle=swrHigh?"#CC0000":"#283030";mctx.strokeRect(lblZoneL,SY1,BX+BW-lblZoneL,SY2-SY1);' +
|
||||
// разделители зона надписей / шкала
|
||||
'mctx.strokeStyle="#404040";mctx.lineWidth=1;' +
|
||||
'mctx.beginPath();mctx.moveTo(BX,PY1+1);mctx.lineTo(BX,PY2-1);mctx.stroke();' +
|
||||
'mctx.beginPath();mctx.moveTo(BX,SY1+1);mctx.lineTo(BX,SY2-1);mctx.stroke();' +
|
||||
// тики мощности выше power-бара
|
||||
'mctx.font="8px Courier New";' +
|
||||
'const pwrMarks=[0,0.25,0.5,0.75,1.0];' +
|
||||
'for(let i=0;i<pwrMarks.length;i++){const x=pwrX(pwrMarks[i]);' +
|
||||
'mctx.strokeStyle="#DCE0E0";mctx.lineWidth=1;mctx.beginPath();mctx.moveTo(x,PY1-TICK_LONG);mctx.lineTo(x,PY1-1);mctx.stroke();' +
|
||||
'const t=(pwrMarks[i]*maxW).toFixed(0),tw=mctx.measureText(t).width;' +
|
||||
'mctx.fillStyle="#DCDCD0";mctx.fillText(t,Math.round(x-tw/2),PY1-TICK_LONG-3);}' +
|
||||
// тики КСВ ниже SWR-бара
|
||||
'const swrMarks=[1,2.5,5,10],swrLbls=["1","2.5","5","10"];' +
|
||||
'for(let i=0;i<swrMarks.length;i++){const x=swrX(swrMarks[i]),isW=swrMarks[i]>=SWR_HIGH;' +
|
||||
'mctx.strokeStyle=isW?"#90A070":"#708070";mctx.lineWidth=1;mctx.beginPath();mctx.moveTo(x,SY2+1);mctx.lineTo(x,SY2+TICK_LONG);mctx.stroke();' +
|
||||
'const t=swrLbls[i],tw=mctx.measureText(t).width;' +
|
||||
'mctx.fillStyle=isW?(swrHigh?"#CC0000":"#90D090"):"#E0E0E0";' +
|
||||
'mctx.fillText(t,Math.round(x-tw/2),SY2+TICK_LONG+11);}' +
|
||||
// надписи в левой зоне
|
||||
'mctx.font="bold 12px Courier New";' +
|
||||
'mctx.fillStyle="#DCD896";mctx.fillText(lblPwr,lblZoneL+4,PY1-12-2);' +
|
||||
'mctx.fillStyle=swrHigh?"#CC0000":"#F0F0F0";mctx.fillText(lblSWR,lblZoneL+4,SY2+14);' +
|
||||
// "SWR High" рядом с мощностью
|
||||
'if(swrHigh){const twP=mctx.measureText(lblPwr).width;' +
|
||||
'mctx.font="bold 8px Courier New";mctx.fillStyle="#CC0000";' +
|
||||
'mctx.fillText("SWR High",lblZoneL+4+twP+2,PY1-8);}}' +
|
||||
|
||||
// pendingCmds: команды в пути — не перезаписываем эти поля из server state
|
||||
'var pendingCmds={};' +
|
||||
'function pend(field){pendingCmds[field]=Date.now()+600;}' +
|
||||
|
||||
+18
-4
@@ -247,6 +247,10 @@ type
|
||||
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
|
||||
FOnXvtrBand: TWebCmdXvtrBand;
|
||||
|
||||
FFwdW: Double;
|
||||
FSWR: Double;
|
||||
FPAMaxPower: Double;
|
||||
|
||||
FWebClientActive: Boolean;
|
||||
|
||||
// ── Внутренние методы ──
|
||||
@@ -290,7 +294,8 @@ type
|
||||
CenterHz: Double; FilterIdx: Integer;
|
||||
VfoB: Double; ActiveVfo: Integer;
|
||||
Transmitting: Boolean; DriveLevel: Integer;
|
||||
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean);
|
||||
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
|
||||
FwdW, SWRV, PAMaxPower: Double);
|
||||
|
||||
property WebClientActive: Boolean read FWebClientActive;
|
||||
property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits;
|
||||
@@ -419,8 +424,11 @@ begin
|
||||
FAGCTop := 90;
|
||||
FSpanHz := 192000;
|
||||
FVolume := 70;
|
||||
FSMeter := -120;
|
||||
FBandIdx := 5;
|
||||
FSMeter := -120;
|
||||
FFwdW := 0;
|
||||
FSWR := 1.0;
|
||||
FPAMaxPower := 100.0;
|
||||
FBandIdx := 5;
|
||||
FCurrentXvtr := -1;
|
||||
FFreqMhzDigits := 3;
|
||||
SetLength(FXvtrBands, 0);
|
||||
@@ -1236,6 +1244,7 @@ begin
|
||||
'"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,' +
|
||||
'"fwd_w":%.1f,"swr":%.2f,"pa_max_power":%.0f,' +
|
||||
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d}',
|
||||
[FFreq, FVfoB, FActiveVfo,
|
||||
FMode, MODE_N[FMode mod 8],
|
||||
@@ -1260,6 +1269,7 @@ begin
|
||||
FAttnIdx,
|
||||
BoolToStr(FTuning, 'true', 'false'),
|
||||
BoolToStr(FDuplex, 'true', 'false'),
|
||||
FFwdW, FSWR, FPAMaxPower,
|
||||
FCurrentXvtr, XvtrJson, FFreqMhzDigits
|
||||
], FS);
|
||||
finally
|
||||
@@ -1327,7 +1337,8 @@ procedure TWebServer.PushSpectrum(
|
||||
CenterHz: Double; FilterIdx: Integer;
|
||||
VfoB: Double; ActiveVfo: Integer;
|
||||
Transmitting: Boolean; DriveLevel: Integer;
|
||||
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean);
|
||||
AttnIdx: Integer; Tuning: Boolean; Duplex: Boolean;
|
||||
FwdW, SWRV, PAMaxPower: Double);
|
||||
var N, i: Integer;
|
||||
begin
|
||||
if not FRunning then Exit;
|
||||
@@ -1364,6 +1375,9 @@ begin
|
||||
FAttnIdx := AttnIdx;
|
||||
FTuning := Tuning;
|
||||
FDuplex := Duplex;
|
||||
FFwdW := FwdW;
|
||||
FSWR := SWRV;
|
||||
FPAMaxPower := PAMaxPower;
|
||||
finally
|
||||
FStateLock.Leave;
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user