diff --git a/MainForm.pas b/MainForm.pas
index 6cdd417..eb1225f 100644
--- a/MainForm.pas
+++ b/MainForm.pas
@@ -3481,7 +3481,7 @@ begin
if FController.FWDSPReady then
begin
- FController.FLastSMeter := FController.FDSPEngine.GetSMeterDBm;
+ FController.FLastSMeter := FController.ReadSMeterDBm;
FController.FDSPEngine.SetSpectrumWidth(FPan.SpectrumWidth);
Inc(FAgcLineCounter);
if FAgcLineCounter >= 6 then
@@ -8262,6 +8262,9 @@ begin
// физических разъёмов на диапазон/трансвертер (если разъёмов две пары).
SF.SetAntennaBackend(FController.BackendCaps.Kind = bkPluto,
FController.BackendCaps.HasAntSwitch);
+ // Вкладка Calibration: band-план и состав групп — по активному железу
+ // (у AD936x нет моста детектора мощности и телеметрии питания).
+ SF.SetCalibrationBackend(FController.BackendCaps.Kind = bkPluto);
SF.LoadAnt936x(FController.Ant936x, FController.FXvtrSettings);
SF.LoadXvtrSettings(FController.FXvtrSettings);
SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal);
diff --git a/RadioController.pas b/RadioController.pas
index 3f93422..39ee9fa 100644
--- a/RadioController.pas
+++ b/RadioController.pas
@@ -623,6 +623,11 @@ type
procedure SetSliceDSP(Id, NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
procedure SetSliceFMSquelch(Id: Integer; On_: Boolean; Level: Integer);
function SliceSMeter(Id: Integer): Double; // dBm, из DSP-потока
+ // S-метр главного приёмника, dBm, с калибровочным офсетом текущего
+ // диапазона/трансвертера (движок отдаёт сырое значение).
+ function ReadSMeterDBm: Double;
+ // Офсет S-метра (дБ) для видимой частоты: слот трансвертера или диапазон.
+ function SMeterCalFor(VisibleHz: Double): Double;
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
function SliceCount: Integer;
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
@@ -685,7 +690,7 @@ type
function PlutoTxAttForDrive: Double; // дБ<0 под текущий FDrivePercent
procedure ApplyPlutoTxAtten; // ставит att по FTransmitting/FTuning
procedure SetPlutoTxMaxAtt(Db: Double); // потолок на drive=100% (внешний усилитель)
- procedure SetCalibration(const C: TCalibration); // вкладка Calibration → FCal + офсет S-метра в DSP
+ procedure SetCalibration(const C: TCalibration); // вкладка Calibration → FCal (офсеты S-метра — в SMeterCalFor)
procedure ApplyWDSPDrive(Pct: Integer); // цифровой drive WDSP: Pluto=1.0 (мощность — атт), HPSDR=Pct/100
// Диапазоны / трансвертеры / спан
@@ -971,7 +976,6 @@ begin
FDSPEngine.OnSliceDemodAudio := OnSliceDemodAudioReady;
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
- FDSPEngine.SMeterCalDB := FCal.SMeterOffsetDB;
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
FBeaconRefHz := 10489750000.0; // центральный BPSK-маяк NB-транспондера (downlink)
@@ -2039,9 +2043,42 @@ begin
end;
function TRadioController.SliceSMeter(Id: Integer): Double;
+// Слайс может стоять на другом диапазоне (паны N кросс-банд), поэтому офсет
+// берём по ЕГО видимой частоте, а не по главному диапазону.
+var idx: Integer;
begin
- if Assigned(FDSPEngine) then Result := FDSPEngine.GetSliceSMeter(Id)
- else Result := -130.0;
+ Result := -130.0;
+ if not Assigned(FDSPEngine) then Exit;
+ Result := FDSPEngine.GetSliceSMeter(Id);
+ idx := FindSliceIndex(Id);
+ if idx >= 0 then Result := Result + SMeterCalFor(FSlices[idx].TargetHz);
+end;
+
+function TRadioController.ReadSMeterDBm: Double;
+begin
+ if not Assigned(FDSPEngine) then Exit(-130.0);
+ Result := FDSPEngine.GetSMeterDBm + SMeterCalFor(ActiveVfoHz);
+end;
+
+function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
+// Трансвертер имеет приоритет над диапазоном: его окно частот — видимые
+// (не IF), и усиление тракта задаёт конвертер/LNB, а не КВ-вход трансивера.
+// Индекс диапазона — по плану активного железа (HF openHPSDR / VHF-UHF Pluto).
+var i, Bi: Integer;
+begin
+ Result := 0.0;
+ if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
+ and FXvtrSettings.Entries[FCurrentXvtr].Enabled
+ and (VisibleHz >= FXvtrSettings.Entries[FCurrentXvtr].FreqBegin)
+ and (VisibleHz <= FXvtrSettings.Entries[FCurrentXvtr].FreqEnd) then
+ Exit(FCal.SMeterXvtrDB[FCurrentXvtr]);
+ for i := 0 to CFG_XVTR_COUNT-1 do
+ if FXvtrSettings.Entries[i].Enabled
+ and (VisibleHz >= FXvtrSettings.Entries[i].FreqBegin)
+ and (VisibleHz <= FXvtrSettings.Entries[i].FreqEnd) then
+ Exit(FCal.SMeterXvtrDB[i]);
+ Bi := FreqToBandIdx(VisibleHz, IsPluto);
+ if (Bi >= 0) and (Bi < CFG_BAND_COUNT) then Result := FCal.SMeterBandDB[Bi];
end;
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
@@ -3567,8 +3604,8 @@ begin
// Санитизация: битый конфиг не должен дать деление на ноль/абсурдные ватты.
FCal.PwrDetModel := EnsureRange(FCal.PwrDetModel, 0, 3);
if FCal.AmpSensMVA <= 0 then FCal.AmpSensMVA := 120.0;
- if Assigned(FDSPEngine) then
- FDSPEngine.SMeterCalDB := FCal.SMeterOffsetDB;
+ // Офсет S-метра берётся из FCal на каждом чтении (SMeterCalFor) — применять
+ // отдельно нечего: он зависит от диапазона/трансвертера, а не от движка.
SaveGlobalSettings; // персист (гейт по FDevConnected внутри)
end;
diff --git a/Settings.pas b/Settings.pas
index d9e4f1d..df2e668 100644
--- a/Settings.pas
+++ b/Settings.pas
@@ -339,7 +339,12 @@ type
SupplyVoltCal: Double; // множитель напряжения питания (1.0 = без коррекции)
AmpVoffMV: Double; // сдвиг нуля датчика тока, мВ (Thetis default 360)
AmpSensMVA: Double; // чувствительность датчика тока, мВ/А (Thetis default 120)
- SMeterOffsetDB: Double; // офсет S-метра, дБ (прибавляется к WDSP dBm)
+ // Офсет S-метра, дБ — раздельно на диапазон и на слот трансвертера (усиление
+ // тракта разное на 160м и на 10м, а у XVTR своё — конвертер/LNB). Смысл
+ // индекса диапазона задаёт band-план железа (HF у openHPSDR, VHF/UHF у
+ // Pluto); таблицы не пересекаются, т.к. секция JSON ключуется по MAC.
+ SMeterBandDB: array[0..CFG_BAND_COUNT-1] of Double;
+ SMeterXvtrDB: array[0..CFG_XVTR_COUNT-1] of Double;
end;
TGlobalSettings = record
@@ -641,7 +646,7 @@ begin
C.SupplyVoltCal := 1.0;
C.AmpVoffMV := 360.0;
C.AmpSensMVA := 120.0;
- C.SMeterOffsetDB := 0.0;
+ // SMeterBandDB/SMeterXvtrDB — нули после FillChar (без коррекции).
end;
constructor TSettingsManager.Create(const FilePath: string);
@@ -769,7 +774,7 @@ end;
function TSettingsManager.LoadDevice(const MAC: array of Byte;
out G: TGlobalSettings;
var Bands: array of TBandSettings): Boolean;
-var MacStr: string; DevObj,GObj,BObj: TJSONObject; i: Integer;
+var MacStr: string; DevObj,GObj,BObj: TJSONObject; i: Integer; SMLegacy: Double;
begin
MacStr := MacToStr(MAC);
Result := FRoot.Find(MacStr) <> nil;
@@ -891,7 +896,15 @@ begin
G.Cal.SupplyVoltCal := EnsureRange(JD(GObj,'cal_supply_volt', 1.0), 0.1, 5.0);
G.Cal.AmpVoffMV := EnsureRange(JD(GObj,'cal_amp_voff_mv', 360.0), 0.0, 2000.0);
G.Cal.AmpSensMVA := EnsureRange(JD(GObj,'cal_amp_sens_mva', 120.0), 1.0, 1000.0);
- G.Cal.SMeterOffsetDB := EnsureRange(JD(GObj,'cal_smeter_db', 0.0), -30.0, 30.0);
+ // S-метр: офсет на диапазон/трансвертер. Легаси-ключ (одно значение на всё
+ // устройство) становится дефолтом всех слотов — показания не меняются.
+ SMLegacy := EnsureRange(JD(GObj,'cal_smeter_db', 0.0), -30.0, 30.0);
+ for i := 0 to CFG_BAND_COUNT-1 do
+ G.Cal.SMeterBandDB[i] :=
+ EnsureRange(JD(GObj,'cal_smeter_band_'+IntToStr(i), SMLegacy), -30.0, 30.0);
+ for i := 0 to CFG_XVTR_COUNT-1 do
+ G.Cal.SMeterXvtrDB[i] :=
+ EnsureRange(JD(GObj,'cal_smeter_xvtr_'+IntToStr(i), SMLegacy), -30.0, 30.0);
for i := 0 to CFG_BAND_COUNT-1 do
begin
@@ -1008,7 +1021,10 @@ begin
JW(O,'cal_supply_volt',G.Cal.SupplyVoltCal);
JW(O,'cal_amp_voff_mv',G.Cal.AmpVoffMV);
JW(O,'cal_amp_sens_mva',G.Cal.AmpSensMVA);
- JW(O,'cal_smeter_db',G.Cal.SMeterOffsetDB);
+ for i := 0 to CFG_BAND_COUNT-1 do
+ JW(O,'cal_smeter_band_'+IntToStr(i),G.Cal.SMeterBandDB[i]);
+ for i := 0 to CFG_XVTR_COUNT-1 do
+ JW(O,'cal_smeter_xvtr_'+IntToStr(i),G.Cal.SMeterXvtrDB[i]);
end;
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
diff --git a/SettingsForm.pas b/SettingsForm.pas
index 5829e39..b842c9b 100644
--- a/SettingsForm.pas
+++ b/SettingsForm.pas
@@ -153,14 +153,31 @@ type
FVHFCalLabels: array[0..CFG_XVTR_COUNT-1] of TLabel;
// ---- Calibration tab controls ----
+ // Таблицы «на диапазон» строятся на 12 слотов (максимум плана); подписи,
+ // видимость и раскладка задаются железом в RelayoutCalTab: у openHPSDR
+ // виден HF-план, у Pluto — VHF/UHF-план, слоты трансвертеров — только
+ // включённые. Значения пишутся в per-device секцию JSON (ключ = MAC),
+ // поэтому таблицы разных аппаратов не пересекаются.
FPageCalib: TScrollBox;
FNavCalib: TFlatButton;
+ FCalGrpDet: TPanel; // Power / SWR detector
+ FCalGrpVA: TPanel; // Supply voltage / current
+ FCalGrpSM: TPanel; // S-meter
FCmbPwrDetModel: TFlatComboBox; // модель детектора Alex-моста
- FEdPADetCal: array[0..10] of TFlatFloatSpinEdit; // множитель FWD/REV на банд
+ FEdPADetCal: array[0..CFG_BAND_COUNT-1] of TFlatFloatSpinEdit; // множитель FWD/REV на банд
+ FLblPADetCal: array[0..CFG_BAND_COUNT-1] of TLabel;
FEdSupplyVoltCal: TFlatFloatSpinEdit; // множитель напряжения питания
FEdAmpVoff: TFlatFloatSpinEdit; // датчик тока: сдвиг нуля, мВ
FEdAmpSens: TFlatFloatSpinEdit; // датчик тока: мВ/А
- FEdSMeterOffset: TFlatFloatSpinEdit; // офсет S-метра, дБ
+ FEdSMBand: array[0..CFG_BAND_COUNT-1] of TFlatFloatSpinEdit; // офсет S-метра на диапазон
+ FLblSMBand: array[0..CFG_BAND_COUNT-1] of TLabel;
+ FEdSMXvtr: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // офсет S-метра на трансвертер
+ FLblSMXvtr: array[0..CFG_XVTR_COUNT-1] of TLabel;
+ FLblSMBandHdr: TLabel;
+ FLblSMXvtrHdr: TLabel;
+ FCalIsPluto: Boolean; // активное железо — AD936x
+ FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
+ FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
FCalConfig: TCalibration;
FOnCalibrationChange: TOnCalibrationChange;
@@ -352,6 +369,7 @@ type
procedure BuildRX1Tab;
procedure BuildPATab;
procedure BuildCalibrationTab;
+ procedure RelayoutCalTab;
procedure CollectCalFromUI;
procedure OnCalAnyChange(Sender: TObject);
procedure BuildCATTab;
@@ -463,6 +481,8 @@ type
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadPlutoTxMaxAtt(MaxAttDb: Double);
procedure LoadCalibration(const C: TCalibration);
+ // Железо вкладки Calibration: band-план и состав групп (Pluto/openHPSDR).
+ procedure SetCalibrationBackend(IsPluto: Boolean);
// Вкладка Antenna: Alex-таблица (openHPSDR) либо таблица разъёмов AD936x.
procedure SetAntennaBackend(IsAD936x, HasAntSwitch: Boolean);
// Таблица разъёмов + имена/видимость слотов трансвертеров.
@@ -569,6 +589,21 @@ const
PA_BAND_NAMES: array[0..10] of string =
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
+ // Раскладка вкладки Calibration — общая для построения (BuildCalibrationTab)
+ // и перекладки под активное железо (RelayoutCalTab).
+ CAL_MARGIN = 22;
+ CAL_PAD = 18;
+ CAL_ROW_H = 32;
+ CAL_R1 = 42;
+ CAL_ED_W = 72;
+ CAL_LBL_W = 54;
+ CAL_SM_LBL_W = 68; // подписи трансвертеров длиннее ('70cm', 'QO-100')
+ CAL_COLS = 4;
+ CAL_GRP_W = 700;
+ CAL_GRP1_H = 236; // Power / SWR detector
+ CAL_GRP2_H = 156; // Supply voltage / current
+ CAL_VA_LBL_W = 220;
+
{ TSettingsForm }
constructor TSettingsForm.Create(AOwner: TComponent);
@@ -1888,20 +1923,11 @@ end;
procedure TSettingsForm.BuildCalibrationTab;
// Вкладка Calibration (паритет с Thetis-Enhanced): модель детектора мощности
// Alex-моста + множитель FWD/REV на банд (Det. Cal.), калибровка напряжения/
-// тока питания (PAVoltCal/AmpVoff/AmpSens) и офсет S-метра.
+// тока питания (PAVoltCal/AmpVoff/AmpSens) и офсет S-метра на диапазон и на
+// слот трансвертера. Позиции/подписи/видимость band-строк ставит RelayoutCalTab
+// под активное железо — здесь только создание контролов.
const
- MARGIN = 22;
- GRP_PAD = 18;
- LBL_W = 54;
- ED_W = 72;
- COL_W = LBL_W + ED_W + 14;
- COLS = 4;
- ROW_H = 32;
- R1 = 42;
- GRP1_H = 236;
- GRP2_H = 156;
- GRP3_H = 92;
- VA_LBL_W = 220;
+ GRP3_H0 = 200; // стартовая высота S-метра, уточняется в RelayoutCalTab
function MakeCalSpin(AParent: TWinControl; ALeft, ATop: Integer;
AMin, AMax, AInc: Double; ADec: Integer;
@@ -1910,7 +1936,7 @@ const
begin
E := TFlatFloatSpinEdit.Create(Self);
E.Parent := AParent;
- E.SetBounds(ALeft, ATop, ED_W, BTN_H + 2);
+ E.SetBounds(ALeft, ATop, CAL_ED_W, BTN_H + 2);
E.Color := CLR_INPUT;
E.Font.Color := CLR_INPUT_TEXT;
E.Font.Name := UI_FONT;
@@ -1927,18 +1953,24 @@ const
var
Grp: TPanel;
Lbl: TLabel;
- i, r, c, X, Y, GTop: Integer;
+ i, GTop: Integer;
begin
// Гард: SetItemIndex у TFlatComboBox стреляет OnChange и при программном
// присвоении — без гарда OnCalAnyChange дёрнется из «ItemIndex := 0» ниже,
// когда остальные контролы вкладки ещё nil → AV в CollectCalFromUI.
FLoading := True;
+ FCalIsPluto := False;
+ for i := 0 to CFG_XVTR_COUNT - 1 do
+ begin
+ FCalXvtrOn[i] := False;
+ FCalXvtrName[i] := '';
+ end;
with TLabel.Create(Self) do
begin
Parent := FPageCalib;
Caption := 'Calibration';
- SetBounds(MARGIN, 18, 400, 28);
+ SetBounds(CAL_MARGIN, 18, 400, 28);
Font.Name := UI_FONT;
Font.Size := 16;
Font.Style := [fsBold];
@@ -1949,7 +1981,7 @@ begin
begin
Parent := FPageCalib;
Caption := 'Power/SWR detector, supply voltage/current and S-meter calibration.';
- SetBounds(MARGIN, 48, 620, 20);
+ SetBounds(CAL_MARGIN, 48, 620, 20);
Font.Name := UI_FONT;
Font.Size := 9;
Font.Color := CLR_TEXTDIM;
@@ -1957,10 +1989,12 @@ begin
// ── Power / SWR detector ────────────────────────────────────────────────
GTop := 86;
- Grp := MakeGroupPanel(FPageCalib, 'Power / SWR detector', MARGIN, GTop, 700, GRP1_H);
+ FCalGrpDet := MakeGroupPanel(FPageCalib, 'Power / SWR detector',
+ CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP1_H);
+ Grp := FCalGrpDet;
- Lbl := MakeLbl(Grp, 'Detector model', GRP_PAD, R1 + 6, 132);
- FCmbPwrDetModel := MakeCombo(Grp, GRP_PAD + 150, R1, 280, OnCalAnyChange);
+ Lbl := MakeLbl(Grp, 'Detector model', CAL_PAD, CAL_R1 + 6, 132);
+ FCmbPwrDetModel := MakeCombo(Grp, CAL_PAD + 150, CAL_R1, 280, OnCalAnyChange);
FCmbPwrDetModel.Items.Add('ANAN-10/100/100B/100D (3.3V)');
FCmbPwrDetModel.Items.Add('ANAN-200D / Orion');
FCmbPwrDetModel.Items.Add('ANAN-7000DLE / Anvelina Pro 3 / G2');
@@ -1970,58 +2004,187 @@ begin
Lbl := MakeLbl(Grp,
'FWD/REV multiplier per band: 1.00 = no correction, >1 if meter under-reads '
+ '(e.g. 88 W shown at real 100 W -> 1.14). SWR is not affected.',
- GRP_PAD, R1 + ROW_H + 10, 650);
+ CAL_PAD, CAL_R1 + CAL_ROW_H + 10, 650);
- for i := 0 to 10 do
+ for i := 0 to CFG_BAND_COUNT - 1 do
begin
- r := i div COLS;
- c := i mod COLS;
- X := GRP_PAD + c * (COL_W + 4);
- Y := R1 + ROW_H + 46 + r * ROW_H;
-
- Lbl := MakeLbl(Grp, PA_BAND_NAMES[i] + ':', X, Y + 5, LBL_W);
-
- FEdPADetCal[i] := MakeCalSpin(Grp, X + LBL_W, Y, 0.50, 2.00, 0.01, 2, 1.00);
+ FLblPADetCal[i] := MakeLbl(Grp, '', 0, 0, CAL_LBL_W);
+ FEdPADetCal[i] := MakeCalSpin(Grp, 0, 0, 0.50, 2.00, 0.01, 2, 1.00);
FEdPADetCal[i].Tag := i;
end;
// ── Supply voltage / current ────────────────────────────────────────────
- GTop := GTop + GRP1_H + 14;
- Grp := MakeGroupPanel(FPageCalib, 'Supply voltage / current', MARGIN, GTop, 700, GRP2_H);
+ GTop := GTop + CAL_GRP1_H + 14;
+ FCalGrpVA := MakeGroupPanel(FPageCalib, 'Supply voltage / current',
+ CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
+ Grp := FCalGrpVA;
- Lbl := MakeLbl(Grp, 'Voltage multiplier (1.000 = no correction)', GRP_PAD, R1 + 6, VA_LBL_W);
- FEdSupplyVoltCal := MakeCalSpin(Grp, GRP_PAD + VA_LBL_W + 10, R1, 0.100, 5.000, 0.001, 3, 1.000);
+ Lbl := MakeLbl(Grp, 'Voltage multiplier (1.000 = no correction)',
+ CAL_PAD, CAL_R1 + 6, CAL_VA_LBL_W);
+ FEdSupplyVoltCal := MakeCalSpin(Grp, CAL_PAD + CAL_VA_LBL_W + 10, CAL_R1,
+ 0.100, 5.000, 0.001, 3, 1.000);
- Lbl := MakeLbl(Grp, 'Current sensor zero offset (mV)', GRP_PAD, R1 + ROW_H + 6, VA_LBL_W);
- FEdAmpVoff := MakeCalSpin(Grp, GRP_PAD + VA_LBL_W + 10, R1 + ROW_H, 0.0, 2000.0, 1.0, 0, 360.0);
+ Lbl := MakeLbl(Grp, 'Current sensor zero offset (mV)',
+ CAL_PAD, CAL_R1 + CAL_ROW_H + 6, CAL_VA_LBL_W);
+ FEdAmpVoff := MakeCalSpin(Grp, CAL_PAD + CAL_VA_LBL_W + 10, CAL_R1 + CAL_ROW_H,
+ 0.0, 2000.0, 1.0, 0, 360.0);
- Lbl := MakeLbl(Grp, 'Current sensor sensitivity (mV/A)', GRP_PAD, R1 + 2 * ROW_H + 6, VA_LBL_W);
- FEdAmpSens := MakeCalSpin(Grp, GRP_PAD + VA_LBL_W + 10, R1 + 2 * ROW_H, 1.0, 1000.0, 0.1, 1, 120.0);
+ Lbl := MakeLbl(Grp, 'Current sensor sensitivity (mV/A)',
+ CAL_PAD, CAL_R1 + 2 * CAL_ROW_H + 6, CAL_VA_LBL_W);
+ FEdAmpSens := MakeCalSpin(Grp, CAL_PAD + CAL_VA_LBL_W + 10, CAL_R1 + 2 * CAL_ROW_H,
+ 1.0, 1000.0, 0.1, 1, 120.0);
Lbl := MakeLbl(Grp, 'Defaults 360 mV / 120 mV/A (ACS712-style, Orion MkII)',
- GRP_PAD + VA_LBL_W + 10 + ED_W + 14, R1 + ROW_H + 6, 320);
+ CAL_PAD + CAL_VA_LBL_W + 10 + CAL_ED_W + 14,
+ CAL_R1 + CAL_ROW_H + 6, 320);
// ── S-meter ─────────────────────────────────────────────────────────────
- GTop := GTop + GRP2_H + 14;
- Grp := MakeGroupPanel(FPageCalib, 'S-meter', MARGIN, GTop, 700, GRP3_H);
+ GTop := GTop + CAL_GRP2_H + 14;
+ FCalGrpSM := MakeGroupPanel(FPageCalib, 'S-meter',
+ CAL_MARGIN, GTop, CAL_GRP_W, GRP3_H0);
+ Grp := FCalGrpSM;
- Lbl := MakeLbl(Grp, 'Offset (dB), added to all S-meter readings', GRP_PAD, R1 + 6, VA_LBL_W);
- FEdSMeterOffset := MakeCalSpin(Grp, GRP_PAD + VA_LBL_W + 10, R1, -30.0, 30.0, 0.1, 1, 0.0);
+ FLblSMBandHdr := MakeLbl(Grp,
+ 'Offset (dB) added to S-meter readings — per band (+ = meter reads higher).',
+ CAL_PAD, 30, 650);
+
+ for i := 0 to CFG_BAND_COUNT - 1 do
+ begin
+ FLblSMBand[i] := MakeLbl(Grp, '', 0, 0, CAL_SM_LBL_W);
+ FEdSMBand[i] := MakeCalSpin(Grp, 0, 0, -30.0, 30.0, 0.1, 1, 0.0);
+ FEdSMBand[i].Tag := i;
+ end;
+
+ FLblSMXvtrHdr := MakeLbl(Grp, 'Per transverter slot', CAL_PAD, 0, 300);
+
+ for i := 0 to CFG_XVTR_COUNT - 1 do
+ begin
+ FLblSMXvtr[i] := MakeLbl(Grp, '', 0, 0, CAL_SM_LBL_W);
+ FLblSMXvtr[i].AutoSize := False;
+ FLblSMXvtr[i].ShowHint := True;
+ FEdSMXvtr[i] := MakeCalSpin(Grp, 0, 0, -30.0, 30.0, 0.1, 1, 0.0);
+ FEdSMXvtr[i].Tag := i;
+ end;
+
+ RelayoutCalTab; // стартовая раскладка (HF-план, трансвертеры скрыты)
FLoading := False;
end;
+procedure TSettingsForm.RelayoutCalTab;
+// Показывает только то, что есть у активного железа: band-план (HF у openHPSDR,
+// VHF/UHF у Pluto), из трансвертеров — включённые слоты. Детектор мощности и
+// датчики питания — свойства Alex-моста openHPSDR, у AD936x такой телеметрии
+// нет, поэтому обе группы прячутся, а S-метр поднимается наверх.
+var
+ i, r, c, X, Y, GTop, BandRows, XvtrRows, Slot, H: Integer;
+ Nm: string;
+begin
+ // Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
+ if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
+ or (FLblSMXvtrHdr = nil) then Exit;
+
+ // ---- строки диапазонов: позиция по индексу плана, пустые слоты скрыты ----
+ BandRows := 0;
+ for i := 0 to CFG_BAND_COUNT - 1 do
+ begin
+ Nm := BandName(i, FCalIsPluto);
+ r := i div CAL_COLS;
+ c := i mod CAL_COLS;
+
+ X := CAL_PAD + c * (CAL_LBL_W + CAL_ED_W + 18);
+ Y := CAL_R1 + CAL_ROW_H + 46 + r * CAL_ROW_H;
+ if FLblPADetCal[i] <> nil then
+ begin
+ FLblPADetCal[i].Caption := Nm + ':';
+ FLblPADetCal[i].SetBounds(X, Y + 5, CAL_LBL_W, 18);
+ FLblPADetCal[i].Visible := Nm <> '';
+ end;
+ if FEdPADetCal[i] <> nil then
+ begin
+ FEdPADetCal[i].SetBounds(X + CAL_LBL_W, Y, CAL_ED_W, BTN_H + 2);
+ FEdPADetCal[i].Visible := Nm <> '';
+ end;
+
+ X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
+ Y := 52 + r * CAL_ROW_H;
+ if FLblSMBand[i] <> nil then
+ begin
+ FLblSMBand[i].Caption := Nm + ':';
+ FLblSMBand[i].SetBounds(X, Y + 5, CAL_SM_LBL_W, 18);
+ FLblSMBand[i].Visible := Nm <> '';
+ end;
+ if FEdSMBand[i] <> nil then
+ begin
+ FEdSMBand[i].SetBounds(X + CAL_SM_LBL_W, Y, CAL_ED_W, BTN_H + 2);
+ FEdSMBand[i].Visible := Nm <> '';
+ end;
+ if Nm <> '' then BandRows := r + 1;
+ end;
+
+ // ---- строки трансвертеров: только включённые слоты, без дырок ----
+ Y := 52 + BandRows * CAL_ROW_H + 6;
+ FLblSMXvtrHdr.SetBounds(CAL_PAD, Y, 300, 18);
+ Slot := 0;
+ for i := 0 to CFG_XVTR_COUNT - 1 do
+ begin
+ if FCalXvtrOn[i] then
+ begin
+ r := Slot div CAL_COLS;
+ c := Slot mod CAL_COLS;
+ X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
+ if FCalXvtrName[i] <> '' then Nm := FCalXvtrName[i]
+ else Nm := 'XVTR' + IntToStr(i);
+ FLblSMXvtr[i].Caption := Nm + ':';
+ FLblSMXvtr[i].Hint := 'XVTR' + IntToStr(i) + ' (' + Nm + ')';
+ FLblSMXvtr[i].SetBounds(X, Y + 24 + r * CAL_ROW_H + 5, CAL_SM_LBL_W, 18);
+ FEdSMXvtr[i].SetBounds(X + CAL_SM_LBL_W, Y + 24 + r * CAL_ROW_H,
+ CAL_ED_W, BTN_H + 2);
+ Inc(Slot);
+ end;
+ FLblSMXvtr[i].Visible := FCalXvtrOn[i];
+ FEdSMXvtr[i].Visible := FCalXvtrOn[i];
+ end;
+ XvtrRows := (Slot + CAL_COLS - 1) div CAL_COLS;
+ FLblSMXvtrHdr.Visible := Slot > 0;
+
+ if Slot > 0 then H := Y + 24 + XvtrRows * CAL_ROW_H + 12
+ else H := Y + 12;
+
+ // ---- вертикальная укладка групп ----
+ GTop := 86;
+ FCalGrpDet.Visible := not FCalIsPluto;
+ FCalGrpVA.Visible := not FCalIsPluto;
+ if not FCalIsPluto then
+ begin
+ FCalGrpDet.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP1_H);
+ GTop := GTop + CAL_GRP1_H + 14;
+ FCalGrpVA.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
+ GTop := GTop + CAL_GRP2_H + 14;
+ end;
+ FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, H);
+end;
+
+procedure TSettingsForm.SetCalibrationBackend(IsPluto: Boolean);
+begin
+ FCalIsPluto := IsPluto;
+ RelayoutCalTab;
+end;
+
procedure TSettingsForm.CollectCalFromUI;
var i: Integer;
begin
FCalConfig.PwrDetModel := FCmbPwrDetModel.ItemIndex;
- // UI редактирует HF-банды 0..10; слот 11 (пустой в HF-плане) не трогаем.
- for i := 0 to 10 do
- FCalConfig.PADetCal[i] := FEdPADetCal[i].Value;
+ for i := 0 to CFG_BAND_COUNT - 1 do
+ begin
+ if FEdPADetCal[i] <> nil then FCalConfig.PADetCal[i] := FEdPADetCal[i].Value;
+ if FEdSMBand[i] <> nil then FCalConfig.SMeterBandDB[i] := FEdSMBand[i].Value;
+ end;
+ for i := 0 to CFG_XVTR_COUNT - 1 do
+ if FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
FCalConfig.SupplyVoltCal := FEdSupplyVoltCal.Value;
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
- FCalConfig.SMeterOffsetDB := FEdSMeterOffset.Value;
end;
procedure TSettingsForm.OnCalAnyChange(Sender: TObject);
@@ -2059,12 +2222,16 @@ begin
try
FCalConfig := C;
FCmbPwrDetModel.ItemIndex := EnsureRange(C.PwrDetModel, 0, 3);
- for i := 0 to 10 do
- FEdPADetCal[i].Value := C.PADetCal[i];
+ for i := 0 to CFG_BAND_COUNT - 1 do
+ begin
+ if FEdPADetCal[i] <> nil then FEdPADetCal[i].Value := C.PADetCal[i];
+ if FEdSMBand[i] <> nil then FEdSMBand[i].Value := C.SMeterBandDB[i];
+ end;
+ for i := 0 to CFG_XVTR_COUNT - 1 do
+ if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
FEdAmpVoff.Value := C.AmpVoffMV;
FEdAmpSens.Value := C.AmpSensMVA;
- FEdSMeterOffset.Value := C.SMeterOffsetDB;
finally
FLoading := False;
end;
@@ -4012,7 +4179,11 @@ begin
FVHFCalLabels[i].Caption := 'VHF' + IntToStr(i) + ':';
FVHFCalLabels[i].Hint := FVHFCalLabels[i].Caption;
end;
+ // Офсеты S-метра на вкладке Calibration — только по включённым слотам.
+ FCalXvtrOn[i] := X.Entries[i].Enabled;
+ FCalXvtrName[i] := X.Entries[i].ButtonText;
end;
+ RelayoutCalTab;
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
LoadQOFromConfig; // QO100_SLOT → отдельная страница QO-100
finally
diff --git a/WDSPEngine.pas b/WDSPEngine.pas
index 2485085..b966605 100644
--- a/WDSPEngine.pas
+++ b/WDSPEngine.pas
@@ -451,7 +451,6 @@ type
FTXMode: Integer; // may differ from RX when a slice is TX source
FRawFMDeviation: Double;
FSMeter: Double;
- FSMeterCalDB: Double; // калибровочный офсет S-метра, дБ (вкладка Calibration)
FFilterLow: Integer;
FFilterHigh: Integer;
FAGCMode: TWDSPAGCMode;
@@ -744,8 +743,6 @@ type
property SNBEnabled: Boolean read FSNBEnabled;
property LastError: string read FLastError;
property SMeter: Double read FSMeter;
- // Офсет S-метра из вкладки Calibration; применяется в GetSMeterDBm/GetSliceSMeter
- property SMeterCalDB: Double read FSMeterCalDB write FSMeterCalDB;
property FFTSize: Integer read FFFTSize;
property WindowType: Integer read FWindowType;
property SpecDetector: Integer read FSpecDetector;
@@ -1248,7 +1245,6 @@ begin
FAGCTop := -90.0;
FVolume := 0.7;
FSMeter := -130.0;
- FSMeterCalDB := 0.0;
FNRMode := NR_MODE_OFF;
FNBMode := NB_MODE_OFF;
FSNBEnabled := False;
@@ -2831,7 +2827,7 @@ begin
FSliceLock.Enter;
try
idx := FindSliceIdx(Id);
- if idx >= 0 then Result := FSlices[idx].SMeter + FSMeterCalDB;
+ if idx >= 0 then Result := FSlices[idx].SMeter;
finally
FSliceLock.Leave;
end;
@@ -3884,7 +3880,10 @@ function TWDSPEngine.GetSMeterDBm: Double;
begin
// НЕ вызываем GetRXAMeter здесь повторно — значение уже обновлено
// в DSP-колбэке. Повторный вызов сбрасывает аккумулятор WDSP → 0.
- Result := FSMeter + FSMeterCalDB;
+ // Калибровочный офсет здесь НЕ прибавляется: он зависит от диапазона/
+ // трансвертера, о которых движок не знает — его добавляет TRadioController
+ // (ReadSMeterDBm / SliceSMeter).
+ Result := FSMeter;
end;
// ---------------------------------------------------------------------------
diff --git a/ewsdr.lpi b/ewsdr.lpi
index 82176c3..2c969dc 100644
--- a/ewsdr.lpi
+++ b/ewsdr.lpi
@@ -17,9 +17,9 @@
-
+
-
+
@@ -126,6 +126,8 @@
+
+
diff --git a/ewsdrd.lpr b/ewsdrd.lpr
index 1af8123..7049ac8 100644
--- a/ewsdrd.lpr
+++ b/ewsdrd.lpr
@@ -466,7 +466,7 @@ begin
// не обновляет в headless — освежаем здесь, как GUI-таймер (MainForm). Без
// этого PushState слал бы клиентам залипший -130 dBm.
if FCtrl.FRunning and FCtrl.FWDSPReady then
- FCtrl.FLastSMeter := FCtrl.FDSPEngine.GetSMeterDBm;
+ FCtrl.FLastSMeter := FCtrl.ReadSMeterDBm;
FWebAd.PushState; // исходящее зеркало состояния/спектра
lastPush := nowMs;
end;