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
+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;