feature(calibration): офсет S-метра на диапазон и на трансвертер, раздельно по железу

Вместо одного офсета на всё устройство — таблицы «на диапазон» и «на слот
трансвертера»; состав таблиц задаёт активное железо.

- Settings: SMeterOffsetDB -> SMeterBandDB[12] / SMeterXvtrDB[8] (JSON
  cal_smeter_band_i / cal_smeter_xvtr_i). Легаси-ключ cal_smeter_db становится
  дефолтом всех слотов — показания после обновления не меняются. Разделение
  Pluto/openHPSDR даёт сама секция JSON: она ключуется по MAC устройства
  (у Pluto — synthetic MAC из serial).
- Офсет убран из WDSPEngine (FSMeterCalDB): движок не знает ни диапазонов, ни
  трансвертеров. Теперь TRadioController.SMeterCalFor(VisibleHz) — окно
  включённого XVTR-слота приоритетнее диапазона, индекс диапазона по плану
  железа (FreqToBandIdx с IsPluto); ReadSMeterDBm для главного S-метра
  (MainForm + ewsdrd) и SliceSMeter по TargetHz слайса — паны N на других
  диапазонах считаются по своему банду, а не по главному.
- SettingsForm: таблицы на 12 слотов, подписи/видимость/раскладку ставит
  RelayoutCalTab; SetCalibrationBackend(IsPluto) вызывается рядом с
  SetAntennaBackend. У openHPSDR — HF-план, у Pluto — VHF/UHF-план и скрытые
  группы Power/SWR detector и Supply V/A (нет Alex-моста и его телеметрии).
  Слоты трансвертеров показываются только включённые, с их именами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 11:02:04 +03:00
co-authored by Claude Opus 4.8
parent ced42fbac9
commit 797bfc2e7c
7 changed files with 301 additions and 73 deletions
+4 -1
View File
@@ -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);
+43 -6
View File
@@ -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;
+21 -5
View File
@@ -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;
+223 -52
View File
@@ -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
+5 -6
View File
@@ -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;
// ---------------------------------------------------------------------------
+4 -2
View File
@@ -17,9 +17,9 @@
<UseVersionInfo Value="True"/>
<AutoIncrementBuild Value="True"/>
<MinorVersionNr Value="9"/>
<BuildNr Value="276"/>
<BuildNr Value="277"/>
</VersionInfo>
<MacroValues Count="106">
<MacroValues Count="108">
<Macro1 Name="LCLWidgetType" Value="qt6"/>
<Macro2 Name="LCLWidgetType" Value="qt6"/>
<Macro3 Name="LCLWidgetType" Value="qt6"/>
@@ -126,6 +126,8 @@
<Macro104 Name="LCLWidgetType" Value="qt6"/>
<Macro105 Name="LCLWidgetType" Value="qt6"/>
<Macro106 Name="LCLWidgetType" Value="qt6"/>
<Macro107 Name="LCLWidgetType" Value="qt6"/>
<Macro108 Name="LCLWidgetType" Value="qt6"/>
</MacroValues>
<BuildModes>
<Item Name="Debug" Default="True"/>
+1 -1
View File
@@ -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;