Files
ewsdr/WebAdapter.pas
T
Uladzimir KarpenkaandClaude Opus 4.8 0a0a5c455e Phase 3 (WA2e): move web Freq/FreqA handlers into the adapter
With ApplyVfoA now a thin SetVfoA delegate (channel orchestration on the
OnAfterTune hook), the web Freq/FreqA handlers carry no MainForm logic — move
them to TWebAdapter.OnFreq (active-VFO routing → SetVfoA/SetVfoB) and OnFreqA
(SetVfoA). CAT freq (CATSetVfoA/SyncCATVfoA) left as-is — moves with the CAT
adapter (Phase 4).

Remaining host-coupled web handlers: Run / Connect / device CRUD (daemon entry).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:18:59 +03:00

456 lines
17 KiB
ObjectPascal

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, 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;
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);
procedure OnClientActiveChanged(Active: Boolean);
// TX-mic от web-клиента → WDSP. Вызывается из WS-потока; без маршалинга —
// подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
procedure OnMic(Samples: PSingle; Count: Integer);
// Исходящее зеркало: периодический снимок состояния + спектр/водопад в web.
// Зовётся хозяином из GUI-таймера (буферы пикселей — у хозяина, остальное из
// FController). Сервер сам гейтит по FRunning и шлёт активным клиентам.
procedure PushState(const ASpec, AWf: array of Single);
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 < 0) or (FSyncInt > MODE_SAM) 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;
// Смена HF-диапазона из web/CAT. Выход из XVTR (если активен) + persist + restore.
begin
if (FSyncInt < 0) or (FSyncInt >= CFG_BAND_COUNT) then Exit;
if FController.FCurrentXvtr >= 0 then
begin
FController.SetXvtrBand(-1); // выход в HF (DeactivateXvtr несёт HF-возврат)
if FSyncInt = FController.FCurrentBand then Exit;
end
else if FSyncInt = FController.FCurrentBand then
Exit;
if FController.FDevConnected then
begin FController.SaveCurrentBand; FController.FSettings.Save; end;
FController.FCurrentBand := FSyncInt;
FController.RestoreBand(FController.FCurrentBand);
end;
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. Принимаем только валидные значения (как десктоп-оверлей);
// SetSampleRate сам пересоздаёт WDSP-канал + персистит (idempotent при том же rate).
const
ValidSpans: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000);
var i: Integer;
begin
for i := 0 to High(ValidSpans) do
if ValidSpans[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.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;
// ── Исходящее зеркало состояния ──────────────────────────────────────────────
procedure TWebAdapter.PushState(const ASpec, AWf: array of Single);
var
StatusText, BoardText, IPText, SupplyText: string;
PLLText, RXText, TXText, SeqText: string;
begin
if FController.FNetwork.Connected then
begin
if FController.FRunning then StatusText := 'Running'
else StatusText := 'Connected';
BoardText := 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType);
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 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 --';
FServer.PushSpectrum(
ASpec, 1024,
AWf,
FController.FLastSMeter,
FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop,
FController.FSpanHz, FController.FVolume,
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,
FController.FCenterFreq, 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);
end;
end.