TX meter: show power and SWR in S-meter panel during transmit

- S-meter panel switches to TX mode when transmitting: single bar splits
  into two sub-bars (power top, SWR bottom) at the same position/size
- Power bar: 0..PAMaxPower W, green; SWR bar: 1..10, amber/red past 2.5
- SWR > 2.5: red border, red label, "SWR High" warning text
- Remove PWR/SWR bars and labels from TX panel (left column)
- Remove FWD/SWR from status bar; remove frequency from RUNNING status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 14:10:04 +03:00
co-authored by Claude Sonnet 4.6
parent efc21b92e6
commit 96b0ccb2c7
2 changed files with 228 additions and 57 deletions
+14 -51
View File
@@ -353,10 +353,6 @@ type
TrkDrive: TFlatSlider;
BtnMOX: TFlatButton;
BtnTUN: TFlatButton;
PbFwdPower: TPaintBox;
PbSWR: TPaintBox;
LblFwdPwr: TLabel;
LblSWRVal: TLabel;
// ---- Right panel ----
PanelRight: TPanel;
@@ -1207,8 +1203,9 @@ begin
FSpecView.SpecRange := FSpecRange;
FSpecView.SpecGridStep := FSpecGridStep;
// TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k)
FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE;
FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE;
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
FSpecView.PAMaxPower := FPAMaxPower;
FNetwork := THPSDRNetwork.Create;
FNetwork.OnDeviceFound := OnDeviceFound;
@@ -1415,7 +1412,7 @@ begin
begin
Add; Items[0].Width := 150; Items[0].Text := 'IP: --';
Add; Items[1].Width := 120; Items[1].Text := 'Board: --';
Add; Items[2].Width := 320; Items[2].Text := 'Supply: -- | FWD: -- | SWR: --';
Add; Items[2].Width := 200; Items[2].Text := 'Supply: --';
Add; Items[3].Width := 250; Items[3].Text := 'RX: waiting...';
Add; Items[4].Width := 200; Items[4].Text := 'Not connected';
Add; Items[5].Width := 280; Items[5].Text := 'TX: --';
@@ -1707,7 +1704,7 @@ begin
// TX controls
PanelTX := TPanel.Create(Self);
PanelTX.Parent := PanelLeft;
PanelTX.SetBounds(0, Y, LEFT_W, 104);
PanelTX.SetBounds(0, Y, LEFT_W, 74);
PanelTX.BevelOuter := bvNone;
MakeLbl(PanelTX, 'TX', 4, 2);
@@ -1728,27 +1725,6 @@ begin
BtnTUN := MakeBtn(PanelTX, 'TUN', 56, 38, 50, 32, BtnTUNClick);
BtnTUN.Font.Size := 12; BtnTUN.Font.Bold := True;
LblFwdPwr := TLabel.Create(Self);
LblFwdPwr.Parent := PanelTX; LblFwdPwr.Left := 112; LblFwdPwr.Top := 42;
LblFwdPwr.Caption := '0W'; LblFwdPwr.Font.Color := CLR_METER_ON;
LblFwdPwr.Font.Name := 'Courier New'; LblFwdPwr.Font.Size := 8;
LblSWRVal := TLabel.Create(Self);
LblSWRVal.Parent := PanelTX; LblSWRVal.Left := 156; LblSWRVal.Top := 42;
LblSWRVal.Caption := 'SWR:1.0'; LblSWRVal.Font.Color := CLR_TEXTDIM;
LblSWRVal.Font.Name := 'Courier New'; LblSWRVal.Font.Size := 8;
MakeLbl(PanelTX, 'PWR', 4, 76);
PbFwdPower := TPaintBox.Create(Self);
PbFwdPower.Parent := PanelTX;
PbFwdPower.SetBounds(34, 74, LEFT_W - 38, 12);
PbFwdPower.OnPaint := FSpecView.PaintFwdPower;
MakeLbl(PanelTX, 'SWR', 4, 92);
PbSWR := TPaintBox.Create(Self);
PbSWR.Parent := PanelTX;
PbSWR.SetBounds(34, 90, LEFT_W - 38, 12);
PbSWR.OnPaint := FSpecView.PaintSWR;
// ---- Right panel ----
PanelRight := TPanel.Create(Self);
@@ -2288,14 +2264,10 @@ begin
PbWaterfall.Color := T.BG;
if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel;
if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel;
PbFwdPower.Color := T.BG;
PbSWR.Color := T.BG;
// Метки
DL(LblAGCTop, False);
DL(LblDrv); DL(LblSpan);
LblFwdPwr.Font.Color := T.MeterOn;
LblSWRVal.Font.Color := T.TextDim;
// Кнопки тулбара
BtnDiscover.ClrNorm := T.TbDiscoverNorm;
@@ -2656,11 +2628,12 @@ begin
FSMeterPeak := FSMeterPeak + PeakAlpha * (PeakTarget - FSMeterPeak);
FSMeterMin := FSMeterMin + MinAlpha * (MinTarget - FSMeterMin);
FSpecView.LastSMeter := FLastSMeter;
FSpecView.SMeterPeak := FSMeterPeak;
FSpecView.SMeterMin := FSMeterMin;
FSpecView.LastFwdW := FLastFwdW;
FSpecView.LastSWR := FLastSWR;
FSpecView.LastSMeter := FLastSMeter;
FSpecView.SMeterPeak := FSMeterPeak;
FSpecView.SMeterMin := FSMeterMin;
FSpecView.LastFwdW := FLastFwdW;
FSpecView.LastSWR := FLastSWR;
FSpecView.Transmitting := FTransmitting;
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
// PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus —
@@ -2991,22 +2964,11 @@ procedure TMainForm.UpdateTXMeters;
var
PLLStr: string;
begin
if LblFwdPwr <> nil then
LblFwdPwr.Caption := Format('%.0fW', [FLastFwdW]);
if LblSWRVal <> nil then
LblSWRVal.Caption := Format('SWR:%.1f', [FLastSWR]);
if PbFwdPower <> nil then PbFwdPower.Invalidate;
if PbSWR <> nil then PbSWR.Invalidate;
if FLastPLLLock then PLLStr := 'PLL OK' else PLLStr := 'PLL?';
if FLastSupplyV >= 0 then
StatusBar1.Panels[2].Text :=
Format('Supply: %.1fV | FWD: %.0fW | SWR: %.1f | %s',
[FLastSupplyV, FLastFwdW, FLastSWR, PLLStr])
StatusBar1.Panels[2].Text := Format('Supply: %.1fV | %s', [FLastSupplyV, PLLStr])
else
StatusBar1.Panels[2].Text :=
Format('FWD: %.0fW | SWR: %.1f | %s',
[FLastFwdW, FLastSWR, PLLStr]);
StatusBar1.Panels[2].Text := PLLStr;
end;
procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
@@ -3696,7 +3658,7 @@ begin
BtnStartStop.Caption := 'STOP';
StyleButton(BtnStartStop, True);
StatusBar1.Panels[4].Text := 'RUNNING ' + FormatFreq(FVfoA);
StatusBar1.Panels[4].Text := 'RUNNING';
BtnMOX.Enabled := True;
end;
@@ -5535,6 +5497,7 @@ procedure TMainForm.OnPASettingsChange(MaxPower: Double;
var i: Integer;
begin
FPAMaxPower := MaxPower;
if FSpecView <> nil then FSpecView.PAMaxPower := MaxPower;
for i := 0 to BAND_COUNT - 1 do
begin
if i <= High(BandCal) then
+214 -6
View File
@@ -104,6 +104,8 @@ type
FSMeterMin: Double;
FLastFwdW: Double;
FLastSWR: Double;
FPAMaxPower: Double;
FTransmitting: Boolean;
// ── Буферы данных (пишутся из DSP-потока) ────────────────────────────────
FSpectrumBuf: array[0..1023] of Single;
@@ -181,8 +183,10 @@ type
property LastSMeter: Double read FLastSMeter write FLastSMeter;
property SMeterPeak: Double read FSMeterPeak write FSMeterPeak;
property SMeterMin: Double read FSMeterMin write FSMeterMin;
property LastFwdW: Double read FLastFwdW write FLastFwdW;
property LastSWR: Double read FLastSWR write FLastSWR;
property LastFwdW: Double read FLastFwdW write FLastFwdW;
property LastSWR: Double read FLastSWR write FLastSWR;
property PAMaxPower: Double read FPAMaxPower write FPAMaxPower;
property Transmitting: Boolean read FTransmitting write FTransmitting;
// ── Флаги обновления ──────────────────────────────────────────────────────
property SpectrumDirty: Boolean read FSpectrumDirty write FSpectrumDirty;
@@ -212,6 +216,7 @@ type
out ZX1, ZX2, ZY1, ZY2: Integer);
procedure DrawSMeterZone(ACanvas: TCanvas; R: TRect;
X1, X2, Y1, Y2: Integer);
procedure DrawTXMeter(ACanvas: TCanvas; R: TRect);
// ── Управление размером bitmap ────────────────────────────────────────────
procedure SetSpectrumBitmapSize(W, H: Integer);
@@ -427,6 +432,7 @@ begin
FWaterfallDirty := True;
FLastSMeter := -130; FSMeterPeak := -130; FSMeterMin := -130;
FLastFwdW := 0; FLastSWR := 1;
FPAMaxPower := 100.0; FTransmitting := False;
FLightTheme := False;
FTheme := DarkTheme;
ResetSpectrumBuf;
@@ -1318,6 +1324,202 @@ begin
finally Img.Free; end;
end;
procedure TSpectrumView.DrawTXMeter(ACanvas: TCanvas; R: TRect);
// Визуально идентичен DrawSMeterWide: те же пропорции, тот же левый блок,
// те же тики — но центральная полоса разбита на две: мощность (верх) и КСВ (низ).
const
SWR_HIGH_THRESH = 2.5;
SWR_MAX = 10.0;
LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4;
TICK_LONG = 5;
PWR_MARKS: array[0..4] of Double = (0.0, 0.25, 0.5, 0.75, 1.0);
SWR_MARKS_VAL: array[0..3] of Double = (1.0, 2.5, 5.0, 10.0);
SWR_MARKS_LBL: array[0..3] of string = ('1', '2.5', '5', '10');
var
CLR_SMETER_BG, CLR_BAR_GREEN, CLR_BAR_OVER: TColor;
CLR_TICK_GREEN, CLR_TICK_WHITE, CLR_TICK_BLUE: TColor;
CLR_LABEL_DBM, CLR_LABEL_S: TColor;
CLR_SCALE_DBM, CLR_SCALE_S_GREEN, CLR_SCALE_S_BLUE, CLR_BDR: TColor;
CLR_SWR_HIGH: TColor;
W, H, BX, BW, LeftInfo: Integer;
Y_BAR_TOP, Y_BAR_BOT, Y_BAR_MID: Integer;
PY1, PY2, SY1, SY2: Integer;
PBarEnd, SBarEnd, S_WarnX: Integer;
LblPwr, LblSWR: string;
TWPwr, LblZoneL, TH10, TH6, MaxTWPwr, MaxTWSWR, LabelNeed: Integer;
i, X, TW: Integer;
Lbl: string;
SWRHigh: Boolean;
PwrPct: Double;
function PwrToX(Pct: Double): Integer; inline;
begin
Result := BX + Round(Max(0.0, Min(1.0, Pct)) * BW);
if Result < BX then Result := BX;
if Result > BX + BW then Result := BX + BW;
end;
function SWRToX(SWR: Double): Integer; inline;
begin
Result := BX + Round(Max(0.0, Min(1.0, (SWR - 1.0) / (SWR_MAX - 1.0))) * BW);
if Result < BX then Result := BX;
if Result > BX + BW then Result := BX + BW;
end;
procedure VLine(X2, Y1, Y2: Integer; C: TColor);
begin ACanvas.Pen.Color := C; ACanvas.MoveTo(X2, Y1); ACanvas.LineTo(X2, Y2); end;
procedure FillBar(X1, Y1, X2, Y2: Integer; C: TColor);
begin
ACanvas.Brush.Color := C; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear;
if X2 > X1 then ACanvas.FillRect(Rect(X1, Y1, X2, Y2));
ACanvas.Pen.Style := psSolid;
end;
begin
CLR_SMETER_BG := FTheme.SMeterBG;
CLR_BAR_GREEN := FTheme.SMeterBarGreen;
CLR_BAR_OVER := FTheme.SMeterBarOver;
CLR_TICK_GREEN := FTheme.SMeterTickGreen;
CLR_TICK_WHITE := FTheme.SMeterTickWhite;
CLR_TICK_BLUE := FTheme.SMeterTickBlue;
CLR_LABEL_DBM := FTheme.SMeterLabelDbm;
CLR_LABEL_S := FTheme.SMeterLabelS;
CLR_SCALE_DBM := FTheme.SMeterScaleDbm;
CLR_SCALE_S_GREEN:= FTheme.SMeterScaleSGreen;
CLR_SCALE_S_BLUE := FTheme.SMeterScaleSBlue;
CLR_BDR := FTheme.SMeterBdr;
CLR_SWR_HIGH := TColor($000000CC);
W := R.Right - R.Left; H := R.Bottom - R.Top;
if (W < 80) or (H < 16) then Exit;
SWRHigh := FLastSWR > SWR_HIGH_THRESH;
LblPwr := Format('%.0fW', [FLastFwdW]);
LblSWR := Format('SWR:%.1f', [FLastSWR]);
// Левая информационная зона — такая же логика как в DrawSMeterWide
ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold];
ACanvas.Font.Size := 10;
TWPwr := ACanvas.TextWidth(LblPwr);
MaxTWPwr := ACanvas.TextWidth(Format('%.0fW', [FPAMaxPower]));
MaxTWSWR := ACanvas.TextWidth('SWR:10.0');
ACanvas.Font.Size := 6; ACanvas.Font.Style := [];
LabelNeed := Max(MaxTWPwr, MaxTWSWR) + LABEL_PAD * 2;
LeftInfo := Max(LEFT_INFO_MIN, LabelNeed + 4);
BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD;
if BW < 10 then Exit;
LblZoneL := BX - LabelNeed;
// Те же вертикальные позиции бара что в DrawSMeterWide (44% / 58%)
Y_BAR_TOP := R.Top + H * 44 div 100;
Y_BAR_BOT := R.Top + H * 58 div 100;
Y_BAR_MID := (Y_BAR_TOP + Y_BAR_BOT) div 2;
// Два суб-бара в той же зоне
PY1 := Y_BAR_TOP;
PY2 := Y_BAR_MID - 1; // power bar
SY1 := Y_BAR_MID + 1;
SY2 := Y_BAR_BOT; // SWR bar
S_WarnX := SWRToX(SWR_HIGH_THRESH);
// Фон
ACanvas.Brush.Color := CLR_SMETER_BG; ACanvas.Brush.Style := bsSolid;
ACanvas.Pen.Style := psClear; ACanvas.FillRect(R); ACanvas.Pen.Style := psSolid;
// Фоновые зоны (как зоны зелёный/красный в S-метре)
FillBar(BX, PY1, BX + BW, PY2, CLR_BAR_GREEN); // power: весь зелёный фон
FillBar(BX, SY1, S_WarnX, SY2, CLR_BAR_GREEN); // SWR: OK-зона
FillBar(S_WarnX, SY1, BX + BW, SY2, CLR_BAR_OVER); // SWR: warn-зона
// Активный заполненный бар мощности
if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0;
PBarEnd := PwrToX(PwrPct);
FillBar(BX, PY1 + 1, PBarEnd, PY2 - 1, SV_CLR_METER_ON);
// Активный заполненный бар КСВ
SBarEnd := SWRToX(FLastSWR);
if not SWRHigh then
FillBar(BX, SY1 + 1, SBarEnd, SY2 - 1, SV_CLR_AMBER)
else
begin
FillBar(BX, SY1 + 1, Min(S_WarnX, SBarEnd), SY2 - 1, SV_CLR_AMBER);
if SBarEnd > S_WarnX then
FillBar(S_WarnX, SY1 + 1, SBarEnd, SY2 - 1, CLR_SWR_HIGH);
end;
// Граница KСВ 2.5
VLine(S_WarnX, SY1, SY2, FTheme.SMeterDivider);
// Рамки обоих баров + левая зона надписей
ACanvas.Brush.Style := bsClear; ACanvas.Pen.Color := CLR_BDR;
ACanvas.Rectangle(LblZoneL, PY1, BX + BW, PY2);
if SWRHigh then ACanvas.Pen.Color := CLR_SWR_HIGH
else ACanvas.Pen.Color := CLR_BDR;
ACanvas.Rectangle(LblZoneL, SY1, BX + BW, SY2);
// Разделители между зоной надписей и шкалой
ACanvas.Pen.Color := FTheme.SMeterDivider;
ACanvas.MoveTo(BX, PY1 + 1); ACanvas.LineTo(BX, PY2 - 1);
ACanvas.MoveTo(BX, SY1 + 1); ACanvas.LineTo(BX, SY2 - 1);
// --- Тики шкалы ---
ACanvas.Font.Size := 6; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear;
// Мощность: тики ВЫШЕ power-бара (как dBm-тики в S-метре)
for i := 0 to 4 do
begin
X := PwrToX(PWR_MARKS[i]);
VLine(X, PY1 - TICK_LONG, PY1 - 1, CLR_TICK_WHITE);
Lbl := Format('%.0f', [PWR_MARKS[i] * FPAMaxPower]);
TW := ACanvas.TextWidth(Lbl);
ACanvas.Font.Color := CLR_SCALE_DBM;
ACanvas.TextOut(X - TW div 2, PY1 - TICK_LONG - ACanvas.TextHeight(Lbl) - 1, Lbl);
end;
// КСВ: тики НИЖЕ SWR-бара (как S-тики в S-метре)
for i := 0 to 3 do
begin
X := SWRToX(SWR_MARKS_VAL[i]);
if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then
VLine(X, SY2 + 1, SY2 + TICK_LONG, CLR_TICK_BLUE)
else
VLine(X, SY2 + 1, SY2 + TICK_LONG, CLR_TICK_GREEN);
Lbl := SWR_MARKS_LBL[i]; TW := ACanvas.TextWidth(Lbl);
if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then
begin
if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH
else ACanvas.Font.Color := CLR_SCALE_S_BLUE;
end else
ACanvas.Font.Color := CLR_SCALE_S_GREEN;
ACanvas.TextOut(X - TW div 2, SY2 + TICK_LONG + 1, Lbl);
end;
// --- Надписи в левой зоне (те же позиции что в DrawSMeterWide) ---
ACanvas.Brush.Style := bsClear;
ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0');
ACanvas.Font.Size := 6; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0');
// Мощность — над верхним баром, как dBm в S-метре
ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM;
ACanvas.TextOut(LblZoneL + 4, PY1 - TH10 - 2, LblPwr);
// КСВ — под нижним баром, как S-значение в S-метре
ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold];
if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH
else ACanvas.Font.Color := CLR_LABEL_S;
ACanvas.TextOut(LblZoneL + 4, SY2 + 2, LblSWR);
// "SWR High" рядом с надписью мощности (как "dBm" в S-метре)
if SWRHigh then
begin
ACanvas.Font.Size := 6; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_SWR_HIGH;
ACanvas.TextOut(LblZoneL + 4 + TWPwr + 2, PY1 - TH6 - 4, 'SWR High');
end;
end;
// ────────────────────────────────────────────────────────────────────────────
// Paint-обработчики
// ────────────────────────────────────────────────────────────────────────────
@@ -1370,10 +1572,16 @@ begin
FSmBitmap := TBitmap.Create;
FSmBitmap.SetSize(W, H);
end;
DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, W, H),
FLastSMeter, FSMeterPeak, FSMeterMin,
ZX1, ZX2, ZY1, ZY2);
DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, W, H), ZX1, ZX2, ZY1, ZY2);
if FTransmitting then
begin
DrawTXMeter(FSmBitmap.Canvas, Rect(0, 0, W, H));
end else
begin
DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, W, H),
FLastSMeter, FSMeterPeak, FSMeterMin,
ZX1, ZX2, ZY1, ZY2);
DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, W, H), ZX1, ZX2, ZY1, ZY2);
end;
PB.Canvas.Draw(0, 0, FSmBitmap);
end;