Phase 5 (batch 25a): de-dup telemetry fields + push web-client-active to core

Prep for moving the HP-status callback into the controller:
- Remove the duplicate FLast{SMeter,FwdW,SWR,SupplyV,SupplyA,PLLLock}
  fields from MainForm; the controller (which already declared them) is
  now the sole owner. The UI-only S-meter ballistics (FSMeterPeak/Min/Avg)
  stay in MainForm.
- TWebServer gains an OnClientActiveChanged event (fired via a
  SetClientActive setter at the three client connect/disconnect points);
  MainForm mirrors it into FController.FWebClientActive. The lazy mirror
  in ApplyMOX/ApplyTUN is removed, so the flag is always current — needed
  for the HWPTT->SetMOX path that moves into the controller next.

Behaviour-preserving (the flag held the same value at MOX time before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 17:55:13 +03:00
co-authored by Claude Opus 4.8
parent 239ff54942
commit 9d2324cc6c
2 changed files with 88 additions and 69 deletions
+65 -64
View File
@@ -145,18 +145,11 @@ type
FDUCPendingI: array[0..239] of Integer;
FDUCPendingQ: array[0..239] of Integer;
FDUCPendingCount: Integer;
FLastSMeter: Double;
FSMeterPeak: Double; // верхняя граница светлой зоны
FSMeterMin: Double; // нижняя граница светлой зоны
FSMeterAvg: Double; // сглаженное среднее (EMA)
// RX/seq телеметрия переехала в TRadioController (пишется из OnDDCIQ).
FLastFwdW: Double;
FLastSWR: Double;
// Эти значения приходят с HP Status пакетами (50..200 раз/с). UI обновляется
// из MeterTimerTick (10 Гц), чтобы цифры/полоски не дёргались.
FLastSupplyV: Double;
FLastSupplyA: Double;
FLastPLLLock: Boolean;
// RX/seq + HP-телеметрия (FController.FLastSMeter/FwdW/SWR/SupplyV/SupplyA/PLLLock)
// живут в TRadioController.
// ---- Spectrum / Waterfall view ----
FSpecView: TSpectrumView;
@@ -478,6 +471,7 @@ type
procedure WebOnWfNF(On_: Boolean);
procedure WebOnRun(On_: Boolean);
procedure WebOnMute(On_: Boolean);
procedure WebOnClientActiveChanged(Active: Boolean);
procedure WebOnCtun(On_: Boolean);
procedure WebOnNR(Mode: Integer);
procedure WebOnNB(Mode: Integer);
@@ -996,7 +990,7 @@ begin
RestoreWindowBounds;
// S-метр позиционируем после рестора размера окна
// (будет пересчитан в первом тике SpectrumTimerTick)
FLastSMeter := -130;
FController.FLastSMeter := -130;
// Загружаем настройки веб-сервера из JSON и создаём сервер
FController.FSettings.LoadWebSettings(WebCfg);
@@ -1034,6 +1028,9 @@ begin
FWebServer.OnFMStep := WebOnFMStep;
FWebServer.OnWebMic := WebOnMic;
FWebServer.OnXvtrBand := WebOnXvtrBand;
// Web-слой держит FController.FWebClientActive актуальным (выбор mic-source
// при TX, в т.ч. для HWPTT из контроллера) — больше не зеркалим лениво.
FWebServer.OnClientActiveChanged := WebOnClientActiveChanged;
if FWebEnabled then FWebServer.Start;
PushXvtrToWeb;
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
@@ -1066,11 +1063,11 @@ begin
FController.FLastSeqErrorDDC := -1;
FController.FLastSeqErrorDelta:= 0;
FController.FSeqOkStreak := 0;
FLastFwdW := 0;
FLastSWR := 1;
FLastSupplyV:= -1;
FLastSupplyA:= -1;
FLastPLLLock:= False;
FController.FLastFwdW := 0;
FController.FLastSWR := 1;
FController.FLastSupplyV:= -1;
FController.FLastSupplyA:= -1;
FController.FLastPLLLock:= False;
FDeviceCount := 0;
FDeviceDialog := TDeviceDialog.Create(Self);
FDeviceDialog.OnDiscover := BtnDiscoverFromDialog;
@@ -2973,11 +2970,11 @@ begin
// Сглаженное среднее (EMA)
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FLastSMeter * AVG_ALPHA;
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA;
// Цели для Peak и Min
PeakTarget := Max(FLastSMeter, FSMeterAvg + ZONE_DB);
MinTarget := Min(FLastSMeter, FSMeterAvg - ZONE_DB);
PeakTarget := Max(FController.FLastSMeter, FSMeterAvg + ZONE_DB);
MinTarget := Min(FController.FLastSMeter, FSMeterAvg - ZONE_DB);
// Адаптивный alpha: чем дальше от цели — тем быстрее догоняем
PeakDiff := Abs(PeakTarget - FSMeterPeak);
@@ -2988,11 +2985,11 @@ begin
FSMeterPeak := FSMeterPeak + PeakAlpha * (PeakTarget - FSMeterPeak);
FSMeterMin := FSMeterMin + MinAlpha * (MinTarget - FSMeterMin);
FSpecView.LastSMeter := FLastSMeter;
FSpecView.LastSMeter := FController.FLastSMeter;
FSpecView.SMeterPeak := FSMeterPeak;
FSpecView.SMeterMin := FSMeterMin;
FSpecView.LastFwdW := FLastFwdW;
FSpecView.LastSWR := FLastSWR;
FSpecView.LastFwdW := FController.FLastFwdW;
FSpecView.LastSWR := FController.FLastSWR;
FSpecView.Transmitting := FController.FTransmitting;
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
@@ -3080,7 +3077,7 @@ begin
if FController.FWDSPReady then
begin
FLastSMeter := FController.FDSPEngine.GetSMeterDBm;
FController.FLastSMeter := FController.FDSPEngine.GetSMeterDBm;
FController.FDSPEngine.SetSpectrumWidth(FSpectrumWidth);
Inc(FAgcLineCounter);
if FAgcLineCounter >= 6 then
@@ -3095,7 +3092,7 @@ begin
// Обновляем оверлей если видим
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
begin
FVfoOverlay.UpdateSMeter(FLastSMeter);
FVfoOverlay.UpdateSMeter(FController.FLastSMeter);
FVfoOverlay.UpdateVfo(FController.FVfoA);
PositionVfoOverlay;
end;
@@ -3187,19 +3184,19 @@ begin
end
else
begin
if FLastPLLLock then
if FController.FLastPLLLock then
WebPLLText := 'PLL OK'
else
WebPLLText := 'PLL?';
if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
WebSupplyText := 'Supply n/a'
else if FLastSupplyV >= 0 then
else if FController.FLastSupplyV >= 0 then
begin
if FLastSupplyA >= 0 then
WebSupplyText := Format('Supply %.1fV %.1fA', [FLastSupplyV, FLastSupplyA])
if FController.FLastSupplyA >= 0 then
WebSupplyText := Format('Supply %.1fV %.1fA', [FController.FLastSupplyV, FController.FLastSupplyA])
else
WebSupplyText := Format('Supply %.1fV', [FLastSupplyV]);
WebSupplyText := Format('Supply %.1fV', [FController.FLastSupplyV]);
end
else
WebSupplyText := 'Supply --';
@@ -3227,7 +3224,7 @@ begin
FWebServer.PushSpectrum(
FSpectrumBuf, 1024,
FWaterfallBuf,
FLastSMeter,
FController.FLastSMeter,
FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop,
FController.FSpanHz, FController.FVolume,
FController.FWfAGCEnabled, FController.FWfNFEnabled,
@@ -3239,7 +3236,7 @@ begin
FController.FVfoB, FController.FActiveVfo,
FController.FTransmitting, FController.FDrivePercent,
FController.FAtten, FController.FTuning, FController.FDisplayDuplex,
FLastFwdW, FLastSWR, FController.FPAMaxPower,
FController.FLastFwdW, FController.FLastSWR, FController.FPAMaxPower,
WebStatusText, WebBoardText, WebIPText, WebSupplyText,
WebPLLText, WebRXText, WebTXText, WebSeqText);
end;
@@ -3322,7 +3319,7 @@ begin
(Status.UserADC1Hi shl 8) or Status.UserADC1Lo,
FController.FNetwork.Device.BoardType);
end;
// FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
// FController.FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
// ExciterPwr это мощность TX, не уровень принятого сигнала.
FwdW := ADCToWatts100(FwdPwr);
// FController.FTransmitting — наш программный флаг (MOX/TUN/HW-PTT через ApplyMOX).
@@ -3377,35 +3374,35 @@ begin
// FwdW=0 / SWRV=1 — это «не на передаче»: снапим без decay,
// чтобы метры моментально падали при отпускании PTT.
if FwdW <= 0 then
FLastFwdW := 0
else if FwdW > FLastFwdW then
FLastFwdW := FwdW // attack: instant
FController.FLastFwdW := 0
else if FwdW > FController.FLastFwdW then
FController.FLastFwdW := FwdW // attack: instant
else
FLastFwdW := DECAY_FWD * FwdW + (1.0 - DECAY_FWD) * FLastFwdW;
FController.FLastFwdW := DECAY_FWD * FwdW + (1.0 - DECAY_FWD) * FController.FLastFwdW;
if SWRV <= 1.0 then
FLastSWR := 1.0
else if SWRV > FLastSWR then
FLastSWR := SWRV // attack: instant
FController.FLastSWR := 1.0
else if SWRV > FController.FLastSWR then
FController.FLastSWR := SWRV // attack: instant
else
FLastSWR := DECAY_SWR * SWRV + (1.0 - DECAY_SWR) * FLastSWR;
FController.FLastSWR := DECAY_SWR * SWRV + (1.0 - DECAY_SWR) * FController.FLastSWR;
if SupplyV >= 0 then
begin
if FLastSupplyV < 0 then
FLastSupplyV := SupplyV // первое значение — без сглаживания
if FController.FLastSupplyV < 0 then
FController.FLastSupplyV := SupplyV // первое значение — без сглаживания
else
FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FLastSupplyV;
FController.FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FController.FLastSupplyV;
end else
FLastSupplyV := -1.0;
FController.FLastSupplyV := -1.0;
if SupplyA >= 0 then
begin
if FLastSupplyA < 0 then
FLastSupplyA := SupplyA
if FController.FLastSupplyA < 0 then
FController.FLastSupplyA := SupplyA
else
FLastSupplyA := ALPHA_SUPPLY * SupplyA + (1.0 - ALPHA_SUPPLY) * FLastSupplyA;
FController.FLastSupplyA := ALPHA_SUPPLY * SupplyA + (1.0 - ALPHA_SUPPLY) * FController.FLastSupplyA;
end else
FLastSupplyA := -1.0;
FLastPLLLock := PLLLock;
FController.FLastSupplyA := -1.0;
FController.FLastPLLLock := PLLLock;
if (FSpecView <> nil) and (FSpecView.ADCOverloadVisible <> (ADCOverload <> 0)) then
begin
@@ -3444,8 +3441,8 @@ begin
end;
procedure TMainForm.UpdateTXMeters;
// Вызывается из MeterTimerTick (10 Гц). Использует FLastFwdW/FLastSWR/
// FLastSupplyV/FLastSupplyA обновляются на rate HP Status (~150/с)
// Вызывается из MeterTimerTick (10 Гц). Использует FController.FLastFwdW/FController.FLastSWR/
// FController.FLastSupplyV/FController.FLastSupplyA обновляются на rate HP Status (~150/с)
// в DoUpdateStatus.
begin
if not FController.FRunning then
@@ -3455,7 +3452,7 @@ begin
Exit;
end;
if FLastPLLLock then
if FController.FLastPLLLock then
SetStatusText(7, 'PLL OK')
else
SetStatusText(7, 'PLL?');
@@ -3466,14 +3463,14 @@ begin
Exit;
end;
if FLastSupplyV >= 0 then
if FController.FLastSupplyV >= 0 then
begin
if FLastSupplyA >= 0 then
if FController.FLastSupplyA >= 0 then
SetStatusText(3,
Format('Supply %.1fV %.1fA', [FLastSupplyV, FLastSupplyA]))
Format('Supply %.1fV %.1fA', [FController.FLastSupplyV, FController.FLastSupplyA]))
else
SetStatusText(3,
Format('Supply %.1fV', [FLastSupplyV]));
Format('Supply %.1fV', [FController.FLastSupplyV]));
end else
SetStatusText(3, 'Supply --');
end;
@@ -4470,9 +4467,7 @@ end;
// ---------------------------------------------------------------------------
procedure TMainForm.ApplyTUN(Active: Boolean);
begin
// Web-слой пока не владеет своим флагом — зеркалим состояние клиента в
// контроллер перед командой (SetTune зовёт SetMOX, который читает его).
FController.FWebClientActive := Assigned(FWebServer) and FWebServer.WebClientActive;
// FWebClientActive держит web-слой актуальным (OnClientActiveChanged).
FController.SetTune(Active); // ядро TUN (safety/тон/drive/MOX) + рендер через rfTuning
end;
@@ -5209,9 +5204,8 @@ end;
procedure TMainForm.ApplyMOX(Active: Boolean);
begin
// Web-слой пока не владеет своим флагом — зеркалим текущее состояние клиента
// в контроллер перед командой (определяет mic-source при TX-on).
FController.FWebClientActive := Assigned(FWebServer) and FWebServer.WebClientActive;
// FWebClientActive держит web-слой актуальным (OnClientActiveChanged) —
// определяет mic-source при TX-on.
FController.SetMOX(Active); // ядро TX (safety/PTT/DSP) + рендер через rfTransmitting
end;
@@ -5350,7 +5344,7 @@ begin
PanelLeft.Width := 0;
FVfoOverlay.Width := 260;
FVfoOverlay.Height := 136; // OVL_H_NORM — расширяется сам при открытии AGC-пикера
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FLastSMeter);
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
FVfoOverlay.Visible := True;
PositionVfoOverlay;
@@ -5435,7 +5429,7 @@ begin
SyncSpecViewFreq;
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
begin
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FLastSMeter);
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
PositionVfoOverlay; // перепозиционируем при смене LSB↔USB
end;
@@ -5682,6 +5676,13 @@ begin
FController.SetMute(FWebSyncBool); // идемпотентно; OnControllerState обновит UI
end;
procedure TMainForm.WebOnClientActiveChanged(Active: Boolean);
// Вызывается из потока web-сервера при connect/disconnect клиента. Зеркалим в
// ядро — SetMOX (включая HWPTT-путь в контроллере) выбирает по этому mic-source.
begin
FController.FWebClientActive := Active;
end;
// ── CTUN ──────────────────────────────────────────────────────────────────
procedure TMainForm.WebOnCtun(On_: Boolean);
@@ -6788,7 +6789,7 @@ function TMainForm.CATGetSNB: Boolean; begin Result := FController.FSNB; e
function TMainForm.CATGetANF: Boolean; begin Result := FController.FANF; end;
function TMainForm.CATGetTX: Boolean; begin Result := FController.FTransmitting; end;
function TMainForm.CATGetRunning: Boolean; begin Result := FController.FRunning; end;
function TMainForm.CATGetSMeter: Double; begin Result := FLastSMeter; end;
function TMainForm.CATGetSMeter: Double; begin Result := FController.FLastSMeter; end;
function TMainForm.CATGetBand: Integer; begin Result := FController.FCurrentBand; end;
// --- Setters ----------------------------------------------------------------
+23 -5
View File
@@ -151,6 +151,9 @@ type
// XVTR-band: web клиент кликнул кнопку трансвертера (Idx 0..CFG_XVTR_COUNT-1).
// Idx=-1 — выход в HF.
TWebCmdXvtrBand = procedure(Idx: Integer) of object;
// Активность web-клиента изменилась (подключился/отключился последний клиент).
// Хозяин зеркалит это в ядро (FController.FWebClientActive) для выбора mic-source.
TWebClientActiveEvent = procedure(Active: Boolean) of object;
// Один XVTR-слот для статуса (для отображения в web-bandSel).
TWebXvtrInfo = record
Idx: Integer;
@@ -294,6 +297,11 @@ type
FSeqText: string;
FWebClientActive: Boolean;
FOnClientActiveChanged: TWebClientActiveEvent;
// Обновляет FWebClientActive и при изменении уведомляет хозяина. Вызывать
// под уже взятой блокировкой поля (FStateLock/FClientLock) — сам не лочит.
procedure SetClientActive(Value: Boolean);
// ── Внутренние методы ──
function LoadOpus: Boolean;
@@ -346,6 +354,8 @@ type
RXText, TXText, SeqText: string);
property WebClientActive: Boolean read FWebClientActive;
property OnClientActiveChanged: TWebClientActiveEvent
read FOnClientActiveChanged write FOnClientActiveChanged;
property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits;
property FMStepIdx: Integer read FFMStepIdx write FFMStepIdx;
@@ -980,7 +990,7 @@ begin
Client.State := wsOpen;
FStateLock.Enter;
FWebClientActive := True;
SetClientActive(True);
FStateLock.Leave;
Client.SendText(BuildStateJson);
@@ -1114,14 +1124,21 @@ begin
end;
FStateLock.Enter;
FWebClientActive := (FClientCount > 1);
SetClientActive(FClientCount > 1);
FStateLock.Leave;
RemoveClient(Client);
end;
procedure TWebServer.SetClientActive(Value: Boolean);
begin
if FWebClientActive = Value then Exit;
FWebClientActive := Value;
if Assigned(FOnClientActiveChanged) then FOnClientActiveChanged(Value);
end;
procedure TWebServer.RemoveClient(Client: TWsClient);
var i, j: Integer;
var i, j: Integer; NewActive: Boolean;
begin
FClientLock.Enter;
try
@@ -1134,10 +1151,11 @@ begin
Dec(FClientCount);
Break;
end;
FWebClientActive := False;
NewActive := False;
for i := 0 to FClientCount - 1 do
if (FClients[i] <> nil) and (FClients[i].State = wsOpen) then
begin FWebClientActive := True; Break; end;
begin NewActive := True; Break; end;
SetClientActive(NewActive);
finally
FClientLock.Leave;
end;