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