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;