feature(settings): вкладка Calibration — детектор мощности/КСВ, V/A, S-метр

Паритет с Thetis-Enhanced (Det. Cal. / Volts&Amps правки):
- TCalibration в TGlobalSettings (JSON cal_*): модель детектора Alex-моста,
  per-band множитель FWD/REV 0.5..2.0, множитель напряжения, Voff/Sens
  датчика тока, офсет S-метра.
- AlexDetToWatts: константы Thetis computeAlexFwdPower/computeRefPower по
  моделям (ANAN-100 / 200D / 7000DLE-Anvelina Pro 3 / 8000DLE-Orion MkII,
  6m-спецкейс REV). Дефолт = прежняя формула ANAN-100 (поведение не меняется).
- OnHPStatus: FWD и REV через модельную формулу x множитель банда;
  КСВ = (1+p)/(1-p), p=sqrt(RevW/FwdW) (Thetis) вместо отношения сырых ADC;
  напряжение x SupplyVoltCal; ток с калибруемыми Voff/Sens.
- WDSPEngine.SMeterCalDB: офсет в GetSMeterDBm/GetSliceSMeter (главный
  S-метр, slice-флаги, web).
- Выбор модели в комбо подставляет модельные дефолты датчика тока
  (Anvelina Pro 3/7000DLE: 340 мВ / 88 мВ/А — Thetis GetDefaultVoltCalibration).
- Фиксы: гард FLoading при постройке вкладки (SetItemIndex стреляет OnChange
  на программном присвоении -> AV на nil-контролах), FPageCalib/FNavCalib
  в списках SelectPage (вкладка была невидимой).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 14:35:34 +03:00
co-authored by Claude Fable 5
parent a7e154b959
commit c645b0d1e6
6 changed files with 381 additions and 20 deletions
+41 -7
View File
@@ -375,6 +375,10 @@ type
FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings;
FPABandCal: array[0..CFG_BAND_COUNT-1] of Double;
FVHFBandCal: array[0..CFG_XVTR_COUNT-1] of Double;
// Калибровки (детектор мощности/КСВ, напряжение/ток, S-метр) — вкладка
// Calibration. Читается сетевым потоком в OnHPStatus (double-поля, torn
// read безопасен как у FPABandCal); менять через SetCalibration.
FCal: TCalibration;
FDevMAC: array[0..5] of Byte;
// Глобальные настройки устройства, загруженные ConnectDevice. Хранятся для
// UI-рендера полей, которых нет в контроллере (DisplayFPS/FreqMhzDigits/CAT).
@@ -681,6 +685,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 ApplyWDSPDrive(Pct: Integer); // цифровой drive WDSP: Pluto=1.0 (мощность — атт), HPSDR=Pct/100
// Диапазоны / трансвертеры / спан
@@ -943,6 +948,7 @@ begin
FFMStepOn := True;
FRawFMDeviation := RAW_FM_DEV_DEFAULT;
FLocalAudio := True; // GUI: звук на локальную карту; демон выключит явно
TSettingsManager.DefaultCalibration(FCal);
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
FLastPlutoRSSIdB := TELEMETRY_NONE;
@@ -965,6 +971,7 @@ 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)
@@ -1186,10 +1193,12 @@ procedure TRadioController.OnHPStatus(const Status: THighPriorityStatus);
const
SWR_MIN_FWD_W = 1.0; // ниже — fwd это ADC-шум, SWR не считаем
SWR_CAP = 9.9;
BAND_6M = 10; // индекс 6m в HF-плане (160m..6m)
var
FwdPwr, RevPwr: Word;
SupplyV, SupplyA, FwdW, SWRV, Rho: Double;
IsTx: Boolean;
SupplyV, SupplyA, FwdW, RevW, SWRV, Rho, DetK: Double;
Bi: Integer;
Is6m, IsTx: Boolean;
begin
FwdPwr := (Status.FwdPwrAlex0Hi shl 8) or Status.FwdPwrAlex0Lo;
RevPwr := (Status.RevPwrAlex0Hi shl 8) or Status.RevPwrAlex0Lo;
@@ -1208,19 +1217,31 @@ begin
SupplyV := ADCToSupplyVolts(
(Status.SupplyVoltsHi shl 8) or Status.SupplyVoltsLo,
FNetwork.Device.BoardType);
if SupplyV >= 0 then SupplyV := SupplyV * FCal.SupplyVoltCal;
if BoardSupportsSupplyCurrent(FNetwork.Device.BoardType) then
SupplyA := ADCToSupplyCurrent(
(Status.UserADC1Hi shl 8) or Status.UserADC1Lo,
FNetwork.Device.BoardType);
FNetwork.Device.BoardType,
FCal.AmpVoffMV, FCal.AmpSensMVA);
end;
FwdW := ADCToWatts100(FwdPwr);
// Детектор Alex-моста: константы по модели (вкладка Calibration) + per-band
// множитель. Множитель общий для FWD/REV, поэтому КСВ от него не зависит
// (как Thetis PABandCalMult).
Bi := TxBandIdx;
Is6m := Bi = BAND_6M;
DetK := 1.0;
if (Bi >= 0) and (Bi < CFG_BAND_COUNT) and (FCal.PADetCal[Bi] > 0) then
DetK := FCal.PADetCal[Bi];
FwdW := AlexDetToWatts(FwdPwr, FCal.PwrDetModel, False, Is6m) * DetK;
RevW := AlexDetToWatts(RevPwr, FCal.PwrDetModel, True, Is6m) * DetK;
// FTransmitting — программный флаг; HPS_PTT — вход HW-PTT (footswitch/mic).
IsTx := FTransmitting or ((Status.StatusBits and HPS_PTT) <> 0);
// SWR = (1+ρ)/(1-ρ), ρ = adc_r/adc_f; только при значимой fwd-мощности.
if IsTx and (FwdW >= SWR_MIN_FWD_W) and (FwdPwr > 0) then
// SWR = (1+ρ)/(1-ρ), ρ = sqrt(RevW/FwdW) (Thetis); только при значимой
// fwd-мощности.
if IsTx and (FwdW >= SWR_MIN_FWD_W) then
begin
Rho := RevPwr / FwdPwr;
Rho := Sqrt(RevW / FwdW);
if Rho >= 0.99 then
SWRV := SWR_CAP
else
@@ -2794,6 +2815,7 @@ begin
Result.PlutoTxMaxAttDb := FPlutoTxMaxAttDb;
for i := 0 to CFG_BAND_COUNT - 1 do Result.PABandCal[i] := FPABandCal[i];
for i := 0 to CFG_XVTR_COUNT - 1 do Result.VHFBandCal[i] := FVHFBandCal[i];
Result.Cal := FCal;
Result.ActiveVfo := FActiveVfo;
Result.NRMode := FNRMode;
Result.NBMode := FNBMode;
@@ -3539,6 +3561,17 @@ begin
SaveGlobalSettings; // персист потолка (per-device)
end;
procedure TRadioController.SetCalibration(const C: TCalibration);
begin
FCal := C;
// Санитизация: битый конфиг не должен дать деление на ноль/абсурдные ватты.
FCal.PwrDetModel := EnsureRange(FCal.PwrDetModel, 0, 3);
if FCal.AmpSensMVA <= 0 then FCal.AmpSensMVA := 120.0;
if Assigned(FDSPEngine) then
FDSPEngine.SMeterCalDB := FCal.SMeterOffsetDB;
SaveGlobalSettings; // персист (гейт по FDevConnected внутри)
end;
procedure TRadioController.ApplyWDSPDrive(Pct: Integer);
// На Pluto мощность задаётся ОДИН раз — железной аттенюацией (PlutoTxAttForDrive),
// поэтому цифровой уровень WDSP держим на максимуме (1.0), а не режем по drive%.
@@ -5018,6 +5051,7 @@ begin
FPABandCal[i] := FLoadedGlobal.PABandCal[i];
for i := 0 to CFG_XVTR_COUNT - 1 do
FVHFBandCal[i] := FLoadedGlobal.VHFBandCal[i];
SetCalibration(FLoadedGlobal.Cal);
// drive% (0..100) — раньше грузился через TrkDrive.Position; теперь напрямую,
// слайдер выставит rfDevice-рендер. Калиброванный байт считаем после FCurrentBand.
FDrivePercent := EnsureRange(FLoadedGlobal.DriveLevel, 0, 100);