Files
ewsdr/WebAdapter.pas
T

613 lines
24 KiB
ObjectPascal

{
Copyright (C)
2026 - Uladzimir Karpenka, EW8BAK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit WebAdapter;
{$mode objfpc}{$H+}
// Адаптер веб-интерфейса: мост между TWebServer (входящие команды из WS-потока)
// и TRadioController (ядро). Раньше эти WebOnXxx/SyncWebXxx жили в TMainForm —
// вынесены сюда, чтобы развязать UI от веба и подготовить headless-демон.
//
// Маршалинг: входящие команды приходят из потока веб-сервера; выполняем их в
// потоке контроллера через FController.Invoke (GUI = TThread.Synchronize; демон
// подставит свою очередь). Synchronize блокирующий и последовательный — поля
// FSync* для передачи параметров безопасны (гонок нет).
//
// Часть команд (тюнинг VFO/диапазон/MOX/...) идёт через UI-обёртки MainForm,
// которые ещё не headless — для них есть IWebHost (его реализует хозяин). По мере
// переноса оркестрации в контроллер интерфейс будет сужаться.
interface
uses
Classes, SysUtils,
RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, RadioBackend, BoardUtils, Settings;
type
// UI/оркестрационные операции, которые адаптер дёргает у хозяина (пока не
// headless — десктоп: методы MainForm; демон: своя реализация). Наполняется
// в WA2 по мере переноса host-зависимых хендлеров.
IWebHost = interface
['{B3A1F7C2-9D54-4E18-9C6B-2E5A7F0D1A33}']
end;
{ TWebAdapter }
TWebAdapter = class
private
FController: TRadioController;
FServer: TWebServer;
FHost: IWebHost;
// Поля для передачи параметров команды в Sync-метод (через Invoke).
FSyncInt: Integer;
FSyncBool: Boolean;
FSyncFreq: Double;
// Sync-методы (выполняются в потоке контроллера через Invoke).
procedure SyncMode;
procedure SyncFilter;
procedure SyncAGC;
procedure SyncAGCTop;
procedure SyncVolume;
procedure SyncDrive;
procedure SyncSpan;
procedure SyncBand;
procedure SyncXvtrBand;
procedure SyncWfAGC;
procedure SyncWfNF;
procedure SyncMute;
procedure SyncNR;
procedure SyncNB;
procedure SyncSNB;
procedure SyncANF;
procedure SyncFreq;
procedure SyncFreqA;
procedure SyncFreqB;
procedure SyncCenter;
procedure SyncAttn;
procedure SyncActiveVfo;
procedure SyncCtun;
procedure SyncMOX;
procedure SyncTun;
procedure SyncFMStep;
procedure SyncBeacon;
procedure SyncBeaconSeed;
procedure SyncRxGainMode;
procedure SyncRxGain;
procedure SyncRxMute;
function BeaconStatusText: string; // компактный статус (как десктоп поле 7)
public
constructor Create(AController: TRadioController; AServer: TWebServer;
AHost: IWebHost);
// Колбэки веб-сервера (навешиваются хозяином на FServer.OnXxx).
procedure OnMode(Mode: Integer);
procedure OnFilter(BW: Integer);
procedure OnAGC(Mode: Integer);
procedure OnAGCTop(DB: Integer);
procedure OnVolume(V: Integer);
procedure OnDrive(V: Integer);
procedure OnSpan(Hz: Integer);
procedure OnBand(Idx: Integer);
procedure OnXvtrBand(Idx: Integer);
procedure OnWfAGC(On_: Boolean);
procedure OnWfNF(On_: Boolean);
procedure OnMute(On_: Boolean);
procedure OnNR(Mode: Integer);
procedure OnNB(Mode: Integer);
procedure OnSNB(On_: Boolean);
procedure OnANF(On_: Boolean);
procedure OnFreq(Hz: Double);
procedure OnFreqA(Hz: Double);
procedure OnFreqB(Hz: Double);
procedure OnCenter(Hz: Double);
procedure OnAttn(Idx: Integer);
procedure OnActiveVfo(Idx: Integer);
procedure OnCtun(On_: Boolean);
procedure OnMOX(On_: Boolean);
procedure OnTun(On_: Boolean);
procedure OnFMStep(Idx: Integer);
// QO-100 beacon: вкл/выкл лок и наведение по клику в спектре (frac 0..1).
procedure OnBeacon(On_: Boolean);
procedure OnBeaconSeed(Frac: Double);
procedure OnRxGainMode(Mode: Integer);
procedure OnRxGain(Db: Integer);
procedure OnRxMute(On_: Boolean);
procedure OnClientActiveChanged(Active: Boolean);
// TX-mic от web-клиента → WDSP. Вызывается из WS-потока; без маршалинга —
// подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
procedure OnMic(Samples: PSingle; Count: Integer);
// RX-аудио-перехват: навешивается на FController.OnAudioConsume. Если есть
// активный web-клиент — сводим в моно и шлём ему, возвращаем True (локальный
// AudioOut пропускается контроллером). Вызывается из DSP-потока.
function AudioConsume(const Left, Right: array of Single;
Count: Integer): Boolean;
// Исходящее зеркало: периодический снимок состояния + спектр/водопад в web.
// Зовётся хозяином из GUI-таймера; всё (включая последний кадр спектра/
// водопада) читается из FController. Сервер сам гейтит по FRunning и шлёт
// активным клиентам.
procedure PushState;
end;
implementation
constructor TWebAdapter.Create(AController: TRadioController;
AServer: TWebServer; AHost: IWebHost);
begin
inherited Create;
FController := AController;
FServer := AServer;
FHost := AHost;
end;
// ── Входящие команды: запоминаем параметр, выполняем в потоке контроллера ─────
procedure TWebAdapter.OnMode(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncMode); end;
procedure TWebAdapter.SyncMode;
begin
if (FSyncInt < MODE_MIN) or (FSyncInt > MODE_MAX) then Exit;
FController.SetMode(FSyncInt); // дефолт фильтра + DSP + cache; рендер через rfMode
end;
procedure TWebAdapter.OnFilter(BW: Integer);
begin FSyncInt := BW; FController.Invoke(@SyncFilter); end;
procedure TWebAdapter.SyncFilter;
begin
// Web-клиент шлёт полосу в Гц (кнопки панели фильтров = пресетные значения);
// команда применяет её и подсвечивает ближайший пресет (рендер — rfFilter).
FController.SetFilterBW(FSyncInt);
end;
procedure TWebAdapter.OnAGC(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncAGC); end;
procedure TWebAdapter.SyncAGC;
begin
FController.SetAGCMode(FSyncInt); // единый маппинг (исправляет старый web-баг)
end;
procedure TWebAdapter.OnAGCTop(DB: Integer);
begin FSyncInt := DB; FController.Invoke(@SyncAGCTop); end;
procedure TWebAdapter.SyncAGCTop;
begin
FController.SetAGCTop(FSyncInt); // клампит, применяет, OnControllerState обновит UI
end;
procedure TWebAdapter.OnVolume(V: Integer);
begin FSyncInt := V; FController.Invoke(@SyncVolume); end;
procedure TWebAdapter.SyncVolume;
begin FController.SetVolume(FSyncInt); end;
procedure TWebAdapter.OnDrive(V: Integer);
begin FSyncInt := V; FController.Invoke(@SyncDrive); end;
procedure TWebAdapter.SyncDrive;
begin FController.SetDrive(FSyncInt); end; // rfDrive снимет память канала (ChannelController)
procedure TWebAdapter.OnBand(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncBand); end;
procedure TWebAdapter.SyncBand;
begin FController.SetBand(FSyncInt); end; // XVTR-выход + persist + restore — в команде
procedure TWebAdapter.OnXvtrBand(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncXvtrBand); end;
procedure TWebAdapter.SyncXvtrBand;
begin
if FSyncInt < 0 then begin FController.SetXvtrBand(-1); Exit; end;
if FSyncInt >= CFG_XVTR_COUNT then Exit;
if not FController.FXvtrSettings.Entries[FSyncInt].Enabled then Exit;
if FSyncInt = FController.FCurrentXvtr then Exit;
if FController.FDevConnected then
begin FController.SaveCurrentBand; FController.FSettings.Save; end;
FController.SetXvtrBand(FSyncInt); // ActivateXvtr (рендер/wideband/web — rfXvtr)
end;
procedure TWebAdapter.OnSpan(Hz: Integer);
begin FSyncInt := Hz; FController.Invoke(@SyncSpan); end;
procedure TWebAdapter.SyncSpan;
// span ≡ sample rate. Валидируем по пресетам текущего бэкенда (единый источник,
// как десктоп-оверлей) — иначе Pluto-рейты (960k/2304k/…) отбрасывались бы.
// SetSampleRate сам пересоздаёт WDSP-канал + персистит (idempotent при том же rate).
var
Presets: TBackendRateArray;
i: Integer;
begin
Presets := FController.SampleRatePresets;
for i := 0 to High(Presets) do
if Presets[i] = FSyncInt then
begin
FController.SetSampleRate(FSyncInt);
Exit;
end;
end;
procedure TWebAdapter.OnWfAGC(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncWfAGC); end;
procedure TWebAdapter.SyncWfAGC;
begin FController.SetWfAGC(FSyncBool); end;
procedure TWebAdapter.OnWfNF(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncWfNF); end;
procedure TWebAdapter.SyncWfNF;
begin FController.SetWfNF(FSyncBool); end;
procedure TWebAdapter.OnMute(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncMute); end;
procedure TWebAdapter.SyncMute;
begin FController.SetMute(FSyncBool); end; // идемпотентно; OnControllerState обновит UI
procedure TWebAdapter.OnNR(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncNR); end;
procedure TWebAdapter.SyncNR;
begin FController.SetNR(FSyncInt); end; // клампит и применяет; рендер через rfNR
procedure TWebAdapter.OnNB(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncNB); end;
procedure TWebAdapter.SyncNB;
begin FController.SetNB(FSyncInt); end;
procedure TWebAdapter.OnSNB(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncSNB); end;
procedure TWebAdapter.SyncSNB;
begin FController.SetSNB(FSyncBool); end;
procedure TWebAdapter.OnANF(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncANF); end;
procedure TWebAdapter.SyncANF;
begin FController.SetANF(FSyncBool); end;
procedure TWebAdapter.OnFreq(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreq); end;
procedure TWebAdapter.SyncFreq;
begin
// "freq" для АКТИВНОГО VFO. SetVfoA/SetVfoB несут CTUN/band-detect/сеть +
// канальную оркестрацию (через OnAfterTune-хук); рендер — rfVfoA/rfVfoB.
if FController.FActiveVfo = 0 then FController.SetVfoA(FSyncFreq)
else FController.SetVfoB(FSyncFreq);
end;
procedure TWebAdapter.OnFreqA(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreqA); end;
procedure TWebAdapter.SyncFreqA;
begin FController.SetVfoA(FSyncFreq); end;
procedure TWebAdapter.OnFreqB(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreqB); end;
procedure TWebAdapter.SyncFreqB;
begin
// Унифицировано с десктопом: команда хранит частоту, при активном B
// перестраивает приёмник (CTUN-aware) + сеть; рендер — через rfVfoB/rfBand.
FController.SetVfoB(FSyncFreq);
end;
procedure TWebAdapter.OnCenter(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncCenter); end;
procedure TWebAdapter.SyncCenter;
begin
// Сдвигаем DDC-центр без смены VFO (аналог CTUN-drag в десктопе).
FController.SetCenter(FSyncFreq);
end;
procedure TWebAdapter.OnAttn(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncAttn); end;
procedure TWebAdapter.SyncAttn;
begin FController.SetAtten(FSyncInt); end;
procedure TWebAdapter.OnActiveVfo(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncActiveVfo); end;
procedure TWebAdapter.SyncActiveVfo;
begin
if (FSyncInt < 0) or (FSyncInt > 1) then Exit;
if FSyncInt = FController.FActiveVfo then Exit;
FController.SetActiveVfo(FSyncInt); // перестройка RX-VFO; рендер rfActiveVfo
end;
procedure TWebAdapter.OnCtun(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncCtun); end;
procedure TWebAdapter.SyncCtun;
begin
if FSyncBool <> FController.FCTun then
FController.SetCTun(FSyncBool); // band cache + center/shift + сеть; рендер rfCTun
end;
procedure TWebAdapter.OnMOX(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncMOX); end;
procedure TWebAdapter.SyncMOX;
begin
// FWebClientActive держится актуальным OnClientActiveChanged — выбор mic-source.
FController.SetMOX(FSyncBool); // ядро TX (safety/PTT/DSP); рендер rfTransmitting
end;
procedure TWebAdapter.OnTun(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncTun); end;
procedure TWebAdapter.SyncTun;
begin
FController.SetTune(FSyncBool); // ядро TUN (safety/тон/drive/MOX); рендер rfTuning
end;
procedure TWebAdapter.OnFMStep(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncFMStep); end;
procedure TWebAdapter.SyncFMStep;
begin
FController.SetFMStep(FSyncInt); // клампит 0..3 внутри; рендер rfFMStepIdx
FController.SaveCurrentBand;
end;
procedure TWebAdapter.OnBeacon(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncBeacon); end;
procedure TWebAdapter.SyncBeacon;
begin
// Вкл/выкл лок маяка (= запуск декодера + трим LOError). Идемпотентно.
FController.SetBeaconLock(FSyncBool);
end;
procedure TWebAdapter.OnBeaconSeed(Frac: Double);
begin FSyncFreq := Frac; FController.Invoke(@SyncBeaconSeed); end;
procedure TWebAdapter.SyncBeaconSeed;
var Hz, VC, VS: Double;
begin
// Frac (доля 0..1 по ширине спектра) → абс. display-Hz от ЖИВОГО видимого окна
// контроллера (зеркалит десктоп PixelToFreq с учётом зума). Так наведение
// точно, даже когда LO ретюнится локом и клиентский center отстаёт.
if (FSyncFreq < 0) or (FSyncFreq > 1) then Exit;
FController.GetViewWindow(VC, VS);
Hz := VC + (FSyncFreq - 0.5) * VS;
FController.BeaconSeedAtHz(Hz);
end;
procedure TWebAdapter.OnRxGainMode(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncRxGainMode); end;
procedure TWebAdapter.SyncRxGainMode;
begin
// Pluto hw-AGC: 0=manual,1=fast,2=slow,3=hybrid (рендер через rfRxGain).
FController.SetRxGainMode(FSyncInt);
end;
procedure TWebAdapter.OnRxGain(Db: Integer);
begin FSyncInt := Db; FController.Invoke(@SyncRxGain); end;
procedure TWebAdapter.SyncRxGain;
begin
// Ручной hw-gain (0..73 дБ); SetRxGain переводит режим в manual.
FController.SetRxGain(FSyncInt);
end;
procedure TWebAdapter.OnRxMute(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncRxMute); end;
procedure TWebAdapter.SyncRxMute;
begin
// QO-100 self-monitor: RX-аудио на TX (toggle, рендер через rfRxMuteOnTx).
FController.SetRxMuteOnTx(FSyncBool);
end;
procedure TWebAdapter.OnClientActiveChanged(Active: Boolean);
// Из потока веб-сервера при connect/disconnect клиента. Простая запись флага —
// ядро (SetMOX, в т.ч. HWPTT-путь) выбирает по нему mic-source. Без маршалинга.
begin
FController.FWebClientActive := Active;
end;
procedure TWebAdapter.OnMic(Samples: PSingle; Count: Integer);
var
Buf: array[0..5759] of Double;
SP: PSingle;
i, n: Integer;
begin
if not FController.FWDSPReady then Exit;
n := Count;
if n > Length(Buf) then n := Length(Buf);
SP := Samples;
for i := 0 to n - 1 do
begin
Buf[i] := SP^;
Inc(SP);
end;
FController.FDSPEngine.PushTXMicSamplesD(Buf, n);
end;
function TWebAdapter.AudioConsume(const Left, Right: array of Single;
Count: Integer): Boolean;
var
MonoBuf: array[0..1023] of Single;
i, n: Integer;
begin
Result := False;
if not (Assigned(FServer) and FServer.WebClientActive) then Exit;
n := Count;
if n > Length(MonoBuf) then n := Length(MonoBuf);
for i := 0 to n - 1 do
MonoBuf[i] := (Left[i] + Right[i]) * 0.5;
FServer.PushAudio(@MonoBuf[0], n);
Result := True;
end;
// ── Исходящее зеркало состояния ──────────────────────────────────────────────
function TWebAdapter.BeaconStatusText: string;
// Зеркалит TMainForm.BeaconStatusText (поле 7 десктопа). Pluto в трансвертере.
begin
if not FController.BeaconLockEnabled then Exit('BCN: off');
if FController.BeaconDecodeFreqHz <= 0 then Exit('BCN: click beacon');
case FController.BeaconState of
bsLock: Result := Format('BCN LOCK %.0fHz /%.0fdB',
[FController.BeaconCorrectionHz, FController.BeaconSNRdB]);
bsSearch: Result := Format('BCN sync /%.0fdB', [FController.BeaconSNRdB]);
else Result := 'BCN --';
end;
end;
procedure TWebAdapter.PushState;
var
StatusText, BoardText, IPText, SupplyText: string;
PLLText, RXText, TXText, SeqText: string;
ViewCenter, ViewSpan: Double;
WfNew: Boolean;
begin
// Пришла ли от DSP новая строка водопада с прошлого пуша. Снимаем ДО пуша:
// без этого сервер повторно отправлял бы прежнюю строку, когда кадров нет
// (RX остановлен) или когда пуш случился чаще кадров — водопад двоил.
WfNew := FController.FWfAccumFresh;
// Видимое окно (центр+span) с учётом зума: пиксели спектра уже зумлены
// анализатором, web-метки/маппинг должны соответствовать.
FController.GetViewWindow(ViewCenter, ViewSpan);
if FController.FNetwork.Connected then
begin
if FController.FRunning then StatusText := 'Running'
else StatusText := 'Connected';
BoardText := 'Board: ' + FController.BoardDisplayName;
IPText := 'IP: ' + FController.FNetwork.Device.IPAddress;
end
else
begin
StatusText := 'Disconnected';
BoardText := 'Board --';
IPText := 'IP --';
end;
if not FController.FRunning then
begin
SupplyText := 'Supply --';
PLLText := 'PLL --';
end
else
begin
if FController.FLastPLLLock then PLLText := 'PLL OK'
else PLLText := 'PLL?';
if FController.IsPluto then
begin
// Pluto: нет PA/supply — температуры (трансивер/SoC) + RSSI (telemetry-поле).
// Temp X/YC: X — трансивер AD9361, Y — Zynq SoC/FPGA (xadc).
SupplyText := '';
if (FController.FLastPlutoTempC > TELEMETRY_NONE)
and (FController.FLastPlutoSocTempC > TELEMETRY_NONE) then
SupplyText := Format('Temp %.0f/%.0fC', [FController.FLastPlutoTempC, FController.FLastPlutoSocTempC])
else if FController.FLastPlutoTempC > TELEMETRY_NONE then
SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC])
else if FController.FLastPlutoSocTempC > TELEMETRY_NONE then
SupplyText := Format('Temp %.0fC', [FController.FLastPlutoSocTempC]);
if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then
begin
if SupplyText <> '' then SupplyText := SupplyText + ' ';
SupplyText := SupplyText + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]);
end;
if SupplyText = '' then SupplyText := 'Pluto --';
end
else if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
SupplyText := 'Supply n/a'
else if FController.FLastSupplyV >= 0 then
begin
if FController.FLastSupplyA >= 0 then
SupplyText := Format('Supply %.1fV %.1fA', [FController.FLastSupplyV, FController.FLastSupplyA])
else
SupplyText := Format('Supply %.1fV', [FController.FLastSupplyV]);
end
else
SupplyText := 'Supply --';
end;
if FController.FRunning then RXText := 'RX running'
else RXText := 'RX idle';
if FController.FTuning then TXText := 'TX tune'
else if FController.FTransmitting then TXText := 'TX active'
else TXText := 'TX idle';
if FController.FSeqErrorCount > 0 then
SeqText := Format('SEQ ERR DDC%d', [FController.FLastSeqErrorDDC])
else if FController.FRunning then
SeqText := 'SEQ OK'
else
SeqText := 'SEQ --';
// QO-100 beacon lock — отдельный канал состояния (видим только на Pluto в XVTR).
FServer.SetBeaconStatus(
FController.IsPluto and (FController.FCurrentXvtr >= 0),
FController.BeaconLockEnabled,
BeaconStatusText,
FController.BeaconRefHz,
FController.BeaconTrackedFreqHz);
// Pluto RF-AGC (виден при HasHWGain = Pluto подключён) + QO-100 RX MUTE (InQO100).
FServer.SetRxGainStatus(
FController.BackendCaps.HasHWGain,
FController.FRxGainMode, FController.FRxGainDb);
FServer.SetRxMuteStatus(
FController.InQO100,
FController.FRxMuteOnTx);
// Счётчики — РЕАЛЬНЫЕ: кадр короче потолка (web.spec_pixels), когда
// разрешение анализатора (= ширина панадаптера) меньше него — узкая панель,
// грид из двух колонок. Константа здесь отправляла хвост буфера — нули
// (0 dBm) поверх шкалы и сжатый по частоте спектр.
FServer.PushSpectrum(
FController.FSpectrumBuf, FController.FSpectrumBufCount,
FController.FWaterfallBuf, FController.FWaterfallBufCount, WfNew,
FController.FLastSMeter,
FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop,
ViewSpan, FController.ActiveVolume,
FController.FWfAGCEnabled, FController.FWfNFEnabled,
FController.FCurrentBand,
FController.FRunning and FController.FNetwork.Connected,
FController.FRunning, FController.FMuted, FController.FCTun,
FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF,
ViewCenter, FController.FFilter,
FController.FVfoB, FController.FActiveVfo,
FController.FTransmitting, FController.FDrivePercent,
FController.FAtten, FController.FTuning, FController.FDisplayDuplex,
FController.FLastFwdW, FController.FLastSWR, FController.FPAMaxPower,
StatusText, BoardText, IPText, SupplyText,
PLLText, RXText, TXText, SeqText);
// Строка забрана — следующий кадр DSP начинает новое накопление.
if WfNew then FController.FWfAccumFresh := False;
end;
end.