mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 20:57:32 +00:00
feat: Pluto support in headless daemon (connect/autostart/discover/dev_add) + headless audio
The headless daemon (ewsdrd) only ever built HPSDR devices, so Pluto could not be used: backend is chosen by Dev.Kind and Pluto opens by URI, neither of which the daemon propagated. Also local audio played on the host and web Discover never probed network Plutos. Shared, backend-agnostic helpers in TRadioController (used by GUI + daemon): - ResolveDevice(IP): discovered->saved lookup restoring Kind/URI/Serial/ BoardType/MAC. MainForm.ResolveDevice now delegates here. - AddSavedDevice(name, addr): web dev_add saves Pluto when addr has a URI scheme (ip:/usb:/local:), else HPSDR. Both web hosts use it. - SeedPlutoProbeFromSaved: seed network-probe URIs from saved Plutos (no mDNS -> a network Pluto is only found by direct URI probe). - LocalAudioEnabled flag (GUI=True): when False the local sound card is neither opened nor written; RX audio goes only to web (OnAudioConsume). DeviceStore.AutoStartDevice(out Dev): full autostart record (Kind/URI/ Serial) so a saved Pluto (URI-addressed, empty IPAddress on USB) can autostart. Daemon (ewsdrd.lpr): LocalAudioEnabled:=False; SyncConnect via ResolveDevice; autostart via AutoStartDevice; SyncDiscover seeds probe URIs then Discover; SyncDevAdd via AddSavedDevice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,9 @@ type
|
|||||||
procedure SetAutoStart(Idx: Integer); // эксклюзивно: только одно autostart
|
procedure SetAutoStart(Idx: Integer); // эксклюзивно: только одно autostart
|
||||||
function AutoStartIP: string;
|
function AutoStartIP: string;
|
||||||
function AutoStartBoardType: Integer;
|
function AutoStartBoardType: Integer;
|
||||||
|
// Полная autostart-запись (Kind/URI/Serial) — для бэкенд-агностичного автозапуска
|
||||||
|
// (Pluto открывается по URI, у USB-Pluto IPAddress пуст). False — нет autostart.
|
||||||
|
function AutoStartDevice(out Dev: TSavedDevice): Boolean;
|
||||||
function SavedBoardType(Idx: Integer): Integer;
|
function SavedBoardType(Idx: Integer): Integer;
|
||||||
function SavedDisplay(Idx: Integer): string; // строка для списка (с * и board)
|
function SavedDisplay(Idx: Integer): string; // строка для списка (с * и board)
|
||||||
|
|
||||||
@@ -242,6 +245,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TDeviceStore.AutoStartDevice(out Dev: TSavedDevice): Boolean;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
for I := 0 to FSavedCount - 1 do
|
||||||
|
if FSaved[I].AutoStart then
|
||||||
|
begin
|
||||||
|
Dev := FSaved[I];
|
||||||
|
Exit(True);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TDeviceStore.SavedBoardType(Idx: Integer): Integer;
|
function TDeviceStore.SavedBoardType(Idx: Integer): Integer;
|
||||||
begin
|
begin
|
||||||
if (Idx >= 0) and (Idx < FSavedCount) then
|
if (Idx >= 0) and (Idx < FSavedCount) then
|
||||||
|
|||||||
+3
-35
@@ -3578,41 +3578,9 @@ end;
|
|||||||
|
|
||||||
function TMainForm.ResolveDevice(const IP: string): THPSDRDevice;
|
function TMainForm.ResolveDevice(const IP: string): THPSDRDevice;
|
||||||
// По IP/URI ищет устройство среди discovered, затем saved, чтобы восстановить
|
// По IP/URI ищет устройство среди discovered, затем saved, чтобы восстановить
|
||||||
// Kind/URI/Serial/MAC (нужно для Pluto). Если не найдено — простой HPSDR по IP.
|
// Kind/URI/Serial/MAC (нужно для Pluto). Логика общая с демоном — в контроллере.
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
dd: TDiscoveredDevice;
|
|
||||||
sd: TSavedDevice;
|
|
||||||
begin
|
begin
|
||||||
// Result — managed-поля (строки) при входе nil, FillChar безопасен.
|
Result := FController.ResolveDevice(IP);
|
||||||
FillChar(Result, SizeOf(Result), 0);
|
|
||||||
Result.Kind := bkHPSDR;
|
|
||||||
Result.IPAddress := IP;
|
|
||||||
|
|
||||||
i := FController.FDeviceStore.FindDiscoveredByIP(IP);
|
|
||||||
if i >= 0 then
|
|
||||||
begin
|
|
||||||
dd := FController.FDeviceStore.Discovered(i);
|
|
||||||
Result.Kind := dd.Kind;
|
|
||||||
Result.URI := dd.URI;
|
|
||||||
Result.Serial := dd.Serial;
|
|
||||||
Result.BoardType := dd.BoardType;
|
|
||||||
Move(dd.MAC[0], Result.MAC[0], 6);
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
for i := 0 to FController.FDeviceStore.SavedCount - 1 do
|
|
||||||
begin
|
|
||||||
sd := FController.FDeviceStore.Saved(i);
|
|
||||||
if SameText(sd.IPAddress, IP) or SameText(sd.URI, IP) then
|
|
||||||
begin
|
|
||||||
Result.Kind := sd.Kind;
|
|
||||||
Result.URI := sd.URI;
|
|
||||||
Result.Serial := sd.Serial;
|
|
||||||
Result.BoardType := sd.BoardType;
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.DoStopAndDisconnect;
|
procedure TMainForm.DoStopAndDisconnect;
|
||||||
@@ -5012,7 +4980,7 @@ end;
|
|||||||
|
|
||||||
procedure TMainForm.SyncWebDevAdd;
|
procedure TMainForm.SyncWebDevAdd;
|
||||||
begin
|
begin
|
||||||
FController.FDeviceStore.AddSaved(FWebSyncStr1, FWebSyncStr2, 0);
|
FController.AddSavedDevice(FWebSyncStr1, FWebSyncStr2); // Pluto по схеме URI
|
||||||
PushDeviceListToWeb;
|
PushDeviceListToWeb;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+128
-27
@@ -129,6 +129,7 @@ type
|
|||||||
FOnStateChanged: TRadioStateEvent;
|
FOnStateChanged: TRadioStateEvent;
|
||||||
FOnAfterTune: TThreadMethod; // пост-тюн хук: канал-оркестрация после SetVfoA
|
FOnAfterTune: TThreadMethod; // пост-тюн хук: канал-оркестрация после SetVfoA
|
||||||
FOnAudioConsume: TAudioConsumeEvent; // web-перехват RX-аудио (если активен клиент)
|
FOnAudioConsume: TAudioConsumeEvent; // web-перехват RX-аудио (если активен клиент)
|
||||||
|
FLocalAudio: Boolean; // локальный звук (динамик/мик ПК): GUI=True, демон=False
|
||||||
FOnSpectrumData: TPixelDataEvent; // сырьё спектра наружу (UI рисует FSpecView)
|
FOnSpectrumData: TPixelDataEvent; // сырьё спектра наружу (UI рисует FSpecView)
|
||||||
FOnWaterfallData: TPixelDataEvent; // сырьё водопада наружу (UI: децимация+рендер)
|
FOnWaterfallData: TPixelDataEvent; // сырьё водопада наружу (UI: децимация+рендер)
|
||||||
procedure Changed(Field: TRadioField);
|
procedure Changed(Field: TRadioField);
|
||||||
@@ -528,6 +529,14 @@ type
|
|||||||
function ConnectDevice(const Dev: THPSDRDevice): Boolean; // Connect + load-by-MAC; рендер через rfDevice
|
function ConnectDevice(const Dev: THPSDRDevice): Boolean; // Connect + load-by-MAC; рендер через rfDevice
|
||||||
function ConnectAndRun(const Dev: THPSDRDevice; SpectrumWidth: Integer): Boolean; // ConnectDevice + band/xvtr/vfo restore + go-live
|
function ConnectAndRun(const Dev: THPSDRDevice; SpectrumWidth: Integer): Boolean; // ConnectDevice + band/xvtr/vfo restore + go-live
|
||||||
procedure StopAndDisconnect; // graceful Run=0 + персист настроек + Disconnect (headless)
|
procedure StopAndDisconnect; // graceful Run=0 + персист настроек + Disconnect (headless)
|
||||||
|
// По IP/URI ищет устройство в store (discovered→saved), восстанавливая
|
||||||
|
// Kind/URI/Serial/BoardType/MAC. Нужно для Pluto (открывается по URI, выбор
|
||||||
|
// бэкенда — по Kind). Не найдено → простой HPSDR по IP. Общий путь GUI+демон.
|
||||||
|
function ResolveDevice(const IP: string): THPSDRDevice;
|
||||||
|
// Сохраняет устройство по введённому адресу. Схема URI ('ip:'/'usb:'/'local:')
|
||||||
|
// → Pluto (AddSavedPluto), иначе HPSDR-IP. Общий путь web-dev_add для GUI и
|
||||||
|
// демона (десктоп-диалог сохраняет Pluto своим путём через AddSavedPluto).
|
||||||
|
procedure AddSavedDevice(const AName, AAddr: string);
|
||||||
procedure Discover;
|
procedure Discover;
|
||||||
// Гарантирует, что FNetwork — бэкенд нужного типа (HPSDR/Pluto): при
|
// Гарантирует, что FNetwork — бэкенд нужного типа (HPSDR/Pluto): при
|
||||||
// несовпадении пересоздаёт его, перенося колбэки данных. Вызывается из
|
// несовпадении пересоздаёт его, перенося колбэки данных. Вызывается из
|
||||||
@@ -547,6 +556,9 @@ type
|
|||||||
// Список URI для probe сетевых Pluto (mDNS недоступен) — задаёт фронтенд
|
// Список URI для probe сетевых Pluto (mDNS недоступен) — задаёт фронтенд
|
||||||
// перед Discover (сохранённые Pluto + ручной ввод IP).
|
// перед Discover (сохранённые Pluto + ручной ввод IP).
|
||||||
procedure SetPlutoProbeURIs(const URIs: array of string);
|
procedure SetPlutoProbeURIs(const URIs: array of string);
|
||||||
|
// Сидирует probe-список из сохранённых Pluto (для бэкенд-агностичного Discover
|
||||||
|
// без UI-диалога — демон). GUI добавляет ручной IP поверх в BuildProbeURIs.
|
||||||
|
procedure SeedPlutoProbeFromSaved;
|
||||||
|
|
||||||
// ---- События наружу ----
|
// ---- События наружу ----
|
||||||
property OnInvoke: TUIInvokeEvent read FOnInvoke write FOnInvoke;
|
property OnInvoke: TUIInvokeEvent read FOnInvoke write FOnInvoke;
|
||||||
@@ -556,6 +568,9 @@ type
|
|||||||
// чтобы любой путь тюнинга (desktop/web/CAT/энкодер) был канал-aware headless.
|
// чтобы любой путь тюнинга (desktop/web/CAT/энкодер) был канал-aware headless.
|
||||||
property OnAfterTune: TThreadMethod read FOnAfterTune write FOnAfterTune;
|
property OnAfterTune: TThreadMethod read FOnAfterTune write FOnAfterTune;
|
||||||
property OnAudioConsume: TAudioConsumeEvent read FOnAudioConsume write FOnAudioConsume;
|
property OnAudioConsume: TAudioConsumeEvent read FOnAudioConsume write FOnAudioConsume;
|
||||||
|
// Локальный звук ПК (динамик RX / микрофон TX). Демон ставит False: аудио
|
||||||
|
// только через web (OnAudioConsume), локальная звуковая карта не трогается.
|
||||||
|
property LocalAudioEnabled: Boolean read FLocalAudio write FLocalAudio;
|
||||||
property OnSpectrumData: TPixelDataEvent read FOnSpectrumData write FOnSpectrumData;
|
property OnSpectrumData: TPixelDataEvent read FOnSpectrumData write FOnSpectrumData;
|
||||||
property OnWaterfallData: TPixelDataEvent read FOnWaterfallData write FOnWaterfallData;
|
property OnWaterfallData: TPixelDataEvent read FOnWaterfallData write FOnWaterfallData;
|
||||||
end;
|
end;
|
||||||
@@ -580,6 +595,7 @@ begin
|
|||||||
FPlutoTxMaxAttDb := -10.0; // ADI-дефолт: линейный режим выходных каскадов
|
FPlutoTxMaxAttDb := -10.0; // ADI-дефолт: линейный режим выходных каскадов
|
||||||
FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
|
FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
|
||||||
FFMStepOn := True;
|
FFMStepOn := True;
|
||||||
|
FLocalAudio := True; // GUI: звук на локальную карту; демон выключит явно
|
||||||
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
||||||
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE;
|
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE;
|
||||||
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
||||||
@@ -896,7 +912,10 @@ begin
|
|||||||
FNetwork.SendSpeakerAudio(Left, Right, Count);
|
FNetwork.SendSpeakerAudio(Left, Right, Count);
|
||||||
if Assigned(FOnAudioConsume) and FOnAudioConsume(Left, Right, Count) then
|
if Assigned(FOnAudioConsume) and FOnAudioConsume(Left, Right, Count) then
|
||||||
Exit;
|
Exit;
|
||||||
FAudioOut.Write(Left, Right, Count);
|
// Без локального звука (демон) на этом маршрут заканчивается: если web-клиент
|
||||||
|
// не активен, аудио просто отбрасывается, а не идёт на звуковую карту хоста.
|
||||||
|
if FLocalAudio then
|
||||||
|
FAudioOut.Write(Left, Right, Count);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.OnSpectrumReady(const Pixels: array of Single;
|
procedure TRadioController.OnSpectrumReady(const Pixels: array of Single;
|
||||||
@@ -2790,6 +2809,58 @@ begin
|
|||||||
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.ResolveDevice(const IP: string): THPSDRDevice;
|
||||||
|
// discovered→saved по IP/URI: восстанавливает Kind/URI/Serial/BoardType/MAC,
|
||||||
|
// иначе простой HPSDR по IP. См. одноимённую обёртку в GUI (TMainForm).
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
dd: TDiscoveredDevice;
|
||||||
|
sd: TSavedDevice;
|
||||||
|
begin
|
||||||
|
FillChar(Result, SizeOf(Result), 0);
|
||||||
|
Result.Kind := bkHPSDR;
|
||||||
|
Result.IPAddress := IP;
|
||||||
|
|
||||||
|
i := FDeviceStore.FindDiscoveredByIP(IP);
|
||||||
|
if i >= 0 then
|
||||||
|
begin
|
||||||
|
dd := FDeviceStore.Discovered(i);
|
||||||
|
Result.Kind := dd.Kind;
|
||||||
|
Result.URI := dd.URI;
|
||||||
|
Result.Serial := dd.Serial;
|
||||||
|
Result.BoardType := dd.BoardType;
|
||||||
|
Move(dd.MAC[0], Result.MAC[0], 6);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
for i := 0 to FDeviceStore.SavedCount - 1 do
|
||||||
|
begin
|
||||||
|
sd := FDeviceStore.Saved(i);
|
||||||
|
if SameText(sd.IPAddress, IP) or SameText(sd.URI, IP) then
|
||||||
|
begin
|
||||||
|
Result.Kind := sd.Kind;
|
||||||
|
Result.URI := sd.URI;
|
||||||
|
Result.Serial := sd.Serial;
|
||||||
|
Result.BoardType := sd.BoardType;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.AddSavedDevice(const AName, AAddr: string);
|
||||||
|
// web-dev_add: Pluto по схеме URI ('ip:'/'usb:'/'local:'), иначе HPSDR-IP.
|
||||||
|
// Serial при ручном добавлении неизвестен (допишется при connect/probe).
|
||||||
|
var
|
||||||
|
A: string;
|
||||||
|
begin
|
||||||
|
A := Trim(AAddr);
|
||||||
|
if A = '' then Exit;
|
||||||
|
if (Pos('ip:', A) = 1) or (Pos('usb:', A) = 1) or (Pos('local:', A) = 1) then
|
||||||
|
FDeviceStore.AddSavedPluto(AName, A, '')
|
||||||
|
else
|
||||||
|
FDeviceStore.AddSaved(AName, A, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.ConnectDevice(const Dev: THPSDRDevice): Boolean;
|
function TRadioController.ConnectDevice(const Dev: THPSDRDevice): Boolean;
|
||||||
// Подключение + загрузка per-device настроек по MAC в поля контроллера + конфиг
|
// Подключение + загрузка per-device настроек по MAC в поля контроллера + конфиг
|
||||||
// движка (DSP display, audio). Возвращает False, если Connect не удался (UI
|
// движка (DSP display, audio). Возвращает False, если Connect не удался (UI
|
||||||
@@ -2884,33 +2955,39 @@ begin
|
|||||||
FDSPEngine.SetWaterfallDisplay(FLoadedGlobal.WfDetector, FLoadedGlobal.WfAvgMode,
|
FDSPEngine.SetWaterfallDisplay(FLoadedGlobal.WfDetector, FLoadedGlobal.WfAvgMode,
|
||||||
FLoadedGlobal.WfAvgTimeMS);
|
FLoadedGlobal.WfAvgTimeMS);
|
||||||
end;
|
end;
|
||||||
// Аудио устройство вывода
|
// Локальная звуковая карта (динамик RX / микрофон TX). В демоне FLocalAudio=
|
||||||
FAudioOut.Close;
|
// False: карту не открываем (иначе headless-хост хватает ALSA-устройство и
|
||||||
if FLoadedGlobal.AudioSampleRate > 0 then
|
// играет звук). Web-аудио идёт через OnAudioConsume и от FAudioOut не зависит.
|
||||||
FAudioOut.SampleRate := FLoadedGlobal.AudioSampleRate;
|
if FLocalAudio then
|
||||||
if FLoadedGlobal.AudioOutDevice <> '' then
|
|
||||||
begin
|
begin
|
||||||
FAudioOut.DeviceIndex := FAudioOut.FindDeviceByName(FLoadedGlobal.AudioOutDevice);
|
// Аудио устройство вывода
|
||||||
FAudioOutDevName := FLoadedGlobal.AudioOutDevice;
|
FAudioOut.Close;
|
||||||
end
|
if FLoadedGlobal.AudioSampleRate > 0 then
|
||||||
else
|
FAudioOut.SampleRate := FLoadedGlobal.AudioSampleRate;
|
||||||
begin
|
if FLoadedGlobal.AudioOutDevice <> '' then
|
||||||
FAudioOut.DeviceIndex := -1;
|
begin
|
||||||
FAudioOutDevName := '';
|
FAudioOut.DeviceIndex := FAudioOut.FindDeviceByName(FLoadedGlobal.AudioOutDevice);
|
||||||
end;
|
FAudioOutDevName := FLoadedGlobal.AudioOutDevice;
|
||||||
FAudioOut.Open;
|
end
|
||||||
// Аудио устройство ввода (TX mic). MicSource определяется в SetMOX по PTT —
|
else
|
||||||
// здесь только открываем устройство.
|
begin
|
||||||
if FLoadedGlobal.AudioInDevice <> '' then
|
FAudioOut.DeviceIndex := -1;
|
||||||
begin
|
FAudioOutDevName := '';
|
||||||
FAudioIn.DeviceIndex := FAudioIn.FindDeviceByName(FLoadedGlobal.AudioInDevice);
|
end;
|
||||||
FAudioInDevName := FLoadedGlobal.AudioInDevice;
|
FAudioOut.Open;
|
||||||
FAudioIn.Open;
|
// Аудио устройство ввода (TX mic). MicSource определяется в SetMOX по PTT —
|
||||||
end
|
// здесь только открываем устройство.
|
||||||
else
|
if FLoadedGlobal.AudioInDevice <> '' then
|
||||||
begin
|
begin
|
||||||
FAudioIn.DeviceIndex := -1;
|
FAudioIn.DeviceIndex := FAudioIn.FindDeviceByName(FLoadedGlobal.AudioInDevice);
|
||||||
FAudioInDevName := '';
|
FAudioInDevName := FLoadedGlobal.AudioInDevice;
|
||||||
|
FAudioIn.Open;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
FAudioIn.DeviceIndex := -1;
|
||||||
|
FAudioInDevName := '';
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -3153,6 +3230,30 @@ begin
|
|||||||
for i := 0 to High(URIs) do FPlutoProbeURIs[i] := URIs[i];
|
for i := 0 to High(URIs) do FPlutoProbeURIs[i] := URIs[i];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SeedPlutoProbeFromSaved;
|
||||||
|
// Заполняет probe-список URI всех сохранённых Pluto (mDNS нет → сетевой Pluto
|
||||||
|
// находится только прямым probe его URI). Демон зовёт это перед Discover; GUI
|
||||||
|
// добавляет ещё и ручной IP из диалога. Дедупликация по URI.
|
||||||
|
var
|
||||||
|
URIs: array of string;
|
||||||
|
i, k: Integer;
|
||||||
|
U: string;
|
||||||
|
begin
|
||||||
|
SetLength(URIs, 0);
|
||||||
|
for i := 0 to FDeviceStore.SavedCount - 1 do
|
||||||
|
if FDeviceStore.Saved(i).Kind = bkPluto then
|
||||||
|
begin
|
||||||
|
U := FDeviceStore.Saved(i).URI;
|
||||||
|
if U = '' then Continue;
|
||||||
|
for k := 0 to High(URIs) do
|
||||||
|
if SameText(URIs[k], U) then begin U := ''; Break; end;
|
||||||
|
if U = '' then Continue;
|
||||||
|
SetLength(URIs, Length(URIs) + 1);
|
||||||
|
URIs[High(URIs)] := U;
|
||||||
|
end;
|
||||||
|
SetPlutoProbeURIs(URIs);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.OnDeviceFound(const Dev: THPSDRDevice);
|
procedure TRadioController.OnDeviceFound(const Dev: THPSDRDevice);
|
||||||
// Сетевой/scan-поток: строим строку для списка → pending → маршалинг в
|
// Сетевой/scan-поток: строим строку для списка → pending → маршалинг в
|
||||||
// ApplyDeviceFound. Строка зависит от типа бэкенда (HPSDR vs Pluto).
|
// ApplyDeviceFound. Строка зависит от типа бэкенда (HPSDR vs Pluto).
|
||||||
|
|||||||
+27
-26
@@ -28,7 +28,7 @@ uses
|
|||||||
{$IFDEF UNIX}cthreads, BaseUnix,{$ENDIF}
|
{$IFDEF UNIX}cthreads, BaseUnix,{$ENDIF}
|
||||||
Classes, SysUtils,
|
Classes, SysUtils,
|
||||||
RadioController, WebServer, WebAdapter,
|
RadioController, WebServer, WebAdapter,
|
||||||
HPSDRNetwork, Settings, WDSP, PlatformUtils;
|
HPSDRNetwork, Settings, WDSP, PlatformUtils, DeviceStore;
|
||||||
|
|
||||||
const
|
const
|
||||||
DAEMON_SPEC_WIDTH = 1024; // ширина спектра для StartRunning (web-буфер = 1024)
|
DAEMON_SPEC_WIDTH = 1024; // ширина спектра для StartRunning (web-буфер = 1024)
|
||||||
@@ -66,6 +66,7 @@ type
|
|||||||
// Sync* — выполняются в главном потоке через DoInvoke.
|
// Sync* — выполняются в главном потоке через DoInvoke.
|
||||||
procedure SyncConnect;
|
procedure SyncConnect;
|
||||||
procedure SyncDisconnect;
|
procedure SyncDisconnect;
|
||||||
|
procedure SyncDiscover;
|
||||||
procedure SyncRun;
|
procedure SyncRun;
|
||||||
procedure SyncDevAdd;
|
procedure SyncDevAdd;
|
||||||
procedure SyncDevRemove;
|
procedure SyncDevRemove;
|
||||||
@@ -112,6 +113,7 @@ var
|
|||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FCtrl := TRadioController.Create; // создаёт FSettings + FDeviceStore (LoadSaved)
|
FCtrl := TRadioController.Create; // создаёт FSettings + FDeviceStore (LoadSaved)
|
||||||
|
FCtrl.LocalAudioEnabled := False; // headless: звук только в web, не на карту хоста
|
||||||
FCtrl.OnInvoke := @DoInvoke;
|
FCtrl.OnInvoke := @DoInvoke;
|
||||||
FCtrl.OnStateChanged := @OnState;
|
FCtrl.OnStateChanged := @OnState;
|
||||||
|
|
||||||
@@ -298,7 +300,7 @@ procedure THeadlessHost.WebConnect(const IP: string);
|
|||||||
begin FsStr1 := IP; FCtrl.Invoke(@SyncConnect); end;
|
begin FsStr1 := IP; FCtrl.Invoke(@SyncConnect); end;
|
||||||
|
|
||||||
procedure THeadlessHost.WebDiscover;
|
procedure THeadlessHost.WebDiscover;
|
||||||
begin FCtrl.Invoke(@FCtrl.Discover); end; // Discover уже headless
|
begin FCtrl.Invoke(@SyncDiscover); end;
|
||||||
|
|
||||||
procedure THeadlessHost.WebDisconnect;
|
procedure THeadlessHost.WebDisconnect;
|
||||||
begin FCtrl.Invoke(@SyncDisconnect); end;
|
begin FCtrl.Invoke(@SyncDisconnect); end;
|
||||||
@@ -315,25 +317,11 @@ begin FsInt := Idx; FCtrl.Invoke(@SyncDevAutoStart); end;
|
|||||||
// --- Sync* (главный поток) ---
|
// --- Sync* (главный поток) ---
|
||||||
|
|
||||||
procedure THeadlessHost.SyncConnect;
|
procedure THeadlessHost.SyncConnect;
|
||||||
var
|
|
||||||
Dev: THPSDRDevice;
|
|
||||||
i: Integer;
|
|
||||||
begin
|
begin
|
||||||
if FCtrl.FNetwork.Connected then Exit;
|
if FCtrl.FNetwork.Connected then Exit;
|
||||||
FillChar(Dev, SizeOf(Dev), 0);
|
// Kind/URI/Serial/BoardType при Connect не приходят — восстанавливаем из store
|
||||||
Dev.IPAddress := FsStr1;
|
// (discovered→saved). Без Kind/URI бэкенд бы выбрался HPSDR, и Pluto не открылся.
|
||||||
// BoardType не приходит при Connect — берём из discovery, иначе из saved по IP.
|
ConnectTo(FCtrl.ResolveDevice(FsStr1));
|
||||||
i := FCtrl.FDeviceStore.FindDiscoveredByIP(FsStr1);
|
|
||||||
if i >= 0 then
|
|
||||||
Dev.BoardType := FCtrl.FDeviceStore.Discovered(i).BoardType
|
|
||||||
else
|
|
||||||
for i := 0 to FCtrl.FDeviceStore.SavedCount - 1 do
|
|
||||||
if FCtrl.FDeviceStore.Saved(i).IPAddress = FsStr1 then
|
|
||||||
begin
|
|
||||||
Dev.BoardType := FCtrl.FDeviceStore.Saved(i).BoardType;
|
|
||||||
Break;
|
|
||||||
end;
|
|
||||||
ConnectTo(Dev);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure THeadlessHost.SyncDisconnect;
|
procedure THeadlessHost.SyncDisconnect;
|
||||||
@@ -345,6 +333,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure THeadlessHost.SyncDiscover;
|
||||||
|
begin
|
||||||
|
// Сетевой Pluto без mDNS находится только прямым probe URI — сидируем список из
|
||||||
|
// сохранённых Pluto (в GUI это делает BuildProbeURIs). Без этого Discover видит
|
||||||
|
// лишь HPSDR-broadcast и USB-Pluto, а сетевые Pluto — никогда.
|
||||||
|
FCtrl.SeedPlutoProbeFromSaved;
|
||||||
|
FCtrl.Discover;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure THeadlessHost.SyncRun;
|
procedure THeadlessHost.SyncRun;
|
||||||
begin
|
begin
|
||||||
// set_run:true бессмыслен без выбранного устройства (web стартует через connect);
|
// set_run:true бессмыслен без выбранного устройства (web стартует через connect);
|
||||||
@@ -357,7 +354,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure THeadlessHost.SyncDevAdd;
|
procedure THeadlessHost.SyncDevAdd;
|
||||||
begin FCtrl.FDeviceStore.AddSaved(FsStr1, FsStr2, 0); PushDeviceList; end;
|
begin FCtrl.AddSavedDevice(FsStr1, FsStr2); PushDeviceList; end;
|
||||||
|
|
||||||
procedure THeadlessHost.SyncDevRemove;
|
procedure THeadlessHost.SyncDevRemove;
|
||||||
begin FCtrl.FDeviceStore.RemoveSaved(FsInt); PushDeviceList; end;
|
begin FCtrl.FDeviceStore.RemoveSaved(FsInt); PushDeviceList; end;
|
||||||
@@ -374,7 +371,7 @@ procedure THeadlessHost.Run;
|
|||||||
var
|
var
|
||||||
WebCfg: TWebSettings;
|
WebCfg: TWebSettings;
|
||||||
Dev: THPSDRDevice;
|
Dev: THPSDRDevice;
|
||||||
AutoIP: string;
|
AutoDev: TSavedDevice;
|
||||||
lastPush, nowMs: QWord;
|
lastPush, nowMs: QWord;
|
||||||
begin
|
begin
|
||||||
FCtrl.FSettings.LoadWebSettings(WebCfg);
|
FCtrl.FSettings.LoadWebSettings(WebCfg);
|
||||||
@@ -391,13 +388,17 @@ begin
|
|||||||
// не перетрётся обратно в True.
|
// не перетрётся обратно в True.
|
||||||
FRunning := True;
|
FRunning := True;
|
||||||
|
|
||||||
// Autostart — подключаемся сразу к отмеченному устройству.
|
// Autostart — подключаемся сразу к отмеченному устройству. Берём полную запись
|
||||||
AutoIP := FCtrl.FDeviceStore.AutoStartIP;
|
// (Kind/URI/Serial), иначе Pluto не подключить: бэкенд выбирается по Kind, USB-
|
||||||
if AutoIP <> '' then
|
// Pluto открывается по URI ('usb:..') и не имеет IPAddress.
|
||||||
|
if FCtrl.FDeviceStore.AutoStartDevice(AutoDev) then
|
||||||
begin
|
begin
|
||||||
FillChar(Dev, SizeOf(Dev), 0);
|
FillChar(Dev, SizeOf(Dev), 0);
|
||||||
Dev.IPAddress := AutoIP;
|
Dev.Kind := AutoDev.Kind;
|
||||||
Dev.BoardType := FCtrl.FDeviceStore.AutoStartBoardType;
|
Dev.IPAddress := AutoDev.IPAddress;
|
||||||
|
Dev.URI := AutoDev.URI;
|
||||||
|
Dev.Serial := AutoDev.Serial;
|
||||||
|
Dev.BoardType := AutoDev.BoardType;
|
||||||
ConnectTo(Dev);
|
ConnectTo(Dev);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user