mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Phase 3 (batch 33+34): web device discovery/connect parity
Move discovery into the controller and add a full web device overlay (discovery + connect + saved-device CRUD), reaching parity with the desktop device dialog. - RadioController: OnDeviceFound/Discover/DiscoverFinishedNone + rfDeviceList; TDiscoverThread moved in from MainForm. - WebServer: device protocol (discover/connect/disconnect/dev_add/ dev_remove/dev_autostart) + "devices" in BuildStateJson. - MainForm: web handlers (marshalled), PushDeviceListToWeb, extracted DoStopAndDisconnect, detached WebOnRun from BtnStartStopClick; web connect resolves board type from discovered then saved by IP. - DeviceForm: RefreshFound renders the found list from the store. - WebPageHtml: DEV overlay; START opens it, STOP disconnects. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9dec47a6ca
commit
c4981fcb19
+222
-114
@@ -185,6 +185,8 @@ type
|
||||
FWebSyncFreq: Double;
|
||||
FWebSyncInt: Integer;
|
||||
FWebSyncBool: Boolean;
|
||||
FWebSyncStr1: string; // для web-команд с текстом (IP/имя)
|
||||
FWebSyncStr2: string;
|
||||
FWebSyncM: TThreadMethod;
|
||||
FSettingsForm: TObject; // TSettingsForm (cast при использовании)
|
||||
FPendingDev: THPSDRDevice; // устройство ожидающее открытия WDSP
|
||||
@@ -325,7 +327,6 @@ type
|
||||
function EnsureWDSPWisdom: Boolean;
|
||||
|
||||
// Network callbacks
|
||||
procedure OnDeviceFound(const Dev: THPSDRDevice);
|
||||
procedure OnWidebandCB(ADCIndex: Integer; const Samples: array of SmallInt;
|
||||
Count: Integer);
|
||||
procedure OnWDSPOpenDone(Success: Boolean);
|
||||
@@ -333,6 +334,9 @@ type
|
||||
// Старт с уже выбранным устройством (preload rate + WDSP open + connect).
|
||||
// Переиспользуется десктоп-диалогом и web-оверлеем — без модального диалога.
|
||||
procedure StartWithDevice(const Dev: THPSDRDevice);
|
||||
// STOP-teardown: persistence + graceful Run=0 + Disconnect + offline-статус.
|
||||
// Общий для десктоп-STOP и web-disconnect.
|
||||
procedure DoStopAndDisconnect;
|
||||
|
||||
// DSP/Audio callbacks (вызываются из рабочих потоков)
|
||||
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
||||
@@ -340,10 +344,8 @@ type
|
||||
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
||||
|
||||
// Public UI update (called from sync objects)
|
||||
procedure DoAddDevice(const Dev: THPSDRDevice; const Entry: string);
|
||||
procedure UpdateTXMeters;
|
||||
procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
|
||||
procedure DoUpdateDDCSeqOrNoDevice(DDCIdx: Integer; Seq: LongWord);
|
||||
|
||||
// Event handlers
|
||||
procedure BtnDiscoverClick(Sender: TObject);
|
||||
@@ -462,6 +464,20 @@ type
|
||||
procedure WebOnFMStep(Idx: Integer);
|
||||
procedure WebOnMic(Samples: PSingle; Count: Integer);
|
||||
procedure WebOnXvtrBand(Idx: Integer);
|
||||
// Discovery / connect / saved-CRUD из web (маршалятся в UI)
|
||||
procedure WebOnConnect(const IP: string);
|
||||
procedure WebOnDiscover;
|
||||
procedure WebOnDisconnect;
|
||||
procedure WebOnDevAdd(const AName, IP: string);
|
||||
procedure WebOnDevRemove(Idx: Integer);
|
||||
procedure WebOnDevAutoStart(Idx: Integer);
|
||||
procedure SyncWebConnect;
|
||||
procedure SyncWebDiscover;
|
||||
procedure SyncWebDisconnect;
|
||||
procedure SyncWebDevAdd;
|
||||
procedure SyncWebDevRemove;
|
||||
procedure SyncWebDevAutoStart;
|
||||
procedure PushDeviceListToWeb; // store → FWebServer.SetDeviceList
|
||||
// Synchronize-обёртки (выполняются в UI-потоке)
|
||||
procedure SyncWebFreq;
|
||||
procedure SyncWebMode;
|
||||
@@ -997,11 +1013,18 @@ begin
|
||||
FWebServer.OnFMStep := WebOnFMStep;
|
||||
FWebServer.OnWebMic := WebOnMic;
|
||||
FWebServer.OnXvtrBand := WebOnXvtrBand;
|
||||
FWebServer.OnConnect := WebOnConnect;
|
||||
FWebServer.OnDiscoverDev := WebOnDiscover;
|
||||
FWebServer.OnDisconnectDev := WebOnDisconnect;
|
||||
FWebServer.OnDevAdd := WebOnDevAdd;
|
||||
FWebServer.OnDevRemove := WebOnDevRemove;
|
||||
FWebServer.OnDevAutoStart := WebOnDevAutoStart;
|
||||
// Web-слой держит FController.FWebClientActive актуальным (выбор mic-source
|
||||
// при TX, в т.ч. для HWPTT из контроллера) — больше не зеркалим лениво.
|
||||
FWebServer.OnClientActiveChanged := WebOnClientActiveChanged;
|
||||
if FWebEnabled then FWebServer.Start;
|
||||
PushXvtrToWeb;
|
||||
PushDeviceListToWeb; // saved-устройства сразу видны web-overlay
|
||||
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
|
||||
FCATLastGlobal.CATTcpPort := 19090;
|
||||
for i := 0 to 3 do
|
||||
@@ -1107,7 +1130,7 @@ begin
|
||||
// лишь навешиваем колбэки на методы MainForm. FSampleRate уже загружен из
|
||||
// startup-preview выше, поэтому FDSPEngine рождается с нужной частотой.
|
||||
FController.CreateEngines(FController.FSampleRate);
|
||||
FController.FNetwork.OnDeviceFound := OnDeviceFound;
|
||||
FController.FNetwork.OnDeviceFound := FController.OnDeviceFound;
|
||||
FController.FNetwork.OnHPStatus := FController.OnHPStatus;
|
||||
FController.FNetwork.OnDDCIQ := FController.OnDDCIQ;
|
||||
FController.FNetwork.OnMicPacket := FController.OnMicPacket;
|
||||
@@ -1466,6 +1489,28 @@ begin
|
||||
InvalidateGridCache;
|
||||
end;
|
||||
end;
|
||||
|
||||
rfDeviceList:
|
||||
begin
|
||||
// Discovery обновил список устройств (или завершился без них).
|
||||
if Assigned(FDeviceDialog) then
|
||||
begin
|
||||
if FController.FDiscoverNoneFound then
|
||||
begin
|
||||
FDeviceDialog.NoDevicesFound;
|
||||
SetStatusText(2, 'No hardware found');
|
||||
end
|
||||
else
|
||||
begin
|
||||
FDeviceDialog.RefreshFound;
|
||||
if FController.FDeviceStore.DiscoveredCount > 0 then
|
||||
SetStatusText(2, Format('Found: %s',
|
||||
[FController.FDeviceStore.Discovered(
|
||||
FController.FDeviceStore.DiscoveredCount - 1).IPAddress]));
|
||||
end;
|
||||
end;
|
||||
PushDeviceListToWeb; // web-overlay тоже получает обновлённый список
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FSyncingFromController := False;
|
||||
@@ -3263,34 +3308,8 @@ end;
|
||||
// Network callbacks → sync helpers
|
||||
// ===========================================================================
|
||||
|
||||
procedure TMainForm.OnDeviceFound(const Dev: THPSDRDevice);
|
||||
var
|
||||
BoardName, Entry: string;
|
||||
Sync: TDeviceFoundSync;
|
||||
M: TThreadMethod;
|
||||
begin
|
||||
BoardName := BoardTypeName(Dev.BoardType);
|
||||
Entry := Format('%s %s FW:%d DDC:%d',
|
||||
[Dev.IPAddress, BoardName,
|
||||
Dev.FirmwareVersion, Dev.NumDDCs]);
|
||||
Sync := TDeviceFoundSync.Create(DoAddDevice, Dev, Entry);
|
||||
try
|
||||
M := Sync.Execute;
|
||||
TThread.Synchronize(nil, M);
|
||||
finally
|
||||
Sync.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoAddDevice(const Dev: THPSDRDevice; const Entry: string);
|
||||
begin
|
||||
// Найденное устройство → общий store (через диалог, который пишет store +
|
||||
// рендерит список). MAC нужен для preload-rate lookup в BtnStartStopClick.
|
||||
if Assigned(FDeviceDialog) then
|
||||
FDeviceDialog.AddDiscovered(Dev.IPAddress, Entry, Dev.BoardType, Dev.MAC);
|
||||
SetStatusText(2, Format('Found: %s', [Dev.IPAddress]));
|
||||
end;
|
||||
|
||||
// OnDeviceFound / discovery теперь в TRadioController (сетевой поток → store +
|
||||
// rfDeviceList). UI слушает rfDeviceList в OnControllerState.
|
||||
|
||||
procedure TMainForm.UpdateTXMeters;
|
||||
// Вызывается из MeterTimerTick (10 Гц). FController.FLastFwdW/FLastSWR/
|
||||
@@ -3402,58 +3421,7 @@ begin
|
||||
FForm.OnWDSPOpenDone(FResult);
|
||||
end;
|
||||
|
||||
type
|
||||
TDiscoverThread = class(TThread)
|
||||
private
|
||||
FNet: THPSDRNetwork;
|
||||
FForm: TMainForm;
|
||||
protected
|
||||
procedure Execute; override;
|
||||
public
|
||||
constructor Create(ANet: THPSDRNetwork; AForm: TMainForm);
|
||||
end;
|
||||
|
||||
constructor TDiscoverThread.Create(ANet: THPSDRNetwork; AForm: TMainForm);
|
||||
begin
|
||||
FNet := ANet;
|
||||
FForm := AForm;
|
||||
FreeOnTerminate := True;
|
||||
inherited Create(False);
|
||||
end;
|
||||
|
||||
procedure TDiscoverThread.Execute;
|
||||
var
|
||||
Devs: THPSDRDeviceArray;
|
||||
Sync: TDDCSeqSync;
|
||||
M: TThreadMethod;
|
||||
begin
|
||||
Devs := FNet.Discover(2000);
|
||||
if Length(Devs) = 0 then
|
||||
begin
|
||||
// DDCIdx = -1 is the sentinel for "no device found"
|
||||
Sync := TDDCSeqSync.Create(FForm.DoUpdateDDCSeqOrNoDevice, -1, 0);
|
||||
try
|
||||
M := Sync.Execute;
|
||||
TThread.Synchronize(nil, M);
|
||||
finally
|
||||
Sync.Free;
|
||||
end;
|
||||
end;
|
||||
// Devices found are signalled via OnDeviceFound callback during Discover()
|
||||
end;
|
||||
|
||||
{ Перегруженный DoUpdateDDCSeq принимает -1 как «нет устройств» }
|
||||
procedure TMainForm.DoUpdateDDCSeqOrNoDevice(DDCIdx: Integer; Seq: LongWord);
|
||||
begin
|
||||
if DDCIdx = -1 then
|
||||
begin
|
||||
if Assigned(FDeviceDialog) then
|
||||
FDeviceDialog.NoDevicesFound;
|
||||
SetStatusText(2, 'No hardware found');
|
||||
end
|
||||
else
|
||||
SetStatusText(4, 'RX running');
|
||||
end;
|
||||
// TDiscoverThread / no-device-сигнал переехали в TRadioController.Discover.
|
||||
|
||||
procedure TMainForm.RecreateDSPEngine(ASampleRate: Integer);
|
||||
begin
|
||||
@@ -3554,12 +3522,11 @@ begin
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnDiscoverFromDialog(Sender: TObject);
|
||||
// Запускается когда пользователь нажимает DISCOVER внутри диалога
|
||||
// Запускается когда пользователь нажимает DISCOVER внутри диалога.
|
||||
// Контроллер чистит список и стартует поток; UI слушает rfDeviceList.
|
||||
begin
|
||||
FController.FDeviceStore.ClearDiscovered;
|
||||
SetStatusText(2, 'Discovering...');
|
||||
FController.FNetwork.DirectIP := '';
|
||||
TDiscoverThread.Create(FController.FNetwork, Self);
|
||||
FController.Discover;
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnStartStopClick(Sender: TObject);
|
||||
@@ -3570,29 +3537,7 @@ begin
|
||||
if FController.FNetwork.Connected then
|
||||
begin
|
||||
// --- STOP ---
|
||||
// Движок (graceful Run=0, сброс TX/телеметрии) + рендер run-кнопки/MOX/sleep
|
||||
// — в контроллере (StopRunning + OnControllerState rfRunning/rfTransmitting).
|
||||
FController.StopRunning;
|
||||
// Сохраняем текущую частоту/настройки чтобы следующий START восстановил их
|
||||
if FController.FDevConnected then
|
||||
begin
|
||||
SaveCurrentBand;
|
||||
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
|
||||
FController.FSettings.SaveAlex(FController.FDevMAC, FController.FAlexSettings);
|
||||
// XVTR LastFreq → актуальная видимая частота в момент остановки
|
||||
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
|
||||
FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings);
|
||||
end;
|
||||
FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate);
|
||||
FController.FSettings.Save;
|
||||
FSpecView.ResetSpectrumBuf;
|
||||
FSpecView.DrawSpectrum;
|
||||
PbSpectrum.Invalidate;
|
||||
FController.FNetwork.Disconnect;
|
||||
// Run-кнопка/MOX уже перерисованы StopRunning (rfRunning); здесь только
|
||||
// контекстный offline-статус.
|
||||
SetRadioOfflineStatus('Disconnected', True);
|
||||
DoStopAndDisconnect;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
@@ -3642,6 +3587,34 @@ begin
|
||||
StartWithDevice(Dev);
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoStopAndDisconnect;
|
||||
// STOP-teardown, общий для десктоп-STOP и web-disconnect. Движок (graceful Run=0,
|
||||
// сброс TX/телеметрии) + рендер run-кнопки/MOX/sleep — в контроллере (StopRunning
|
||||
// + OnControllerState). Здесь: persistence + сеть Disconnect + offline-статус.
|
||||
begin
|
||||
FController.StopRunning;
|
||||
// Сохраняем текущую частоту/настройки чтобы следующий START восстановил их
|
||||
if FController.FDevConnected then
|
||||
begin
|
||||
SaveCurrentBand;
|
||||
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
|
||||
FController.FSettings.SaveAlex(FController.FDevMAC, FController.FAlexSettings);
|
||||
// XVTR LastFreq → актуальная видимая частота в момент остановки
|
||||
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
|
||||
FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings);
|
||||
end;
|
||||
FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate);
|
||||
FController.FSettings.Save;
|
||||
FSpecView.ResetSpectrumBuf;
|
||||
FSpecView.DrawSpectrum;
|
||||
PbSpectrum.Invalidate;
|
||||
FController.FNetwork.Disconnect;
|
||||
// Run-кнопка/MOX уже перерисованы StopRunning (rfRunning); здесь только
|
||||
// контекстный offline-статус.
|
||||
SetRadioOfflineStatus('Disconnected', True);
|
||||
end;
|
||||
|
||||
procedure TMainForm.StartWithDevice(const Dev: THPSDRDevice);
|
||||
// Запуск с уже выбранным устройством — общий путь для десктоп-диалога и
|
||||
// web-оверлея (без модального диалога). Подбор sample rate до WDSP, открытие
|
||||
@@ -5227,9 +5200,144 @@ end;
|
||||
|
||||
procedure TMainForm.SyncWebRun;
|
||||
begin
|
||||
// Toggle only if state differs
|
||||
if FWebSyncBool <> FController.FRunning then
|
||||
BtnStartStopClick(nil);
|
||||
// set_run отвязан от десктопного модального диалога: web стартует через connect
|
||||
// (overlay), а set_run:true без выбранного устройства бессмысленно. Здесь только
|
||||
// STOP-путь (set_run:false), симметрично web-disconnect.
|
||||
if (not FWebSyncBool) and FController.FNetwork.Connected then
|
||||
DoStopAndDisconnect;
|
||||
end;
|
||||
|
||||
// ── Web discovery / connect / disconnect / saved-CRUD ───────────────────────
|
||||
|
||||
procedure TMainForm.WebOnConnect(const IP: string);
|
||||
begin
|
||||
FWebSyncStr1 := IP;
|
||||
FWebSyncM := SyncWebConnect;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebConnect;
|
||||
var
|
||||
Dev: THPSDRDevice;
|
||||
i: Integer;
|
||||
begin
|
||||
if FController.FNetwork.Connected then Exit; // уже подключены
|
||||
FillChar(Dev, SizeOf(Dev), 0);
|
||||
Dev.IPAddress := FWebSyncStr1;
|
||||
// BoardType (тип платы не определяется при Connect — только из discovery/saved):
|
||||
// сначала найденное устройство (есть и MAC для preload), иначе сохранённое по IP
|
||||
// — как десктоп берёт FPendingBoardType из GetSavedBoardType.
|
||||
i := FController.FDeviceStore.FindDiscoveredByIP(FWebSyncStr1);
|
||||
if i >= 0 then
|
||||
Dev.BoardType := FController.FDeviceStore.Discovered(i).BoardType
|
||||
else
|
||||
for i := 0 to FController.FDeviceStore.SavedCount - 1 do
|
||||
if FController.FDeviceStore.Saved(i).IPAddress = FWebSyncStr1 then
|
||||
begin
|
||||
Dev.BoardType := FController.FDeviceStore.Saved(i).BoardType;
|
||||
Break;
|
||||
end;
|
||||
StartWithDevice(Dev);
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnDiscover;
|
||||
begin
|
||||
FWebSyncM := SyncWebDiscover;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebDiscover;
|
||||
begin
|
||||
SetStatusText(2, 'Discovering...');
|
||||
FController.Discover; // rfDeviceList-рендер запушит обновлённый список в web
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnDisconnect;
|
||||
begin
|
||||
FWebSyncM := SyncWebDisconnect;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebDisconnect;
|
||||
begin
|
||||
if FController.FNetwork.Connected then
|
||||
DoStopAndDisconnect;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnDevAdd(const AName, IP: string);
|
||||
begin
|
||||
FWebSyncStr1 := AName;
|
||||
FWebSyncStr2 := IP;
|
||||
FWebSyncM := SyncWebDevAdd;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebDevAdd;
|
||||
begin
|
||||
FController.FDeviceStore.AddSaved(FWebSyncStr1, FWebSyncStr2, 0);
|
||||
PushDeviceListToWeb;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnDevRemove(Idx: Integer);
|
||||
begin
|
||||
FWebSyncInt := Idx;
|
||||
FWebSyncM := SyncWebDevRemove;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebDevRemove;
|
||||
begin
|
||||
FController.FDeviceStore.RemoveSaved(FWebSyncInt);
|
||||
PushDeviceListToWeb;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WebOnDevAutoStart(Idx: Integer);
|
||||
begin
|
||||
FWebSyncInt := Idx;
|
||||
FWebSyncM := SyncWebDevAutoStart;
|
||||
TThread.Synchronize(nil, FWebSyncM);
|
||||
end;
|
||||
|
||||
procedure TMainForm.SyncWebDevAutoStart;
|
||||
begin
|
||||
FController.FDeviceStore.SetAutoStart(FWebSyncInt);
|
||||
PushDeviceListToWeb;
|
||||
end;
|
||||
|
||||
procedure TMainForm.PushDeviceListToWeb;
|
||||
// store (saved+discovered) → FWebServer.SetDeviceList. Saved идут первыми, поэтому
|
||||
// их позиция в массиве == saved-индекс (web шлёт его в dev_remove/dev_autostart).
|
||||
var
|
||||
Arr: TWebDeviceArray;
|
||||
i, k: Integer;
|
||||
D: TDiscoveredDevice;
|
||||
Sv: TSavedDevice;
|
||||
begin
|
||||
if FWebServer = nil then Exit;
|
||||
SetLength(Arr, FController.FDeviceStore.SavedCount +
|
||||
FController.FDeviceStore.DiscoveredCount);
|
||||
k := 0;
|
||||
for i := 0 to FController.FDeviceStore.SavedCount - 1 do
|
||||
begin
|
||||
Sv := FController.FDeviceStore.Saved(i);
|
||||
Arr[k].Name := Sv.Name;
|
||||
Arr[k].IP := Sv.IPAddress;
|
||||
Arr[k].BoardType := Sv.BoardType;
|
||||
Arr[k].IsSaved := True;
|
||||
Arr[k].AutoStart := Sv.AutoStart;
|
||||
Inc(k);
|
||||
end;
|
||||
for i := 0 to FController.FDeviceStore.DiscoveredCount - 1 do
|
||||
begin
|
||||
D := FController.FDeviceStore.Discovered(i);
|
||||
Arr[k].Name := D.DisplayName;
|
||||
Arr[k].IP := D.IPAddress;
|
||||
Arr[k].BoardType := D.BoardType;
|
||||
Arr[k].IsSaved := False;
|
||||
Arr[k].AutoStart := False;
|
||||
Inc(k);
|
||||
end;
|
||||
FWebServer.SetDeviceList(Arr);
|
||||
end;
|
||||
|
||||
// ── Mute ───────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user