Phase 5 (batch 25b): move HP-status callback into the controller

OnHPStatusCB + DoUpdateStatus move into TRadioController as OnHPStatus
(network thread: board-specific supply/power/SWR decode -> pending fields
-> Invoke) and ApplyHPStatus (marshalled: PEP-style fwd/SWR ballistics +
supply EMA into FLast*, ADC-overload via Changed(rfADCOverload), and the
HW-PTT edge -> SetMOX). FNetwork.OnHPStatus is wired to the controller;
the ADC-overload indicator renders via OnControllerState(rfADCOverload).
The HWPTT->TX path is now headless-capable and reads an always-current
FWebClientActive for mic-source. TStatusUISync is left dead in UISync.pas
(unit still used for TDDCSeqSync) pending a separate cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 23:35:35 +03:00
co-authored by Claude Opus 4.8
parent 9d2324cc6c
commit c1da506f73
2 changed files with 155 additions and 165 deletions
+10 -165
View File
@@ -351,7 +351,6 @@ type
// Network callbacks
procedure OnDeviceFound(const Dev: THPSDRDevice);
procedure OnHPStatusCB(const Status: THighPriorityStatus);
procedure OnWidebandCB(ADCIndex: Integer; const Samples: array of SmallInt;
Count: Integer);
procedure OnWDSPOpenDone(Success: Boolean);
@@ -364,8 +363,6 @@ type
// Public UI update (called from sync objects)
procedure DoAddDevice(const Dev: THPSDRDevice; const Entry: string);
procedure DoUpdateStatus(FwdW, SWRV, SupplyV, SupplyA: Double; PLLLock, HWPTT: Boolean;
ADCOverload: Byte);
procedure UpdateTXMeters;
procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
procedure DoUpdateDDCSeqOrNoDevice(DDCIdx: Integer; Seq: LongWord);
@@ -1139,7 +1136,7 @@ begin
// startup-preview выше, поэтому FDSPEngine рождается с нужной частотой.
FController.CreateEngines(FController.FSampleRate);
FController.FNetwork.OnDeviceFound := OnDeviceFound;
FController.FNetwork.OnHPStatus := OnHPStatusCB;
FController.FNetwork.OnHPStatus := FController.OnHPStatus;
FController.FNetwork.OnDDCIQ := FController.OnDDCIQ;
FController.FNetwork.OnMicPacket := FController.OnMicPacket;
FController.FNetwork.OnWideband := OnWidebandCB;
@@ -1317,6 +1314,12 @@ begin
end;
rfTuning:
StyleButton(BtnTUN, FController.FTuning);
rfADCOverload:
if FSpecView <> nil then
begin
FSpecView.ADCOverloadVisible := FController.FADCOverloadActive;
if PbSpectrum <> nil then PbSpectrum.Invalidate;
end;
rfBand:
// В XVTR-режиме HF-кнопки гасим (активна XVTR-кнопка, см. rfXvtr).
for i := 0 to BAND_COUNT - 1 do
@@ -3281,169 +3284,11 @@ begin
SetStatusText(2, Format('Found: %s', [Dev.IPAddress]));
end;
procedure TMainForm.OnHPStatusCB(const Status: THighPriorityStatus);
const
// Порог fwd-мощности (W), ниже которого SWR не вычисляем — там всё ADC-шум.
SWR_MIN_FWD_W = 1.0;
// Верхняя граница SWR (выше — это всё равно «беда»).
SWR_CAP = 9.9;
var
ExcPwr, FwdPwr, RevPwr: Word;
SupplyV, SupplyA, FwdW, SWRV, Rho: Double;
IsTx: Boolean;
Sync: TStatusUISync;
M: TThreadMethod;
begin
ExcPwr := (Status.ExciterPwr0Hi shl 8) or Status.ExciterPwr0Lo;
FwdPwr := (Status.FwdPwrAlex0Hi shl 8) or Status.FwdPwrAlex0Lo;
RevPwr := (Status.RevPwrAlex0Hi shl 8) or Status.RevPwrAlex0Lo;
SupplyV := -1.0;
SupplyA := -1.0;
// Board 5 (Orion MkII): voltage on UserADC0 (bytes 57-58),
// current on UserADC1 (bytes 55-56) — matching Thetis behaviour.
// Other supported boards report voltage in SupplyVolts bytes and no current.
if BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
begin
if FController.FNetwork.Device.BoardType = 5 then
SupplyV := ADCToSupplyVolts(
(Status.UserADC0Hi shl 8) or Status.UserADC0Lo,
FController.FNetwork.Device.BoardType)
else
SupplyV := ADCToSupplyVolts(
(Status.SupplyVoltsHi shl 8) or Status.SupplyVoltsLo,
FController.FNetwork.Device.BoardType);
if BoardSupportsSupplyCurrent(FController.FNetwork.Device.BoardType) then
SupplyA := ADCToSupplyCurrent(
(Status.UserADC1Hi shl 8) or Status.UserADC1Lo,
FController.FNetwork.Device.BoardType);
end;
// FController.FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
// ExciterPwr это мощность TX, не уровень принятого сигнала.
FwdW := ADCToWatts100(FwdPwr);
// FController.FTransmitting — наш программный флаг (MOX/TUN/HW-PTT через ApplyMOX).
// HPS_PTT-бит в StatusBits — это вход аппаратного PTT (footswitch/mic),
// а не «трансивер сейчас передаёт», поэтому только на него полагаться нельзя.
IsTx := FController.FTransmitting or ((Status.StatusBits and HPS_PTT) <> 0);
// SWR: правильная формула (1+ρ)/(1-ρ), считаем только когда есть значимая
// прямая мощность — иначе ADC-шум RevPwr даёт случайные «прыжки» SWR.
// Не на передаче — SWR не показываем (фиксируем 1.0), Fwd принудительно 0.
if IsTx and (FwdW >= SWR_MIN_FWD_W) and (FwdPwr > 0) then
begin
// ρ = sqrt(Pr/Pf). Поскольку P=V²/R и V пропорционально ADC, то
// sqrt(Pr/Pf) = adc_r/adc_f напрямую — лишнего sqrt не нужно.
Rho := RevPwr / FwdPwr;
if Rho >= 0.99 then
SWRV := SWR_CAP
else
begin
SWRV := (1.0 + Rho) / (1.0 - Rho);
if SWRV > SWR_CAP then SWRV := SWR_CAP;
if SWRV < 1.0 then SWRV := 1.0;
end;
end else
SWRV := 1.0;
if not IsTx then FwdW := 0;
Sync := TStatusUISync.Create(DoUpdateStatus, FwdW, SWRV, SupplyV, SupplyA,
(Status.StatusBits and HPS_PLL_LOCKED) <> 0,
(Status.StatusBits and HPS_PTT) <> 0,
Status.ADCOverload);
try
M := Sync.Execute;
TThread.Synchronize(nil, M);
finally
Sync.Free;
end;
end;
procedure TMainForm.DoUpdateStatus(FwdW, SWRV, SupplyV, SupplyA: Double; PLLLock, HWPTT: Boolean;
ADCOverload: Byte);
const
// Thetis/piHPSDR PEP-style баллистика для FwdW и SWR: атака мгновенная
// (peak-hold), спад медленный — стрелка «висит» на пике и плавно опускается.
// Это маскирует огибающую SSB-голоса, которая на ADC прыгает на 10-20 дБ
// внутри слога. На rate ~150 пакетов/с эти α дают τ_decay ≈ 350 мс / 500 мс.
DECAY_FWD = 0.02;
DECAY_SWR = 0.015;
// Напряжение питания меняется медленно — обычное симметричное EMA.
ALPHA_SUPPLY = 0.05;
begin
// Запоминаем значения; вывод в UI делает MeterTimerTick на 10 Гц.
// Сама обработка здесь идёт на UI-потоке через Synchronize.
// FwdW=0 / SWRV=1 — это «не на передаче»: снапим без decay,
// чтобы метры моментально падали при отпускании PTT.
if FwdW <= 0 then
FController.FLastFwdW := 0
else if FwdW > FController.FLastFwdW then
FController.FLastFwdW := FwdW // attack: instant
else
FController.FLastFwdW := DECAY_FWD * FwdW + (1.0 - DECAY_FWD) * FController.FLastFwdW;
if SWRV <= 1.0 then
FController.FLastSWR := 1.0
else if SWRV > FController.FLastSWR then
FController.FLastSWR := SWRV // attack: instant
else
FController.FLastSWR := DECAY_SWR * SWRV + (1.0 - DECAY_SWR) * FController.FLastSWR;
if SupplyV >= 0 then
begin
if FController.FLastSupplyV < 0 then
FController.FLastSupplyV := SupplyV // первое значение — без сглаживания
else
FController.FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FController.FLastSupplyV;
end else
FController.FLastSupplyV := -1.0;
if SupplyA >= 0 then
begin
if FController.FLastSupplyA < 0 then
FController.FLastSupplyA := SupplyA
else
FController.FLastSupplyA := ALPHA_SUPPLY * SupplyA + (1.0 - ALPHA_SUPPLY) * FController.FLastSupplyA;
end else
FController.FLastSupplyA := -1.0;
FController.FLastPLLLock := PLLLock;
if (FSpecView <> nil) and (FSpecView.ADCOverloadVisible <> (ADCOverload <> 0)) then
begin
FSpecView.ADCOverloadVisible := ADCOverload <> 0;
if PbSpectrum <> nil then
PbSpectrum.Invalidate;
end;
// Hardware PTT (foot switch / mic PTT) — обнаружение фронта (нужно делать
// на rate HP Status, а не таймера, чтобы не пропустить короткое нажатие).
if HWPTT <> FController.FHWPTTActive then
begin
FController.FHWPTTActive := HWPTT;
if not FController.FTuning then
begin
if HWPTT then
begin
// Press: не перекрываем ручной MOX; запоминаем что мы включили TX.
if not BtnMOX.Active then
begin
ApplyMOX(True);
FController.FHWPTTStartedTX := True;
end;
end else
begin
// Release: выключаем TX только если он был включён через HWPTT.
// Нельзя проверять BtnMOX.Active — ApplyMOX(True) сам его выставляет.
if FController.FHWPTTStartedTX then
begin
FController.FHWPTTStartedTX := False;
ApplyMOX(False);
end;
end;
end;
end;
end;
procedure TMainForm.UpdateTXMeters;
// Вызывается из MeterTimerTick (10 Гц). Использует FController.FLastFwdW/FController.FLastSWR/
// FController.FLastSupplyV/FController.FLastSupplyA обновляются на rate HP Status (~150/с)
// в DoUpdateStatus.
// Вызывается из MeterTimerTick (10 Гц). FController.FLastFwdW/FLastSWR/
// FLastSupplyV/FLastSupplyA обновляются на rate HP Status (~150/с)
// в TRadioController.ApplyHPStatus.
begin
if not FController.FRunning then
begin