From 9d2324cc6c4d44066ad12050a033e89447265a68 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 5 Jun 2026 17:55:13 +0300 Subject: [PATCH] Phase 5 (batch 25a): de-dup telemetry fields + push web-client-active to core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- MainForm.pas | 129 +++++++++++++++++++++++++------------------------- WebServer.pas | 28 +++++++++-- 2 files changed, 88 insertions(+), 69 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index d933ca7..1630e70 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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 ---------------------------------------------------------------- diff --git a/WebServer.pas b/WebServer.pas index f4e0043..45f3261 100644 --- a/WebServer.pas +++ b/WebServer.pas @@ -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;