Phase 5 (D4 fix): daemon WDSP wisdom, spectrum width, shutdown order

Three runtime fixes found testing the daemon on real hardware:

- WDSP opened in ~47s (vs <1s GUI): WDSPEngine.Open never imports FFTW wisdom;
  the GUI does it separately in EnsureWDSPWisdom before Open. Daemon now calls
  WDSPwisdom(GetAppCfgDir) before Open (imports the existing wdspWisdom00 in ms;
  builds it once if absent). Verified: open is now instant.

- Web waterfall/spectrum filled only the left ~6%: the analyzer's pixel width
  defaults to DISPLAY_BLOCK_SIZE (64). The GUI calls SetSpectrumWidth(panel) every
  tick; headless never did, so only 64 of the 1024 web buffer points had data.
  Daemon now calls SetSpectrumWidth(1024) after Open (== SPECTRUM_PIXELS / web
  buffer). FLastSpectrumW survives channel recreation.

- SIGINT during the (blocking) autostart connect was lost because Run set
  FRunning:=True *after* the connect, overwriting RequestStop. Set FRunning:=True
  before autostart so a stop during connect exits the loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-10 10:04:25 +03:00
co-authored by Claude Opus 4.8
parent db361a274b
commit 8fd45bc266
+27 -2
View File
@@ -28,7 +28,7 @@ uses
{$IFDEF UNIX}cthreads, BaseUnix,{$ENDIF}
Classes, SysUtils,
RadioController, WebServer, WebAdapter,
HPSDRNetwork, Settings;
HPSDRNetwork, Settings, WDSP, PlatformUtils;
const
DAEMON_SPEC_WIDTH = 1024; // ширина спектра для StartRunning (web-буфер = 1024)
@@ -199,13 +199,34 @@ begin
end;
function THeadlessHost.EnsureWDSP: Boolean;
var
WisdomDir: AnsiString;
begin
if FCtrl.FWDSPReady then Exit(True);
// FFTW-wisdom ДО Open: иначе XCreateAnalyzer строит планы с нуля (~минуту).
// WDSPwisdom(dir) импортирует готовый wdspWisdom00 (обычно создан GUI) за мс;
// если файла нет — строит и пишет его (одноразово, медленно). Как GUI
// (MainForm.EnsureWDSPWisdom), но без потока/диалога — демону блокировать ок.
if Assigned(@WDSPwisdom) then
begin
WisdomDir := AnsiString(GetAppCfgDir);
if not FileExists(string(WisdomDir) + 'wdspWisdom00') then
Log('Building WDSP wisdom (one-time, may take ~30-60s)...');
WDSPwisdom(PAnsiChar(WisdomDir));
end;
Log('Opening WDSP...');
Result := FCtrl.FDSPEngine.Open;
FCtrl.FWDSPReady := Result;
if not Result then
begin
Log('WDSP open FAILED: ' + FCtrl.FDSPEngine.LastError);
Exit;
end;
// Ширина анализатора спектра. Без UI её никто не выставляет (GUI зовёт
// SetSpectrumWidth(панель) каждый тик), и анализатор отдаёт дефолтные 64 пикселя
// → web рисует лишь ~6% ширины, остальное белым. Ставим ровно под web-буфер
// (SPECTRUM_PIXELS=1024). FLastSpectrumW переживает пересоздание канала.
FCtrl.FDSPEngine.SetSpectrumWidth(DAEMON_SPEC_WIDTH);
end;
procedure THeadlessHost.ConnectTo(const Dev: THPSDRDevice);
@@ -365,6 +386,11 @@ begin
Log('Web control on http://' + WebCfg.BindAddr + ':' + IntToStr(WebCfg.Port));
PushDeviceList;
// FRunning ставим ДО autostart: первый WDSP-open может занять время, и если за
// него прилетит SIGINT (RequestStop→FRunning:=False), цикл сразу завершится, а
// не перетрётся обратно в True.
FRunning := True;
// Autostart — подключаемся сразу к отмеченному устройству.
AutoIP := FCtrl.FDeviceStore.AutoStartIP;
if AutoIP <> '' then
@@ -377,7 +403,6 @@ begin
else
Log('No autostart device — waiting for web connect.');
FRunning := True;
lastPush := GetTickCount64;
while FRunning do
begin