Phase 3 (batch 31a): shared TDeviceStore for saved+discovered devices

Introduce DeviceStore.pas (TDeviceStore) as the single source of truth for
saved devices (hpsdr_devices.ini CRUD + autostart) and the current discovery
list. The controller owns one instance (created/freed in its ctor/dtor);
desktop and (later) web frontends edit/render through it so the lists never
diverge.

Refactor TDeviceDialog to use a TDeviceStore reference instead of its own
arrays + ini code. MainForm wires FDeviceDialog.Store to the controller's
store, routes discovery results (DoAddDevice) and the preload-rate lookup
through it (TDiscoveredDevice now carries the MAC), and drops the now-unused
FDevices/TDeviceItem. Behavior-preserving for the desktop dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-08 12:36:43 +03:00
co-authored by Claude Opus 4.8
parent 783c8976c9
commit 7f7f429515
4 changed files with 368 additions and 212 deletions
+63 -173
View File
@@ -6,21 +6,10 @@ interface
uses uses
Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs, Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, IniFiles, StdCtrls, ExtCtrls, ComCtrls,
BoardUtils, PlatformUtils; BoardUtils, PlatformUtils, DeviceStore;
const
DEVICE_CFG_NAME = 'hpsdr_devices.ini';
type type
// Запись о сохранённом устройстве
TSavedDevice = record
Name: string; // пользовательское имя
IPAddress: string;
BoardType: Integer;
AutoStart: Boolean; // запускать автоматически при старте
end;
// Результат диалога // Результат диалога
TDeviceDialogResult = record TDeviceDialogResult = record
Accepted: Boolean; Accepted: Boolean;
@@ -31,17 +20,11 @@ type
{ TDeviceDialog } { TDeviceDialog }
TDeviceDialog = class(TForm) TDeviceDialog = class(TForm)
private private
// Сохранённые устройства // Общее хранилище устройств (saved+discovered) — единый источник правды,
FSavedDevices: array of TSavedDevice; // владелец контроллер; диалог только рендерит и редактирует через него.
FSavedCount: Integer; FStore: TDeviceStore;
FResult: TDeviceDialogResult; FResult: TDeviceDialogResult;
// Discovered devices (IP strings)
FDiscoveredIPs: array of string;
FDiscoveredNames: array of string;
FDiscoveredBoardTypes: array of Integer;
FDiscoveredCount: Integer;
// UI // UI
PanelTop: TPanel; PanelTop: TPanel;
PanelBottom: TPanel; PanelBottom: TPanel;
@@ -70,8 +53,7 @@ type
procedure BuildUI; procedure BuildUI;
procedure ApplyTheme; procedure ApplyTheme;
procedure LoadSaved; procedure SetStore(AStore: TDeviceStore);
procedure SaveSaved;
procedure RefreshSavedList; procedure RefreshSavedList;
procedure BtnDiscoverClick(Sender: TObject); procedure BtnDiscoverClick(Sender: TObject);
@@ -95,8 +77,10 @@ type
procedure SetTheme(const T: TAppTheme); procedure SetTheme(const T: TAppTheme);
// Добавить найденное устройство (вызывается из MainForm при discovery) // Добавить найденное устройство (вызывается из MainForm при discovery)
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer = 0); procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
procedure ClearDiscovered; procedure ClearDiscovered;
procedure NoDevicesFound; // UI-сообщение «нет устройств» в список найденных
// Автозапуск: возвращает IP если есть устройство с AutoStart=True // Автозапуск: возвращает IP если есть устройство с AutoStart=True
function GetAutoStartIP: string; function GetAutoStartIP: string;
@@ -105,9 +89,11 @@ type
// Получить/сбросить результат // Получить/сбросить результат
procedure ClearResult; procedure ClearResult;
// Общее хранилище устройств — назначается владельцем (MainForm/контроллер)
// до показа диалога; диалог рендерит и редактирует через него.
property Store: TDeviceStore read FStore write SetStore;
property DialogResult: TDeviceDialogResult read FResult; property DialogResult: TDeviceDialogResult read FResult;
property OnDiscover: TNotifyEvent read FOnDiscover write FOnDiscover; property OnDiscover: TNotifyEvent read FOnDiscover write FOnDiscover;
property SavedCount: Integer read FSavedCount;
end; end;
implementation implementation
@@ -145,14 +131,17 @@ begin
Font.Size := 8; Font.Size := 8;
Font.Color := CLR_TEXT; Font.Color := CLR_TEXT;
FSavedCount := 0; FStore := nil; // назначается владельцем через property Store до показа
FDiscoveredCount := 0;
FResult.Accepted := False; FResult.Accepted := False;
BuildUI; BuildUI;
SetTheme(DarkTheme); SetTheme(DarkTheme);
LoadSaved; end;
RefreshSavedList;
procedure TDeviceDialog.SetStore(AStore: TDeviceStore);
begin
FStore := AStore;
if FStore <> nil then RefreshSavedList;
end; end;
function TDeviceDialog.MakeBtn(AParent: TWinControl; const Cap: string; function TDeviceDialog.MakeBtn(AParent: TWinControl; const Cap: string;
@@ -348,80 +337,26 @@ begin
Invalidate; Invalidate;
end; end;
procedure TDeviceDialog.LoadSaved;
var
Ini: TIniFile;
I, N: Integer;
Section: string;
begin
FSavedCount := 0;
if not FileExists(GetAppCfgDir + DEVICE_CFG_NAME) then Exit;
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
N := Ini.ReadInteger('Devices', 'Count', 0);
SetLength(FSavedDevices, N);
for I := 0 to N - 1 do
begin
Section := 'Device' + IntToStr(I);
FSavedDevices[I].Name := Ini.ReadString (Section, 'Name', 'HPSDR');
FSavedDevices[I].IPAddress := Ini.ReadString (Section, 'IP', '');
FSavedDevices[I].BoardType := Ini.ReadInteger(Section, 'BoardType', 0);
FSavedDevices[I].AutoStart := Ini.ReadBool (Section, 'AutoStart', False);
Inc(FSavedCount);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceDialog.SaveSaved;
var
Ini: TIniFile;
I: Integer;
Section: string;
begin
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
Ini.WriteInteger('Devices', 'Count', FSavedCount);
for I := 0 to FSavedCount - 1 do
begin
Section := 'Device' + IntToStr(I);
Ini.WriteString (Section, 'Name', FSavedDevices[I].Name);
Ini.WriteString (Section, 'IP', FSavedDevices[I].IPAddress);
Ini.WriteInteger(Section, 'BoardType', FSavedDevices[I].BoardType);
Ini.WriteBool (Section, 'AutoStart', FSavedDevices[I].AutoStart);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceDialog.RefreshSavedList; procedure TDeviceDialog.RefreshSavedList;
var var
I: Integer; I: Integer;
S: string;
begin begin
LstSaved.Items.Clear; LstSaved.Items.Clear;
for I := 0 to FSavedCount - 1 do if FStore = nil then Exit;
begin for I := 0 to FStore.SavedCount - 1 do
S := FSavedDevices[I].Name + ' [' + FSavedDevices[I].IPAddress + ']'; LstSaved.Items.Add(FStore.SavedDisplay(I));
if FSavedDevices[I].BoardType > 0 then
S := S + ' ' + BoardTypeName(FSavedDevices[I].BoardType);
if FSavedDevices[I].AutoStart then
S := '* ' + S;
LstSaved.Items.Add(S);
end;
end; end;
procedure TDeviceDialog.LstSavedClick(Sender: TObject); procedure TDeviceDialog.LstSavedClick(Sender: TObject);
var var
Idx: Integer; Idx: Integer;
Dev: TSavedDevice;
begin begin
Idx := LstSaved.ItemIndex; Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then Exit; if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then Exit;
EdName.Text := FSavedDevices[Idx].Name; Dev := FStore.Saved(Idx);
EdIP.Text := FSavedDevices[Idx].IPAddress; EdName.Text := Dev.Name;
EdIP.Text := Dev.IPAddress;
end; end;
procedure TDeviceDialog.LstSavedDblClick(Sender: TObject); procedure TDeviceDialog.LstSavedDblClick(Sender: TObject);
@@ -444,29 +379,20 @@ end;
procedure TDeviceDialog.ClearDiscovered; procedure TDeviceDialog.ClearDiscovered;
begin begin
FDiscoveredCount := 0; if FStore <> nil then FStore.ClearDiscovered;
SetLength(FDiscoveredIPs, 0);
SetLength(FDiscoveredNames, 0);
SetLength(FDiscoveredBoardTypes, 0);
LstFound.Items.Clear; LstFound.Items.Clear;
end; end;
procedure TDeviceDialog.AddDiscovered(const IP, DisplayName: string; BoardType: Integer = 0); procedure TDeviceDialog.AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
var var
Idx: Integer;
S: string; S: string;
begin begin
if FStore = nil then Exit;
if (LstFound.Items.Count = 1) and (LstFound.Items[0] = 'Searching...') then if (LstFound.Items.Count = 1) and (LstFound.Items[0] = 'Searching...') then
LstFound.Items.Clear; LstFound.Items.Clear;
Idx := FDiscoveredCount; FStore.AddDiscovered(IP, DisplayName, BoardType, MAC);
Inc(FDiscoveredCount);
SetLength(FDiscoveredIPs, FDiscoveredCount);
SetLength(FDiscoveredNames, FDiscoveredCount);
SetLength(FDiscoveredBoardTypes, FDiscoveredCount);
FDiscoveredIPs[Idx] := IP;
FDiscoveredNames[Idx] := DisplayName;
FDiscoveredBoardTypes[Idx] := BoardType;
S := DisplayName; S := DisplayName;
if BoardType > 0 then if BoardType > 0 then
@@ -474,44 +400,34 @@ begin
LstFound.Items.Add(S); LstFound.Items.Add(S);
end; end;
procedure TDeviceDialog.NoDevicesFound;
begin
ClearDiscovered;
LstFound.Items.Add('-- no device found --');
end;
procedure TDeviceDialog.BtnAddClick(Sender: TObject); procedure TDeviceDialog.BtnAddClick(Sender: TObject);
var var
Idx: Integer; Idx: Integer;
begin begin
if FStore = nil then Exit;
if Trim(EdIP.Text) = '' then if Trim(EdIP.Text) = '' then
begin begin
ShowMessage('Enter IP address'); ShowMessage('Enter IP address');
Exit; Exit;
end; end;
Idx := FStore.AddSaved(EdName.Text, EdIP.Text, 0);
Idx := FSavedCount;
Inc(FSavedCount);
SetLength(FSavedDevices, FSavedCount);
FSavedDevices[Idx].Name := Trim(EdName.Text);
if FSavedDevices[Idx].Name = '' then
FSavedDevices[Idx].Name := 'HPSDR';
FSavedDevices[Idx].IPAddress := Trim(EdIP.Text);
FSavedDevices[Idx].BoardType := 0;
FSavedDevices[Idx].AutoStart := False;
SaveSaved;
RefreshSavedList; RefreshSavedList;
LstSaved.ItemIndex := Idx; LstSaved.ItemIndex := Idx;
end; end;
procedure TDeviceDialog.BtnRemoveClick(Sender: TObject); procedure TDeviceDialog.BtnRemoveClick(Sender: TObject);
var var
Idx, I: Integer; Idx: Integer;
begin begin
Idx := LstSaved.ItemIndex; Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then Exit; if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then Exit;
FStore.RemoveSaved(Idx);
for I := Idx to FSavedCount - 2 do
FSavedDevices[I] := FSavedDevices[I + 1];
Dec(FSavedCount);
SetLength(FSavedDevices, FSavedCount);
SaveSaved;
RefreshSavedList; RefreshSavedList;
EdName.Text := ''; EdName.Text := '';
EdIP.Text := ''; EdIP.Text := '';
@@ -519,45 +435,34 @@ end;
procedure TDeviceDialog.BtnSetAutoClick(Sender: TObject); procedure TDeviceDialog.BtnSetAutoClick(Sender: TObject);
var var
Idx, I: Integer; Idx: Integer;
begin begin
Idx := LstSaved.ItemIndex; Idx := LstSaved.ItemIndex;
if (Idx < 0) or (Idx >= FSavedCount) then if (FStore = nil) or (Idx < 0) or (Idx >= FStore.SavedCount) then
begin begin
ShowMessage('Select a device first'); ShowMessage('Select a device first');
Exit; Exit;
end; end;
FStore.SetAutoStart(Idx);
// Только одно устройство может быть AutoStart
for I := 0 to FSavedCount - 1 do
FSavedDevices[I].AutoStart := (I = Idx);
SaveSaved;
RefreshSavedList; RefreshSavedList;
LstSaved.ItemIndex := Idx; LstSaved.ItemIndex := Idx;
end; end;
procedure TDeviceDialog.BtnAddFoundClick(Sender: TObject); procedure TDeviceDialog.BtnAddFoundClick(Sender: TObject);
var var
Idx: Integer; Idx, NewIdx: Integer;
D: TDiscoveredDevice;
begin begin
Idx := LstFound.ItemIndex; Idx := LstFound.ItemIndex;
if (Idx < 0) or (Idx >= FDiscoveredCount) then if (FStore = nil) or (Idx < 0) or (Idx >= FStore.DiscoveredCount) then
begin begin
ShowMessage('Select a discovered device first'); ShowMessage('Select a discovered device first');
Exit; Exit;
end; end;
EdIP.Text := FDiscoveredIPs[Idx]; D := FStore.Discovered(Idx);
EdName.Text := FDiscoveredNames[Idx]; NewIdx := FStore.AddSaved(D.DisplayName, D.IPAddress, D.BoardType);
BtnAddClick(nil);
// Обновляем BoardType только что добавленной записи
if FSavedCount > 0 then
begin
FSavedDevices[FSavedCount - 1].BoardType := FDiscoveredBoardTypes[Idx];
SaveSaved;
RefreshSavedList; RefreshSavedList;
LstSaved.ItemIndex := FSavedCount - 1; LstSaved.ItemIndex := NewIdx;
end;
end; end;
procedure TDeviceDialog.BtnConnectClick(Sender: TObject); procedure TDeviceDialog.BtnConnectClick(Sender: TObject);
@@ -566,20 +471,21 @@ var
Idx: Integer; Idx: Integer;
begin begin
IP := ''; IP := '';
if FStore = nil then Exit;
// Приоритет: выбранное сохранённое > выбранное найденное > ручной IP // Приоритет: выбранное сохранённое > выбранное найденное > ручной IP
Idx := LstSaved.ItemIndex; Idx := LstSaved.ItemIndex;
if (Idx >= 0) and (Idx < FSavedCount) then if (Idx >= 0) and (Idx < FStore.SavedCount) then
begin begin
IP := FSavedDevices[Idx].IPAddress; IP := FStore.Saved(Idx).IPAddress;
FResult.SavedIdx := Idx; FResult.SavedIdx := Idx;
end end
else else
begin begin
Idx := LstFound.ItemIndex; Idx := LstFound.ItemIndex;
if (Idx >= 0) and (Idx < FDiscoveredCount) then if (Idx >= 0) and (Idx < FStore.DiscoveredCount) then
begin begin
IP := FDiscoveredIPs[Idx]; IP := FStore.Discovered(Idx).IPAddress;
FResult.SavedIdx := -1; FResult.SavedIdx := -1;
end end
else if Trim(EdIP.Text) <> '' then else if Trim(EdIP.Text) <> '' then
@@ -614,37 +520,21 @@ begin
end; end;
function TDeviceDialog.GetAutoStartIP: string; function TDeviceDialog.GetAutoStartIP: string;
var
I: Integer;
begin begin
Result := ''; if FStore <> nil then Result := FStore.AutoStartIP
for I := 0 to FSavedCount - 1 do else Result := '';
if FSavedDevices[I].AutoStart then
begin
Result := FSavedDevices[I].IPAddress;
Exit;
end;
end; end;
function TDeviceDialog.GetAutoStartBoardType: Integer; function TDeviceDialog.GetAutoStartBoardType: Integer;
var
I: Integer;
begin begin
Result := 0; if FStore <> nil then Result := FStore.AutoStartBoardType
for I := 0 to FSavedCount - 1 do else Result := 0;
if FSavedDevices[I].AutoStart then
begin
Result := FSavedDevices[I].BoardType;
Exit;
end;
end; end;
function TDeviceDialog.GetSavedBoardType(Idx: Integer): Integer; function TDeviceDialog.GetSavedBoardType(Idx: Integer): Integer;
begin begin
if (Idx >= 0) and (Idx < FSavedCount) then if FStore <> nil then Result := FStore.SavedBoardType(Idx)
Result := FSavedDevices[Idx].BoardType else Result := 0;
else
Result := 0;
end; end;
end. end.
+279
View File
@@ -0,0 +1,279 @@
unit DeviceStore;
{$mode objfpc}{$H+}
// Общее хранилище устройств — единый источник правды для десктоп-диалога
// (DeviceForm) и web-оверлея. Владеет сохранёнными устройствами (persist в
// hpsdr_devices.ini) и текущим списком найденных при discovery.
// Контроллер (TRadioController) держит один экземпляр; фронтенды работают
// через него, поэтому списки не расходятся.
interface
uses
Classes, SysUtils, IniFiles, BoardUtils, PlatformUtils;
const
DEVICE_CFG_NAME = 'hpsdr_devices.ini';
type
// Запись о сохранённом устройстве
TSavedDevice = record
Name: string; // пользовательское имя
IPAddress: string;
BoardType: Integer;
AutoStart: Boolean; // запускать автоматически при старте
end;
// Найденное устройство (результат discovery)
TDiscoveredDevice = record
IPAddress: string;
DisplayName: string;
BoardType: Integer;
MAC: array[0..5] of Byte; // нужен для preload-rate lookup (LoadDevice по MAC)
end;
{ TDeviceStore }
TDeviceStore = class
private
FSaved: array of TSavedDevice;
FSavedCount: Integer;
FDiscovered: array of TDiscoveredDevice;
FDiscoveredCount: Integer;
public
constructor Create;
// ---- Сохранённые устройства ----
procedure LoadSaved;
procedure SaveSaved;
function SavedCount: Integer;
function Saved(Idx: Integer): TSavedDevice;
// Добавляет запись (Name пустое → 'HPSDR'); возвращает индекс новой записи.
function AddSaved(const AName, IP: string; BoardType: Integer): Integer;
procedure RemoveSaved(Idx: Integer);
procedure SetAutoStart(Idx: Integer); // эксклюзивно: только одно autostart
function AutoStartIP: string;
function AutoStartBoardType: Integer;
function SavedBoardType(Idx: Integer): Integer;
function SavedDisplay(Idx: Integer): string; // строка для списка (с * и board)
// ---- Найденные (discovery) ----
procedure ClearDiscovered;
procedure AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
function DiscoveredCount: Integer;
function Discovered(Idx: Integer): TDiscoveredDevice;
// IP найденного устройства по IP (для preload-rate lookup и т.п.)
function FindDiscoveredByIP(const IP: string): Integer;
end;
implementation
{ TDeviceStore }
constructor TDeviceStore.Create;
begin
inherited Create;
FSavedCount := 0;
FDiscoveredCount := 0;
LoadSaved;
end;
procedure TDeviceStore.LoadSaved;
var
Ini: TIniFile;
I, N: Integer;
Section: string;
begin
FSavedCount := 0;
SetLength(FSaved, 0);
if not FileExists(GetAppCfgDir + DEVICE_CFG_NAME) then Exit;
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
N := Ini.ReadInteger('Devices', 'Count', 0);
SetLength(FSaved, N);
for I := 0 to N - 1 do
begin
Section := 'Device' + IntToStr(I);
FSaved[I].Name := Ini.ReadString (Section, 'Name', 'HPSDR');
FSaved[I].IPAddress := Ini.ReadString (Section, 'IP', '');
FSaved[I].BoardType := Ini.ReadInteger(Section, 'BoardType', 0);
FSaved[I].AutoStart := Ini.ReadBool (Section, 'AutoStart', False);
Inc(FSavedCount);
end;
finally
Ini.Free;
end;
end;
procedure TDeviceStore.SaveSaved;
var
Ini: TIniFile;
I: Integer;
Section: string;
begin
Ini := TIniFile.Create(GetAppCfgDir + DEVICE_CFG_NAME);
try
Ini.WriteInteger('Devices', 'Count', FSavedCount);
for I := 0 to FSavedCount - 1 do
begin
Section := 'Device' + IntToStr(I);
Ini.WriteString (Section, 'Name', FSaved[I].Name);
Ini.WriteString (Section, 'IP', FSaved[I].IPAddress);
Ini.WriteInteger(Section, 'BoardType', FSaved[I].BoardType);
Ini.WriteBool (Section, 'AutoStart', FSaved[I].AutoStart);
end;
finally
Ini.Free;
end;
end;
function TDeviceStore.SavedCount: Integer;
begin
Result := FSavedCount;
end;
function TDeviceStore.Saved(Idx: Integer): TSavedDevice;
begin
if (Idx >= 0) and (Idx < FSavedCount) then
Result := FSaved[Idx]
else
begin
Result.Name := ''; Result.IPAddress := '';
Result.BoardType := 0; Result.AutoStart := False;
end;
end;
function TDeviceStore.AddSaved(const AName, IP: string; BoardType: Integer): Integer;
begin
Result := FSavedCount;
Inc(FSavedCount);
SetLength(FSaved, FSavedCount);
if Trim(AName) <> '' then FSaved[Result].Name := Trim(AName)
else FSaved[Result].Name := 'HPSDR';
FSaved[Result].IPAddress := Trim(IP);
FSaved[Result].BoardType := BoardType;
FSaved[Result].AutoStart := False;
SaveSaved;
end;
procedure TDeviceStore.RemoveSaved(Idx: Integer);
var
I: Integer;
begin
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
for I := Idx to FSavedCount - 2 do
FSaved[I] := FSaved[I + 1];
Dec(FSavedCount);
SetLength(FSaved, FSavedCount);
SaveSaved;
end;
procedure TDeviceStore.SetAutoStart(Idx: Integer);
var
I: Integer;
begin
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
// Только одно устройство может быть AutoStart
for I := 0 to FSavedCount - 1 do
FSaved[I].AutoStart := (I = Idx);
SaveSaved;
end;
function TDeviceStore.AutoStartIP: string;
var
I: Integer;
begin
Result := '';
for I := 0 to FSavedCount - 1 do
if FSaved[I].AutoStart then
begin
Result := FSaved[I].IPAddress;
Exit;
end;
end;
function TDeviceStore.AutoStartBoardType: Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to FSavedCount - 1 do
if FSaved[I].AutoStart then
begin
Result := FSaved[I].BoardType;
Exit;
end;
end;
function TDeviceStore.SavedBoardType(Idx: Integer): Integer;
begin
if (Idx >= 0) and (Idx < FSavedCount) then
Result := FSaved[Idx].BoardType
else
Result := 0;
end;
function TDeviceStore.SavedDisplay(Idx: Integer): string;
begin
Result := '';
if (Idx < 0) or (Idx >= FSavedCount) then Exit;
Result := FSaved[Idx].Name + ' [' + FSaved[Idx].IPAddress + ']';
if FSaved[Idx].BoardType > 0 then
Result := Result + ' ' + BoardTypeName(FSaved[Idx].BoardType);
if FSaved[Idx].AutoStart then
Result := '* ' + Result;
end;
procedure TDeviceStore.ClearDiscovered;
begin
FDiscoveredCount := 0;
SetLength(FDiscovered, 0);
end;
procedure TDeviceStore.AddDiscovered(const IP, DisplayName: string; BoardType: Integer;
const MAC: array of Byte);
var
Idx, J: Integer;
begin
Idx := FDiscoveredCount;
Inc(FDiscoveredCount);
SetLength(FDiscovered, FDiscoveredCount);
FDiscovered[Idx].IPAddress := IP;
FDiscovered[Idx].DisplayName := DisplayName;
FDiscovered[Idx].BoardType := BoardType;
FillChar(FDiscovered[Idx].MAC, SizeOf(FDiscovered[Idx].MAC), 0);
for J := 0 to High(MAC) do
if J <= 5 then FDiscovered[Idx].MAC[J] := MAC[J];
end;
function TDeviceStore.DiscoveredCount: Integer;
begin
Result := FDiscoveredCount;
end;
function TDeviceStore.Discovered(Idx: Integer): TDiscoveredDevice;
begin
if (Idx >= 0) and (Idx < FDiscoveredCount) then
Result := FDiscovered[Idx]
else
begin
Result.IPAddress := ''; Result.DisplayName := ''; Result.BoardType := 0;
end;
end;
function TDeviceStore.FindDiscoveredByIP(const IP: string): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to FDiscoveredCount - 1 do
if SameText(FDiscovered[I].IPAddress, IP) then
begin
Result := I;
Exit;
end;
end;
end.
+15 -31
View File
@@ -41,6 +41,7 @@ uses
FlatEdit, FMRepeater, FlatEdit, FMRepeater,
ChannelStore, ChannelsForm, ChannelStore, ChannelsForm,
PowerInhibit, PowerInhibit,
DeviceStore,
RadioController; RadioController;
const const
@@ -107,11 +108,6 @@ const
LEFT_PANEL_BTN_GAP = 2; LEFT_PANEL_BTN_GAP = 2;
type type
TDeviceItem = record
Dev: THPSDRDevice;
Display: string;
end;
{ TMainForm } { TMainForm }
TMainForm = class(TForm) TMainForm = class(TForm)
private private
@@ -124,8 +120,7 @@ type
FSyncingFromController: Boolean; FSyncingFromController: Boolean;
// ---- Network ---- // ---- Network ----
FDevices: array of TDeviceItem; // Список устройств (saved+discovered) → FController.FDeviceStore (общий с web).
FDeviceCount: Integer;
FDeviceDialog: TDeviceDialog; FDeviceDialog: TDeviceDialog;
FPendingIP: string; // IP устройства для подключения FPendingIP: string; // IP устройства для подключения
FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре
@@ -1039,8 +1034,8 @@ begin
FController.FLastSupplyV:= -1; FController.FLastSupplyV:= -1;
FController.FLastSupplyA:= -1; FController.FLastSupplyA:= -1;
FController.FLastPLLLock:= False; FController.FLastPLLLock:= False;
FDeviceCount := 0;
FDeviceDialog := TDeviceDialog.Create(Self); FDeviceDialog := TDeviceDialog.Create(Self);
FDeviceDialog.Store := FController.FDeviceStore;
FDeviceDialog.OnDiscover := BtnDiscoverFromDialog; FDeviceDialog.OnDiscover := BtnDiscoverFromDialog;
FVfoOverlay := TVfoOverlay.Create(Self); FVfoOverlay := TVfoOverlay.Create(Self);
FVfoOverlay.OnSelect := OnModeFilterSelect; FVfoOverlay.OnSelect := OnModeFilterSelect;
@@ -3285,18 +3280,11 @@ begin
end; end;
procedure TMainForm.DoAddDevice(const Dev: THPSDRDevice; const Entry: string); procedure TMainForm.DoAddDevice(const Dev: THPSDRDevice; const Entry: string);
var
Idx: Integer;
begin begin
Idx := Length(FDevices); // Найденное устройство → общий store (через диалог, который пишет store +
SetLength(FDevices, Idx + 1); // рендерит список). MAC нужен для preload-rate lookup в BtnStartStopClick.
FDevices[Idx].Dev := Dev;
FDevices[Idx].Display := Entry;
Inc(FDeviceCount);
if Assigned(FDeviceDialog) then if Assigned(FDeviceDialog) then
FDeviceDialog.AddDiscovered(Dev.IPAddress, Entry, Dev.BoardType); FDeviceDialog.AddDiscovered(Dev.IPAddress, Entry, Dev.BoardType, Dev.MAC);
SetStatusText(2, Format('Found: %s', [Dev.IPAddress])); SetStatusText(2, Format('Found: %s', [Dev.IPAddress]));
end; end;
@@ -3457,10 +3445,7 @@ begin
if DDCIdx = -1 then if DDCIdx = -1 then
begin begin
if Assigned(FDeviceDialog) then if Assigned(FDeviceDialog) then
begin FDeviceDialog.NoDevicesFound;
FDeviceDialog.ClearDiscovered;
FDeviceDialog.AddDiscovered('', '-- no device found --');
end;
SetStatusText(2, 'No hardware found'); SetStatusText(2, 'No hardware found');
end end
else else
@@ -3555,6 +3540,7 @@ begin
// Открываем диалог выбора устройства // Открываем диалог выбора устройства
if not Assigned(FDeviceDialog) then if not Assigned(FDeviceDialog) then
FDeviceDialog := TDeviceDialog.Create(Self); FDeviceDialog := TDeviceDialog.Create(Self);
FDeviceDialog.Store := FController.FDeviceStore;
FDeviceDialog.OnDiscover := BtnDiscoverFromDialog; FDeviceDialog.OnDiscover := BtnDiscoverFromDialog;
if FLightTheme then FDeviceDialog.SetTheme(LightTheme) if FLightTheme then FDeviceDialog.SetTheme(LightTheme)
else FDeviceDialog.SetTheme(DarkTheme); else FDeviceDialog.SetTheme(DarkTheme);
@@ -3567,8 +3553,7 @@ end;
procedure TMainForm.BtnDiscoverFromDialog(Sender: TObject); procedure TMainForm.BtnDiscoverFromDialog(Sender: TObject);
// Запускается когда пользователь нажимает DISCOVER внутри диалога // Запускается когда пользователь нажимает DISCOVER внутри диалога
begin begin
SetLength(FDevices, 0); FController.FDeviceStore.ClearDiscovered;
FDeviceCount := 0;
SetStatusText(2, 'Discovering...'); SetStatusText(2, 'Discovering...');
FController.FNetwork.DirectIP := ''; FController.FNetwork.DirectIP := '';
TDiscoverThread.Create(FController.FNetwork, Self); TDiscoverThread.Create(FController.FNetwork, Self);
@@ -3576,10 +3561,8 @@ end;
procedure TMainForm.BtnStartStopClick(Sender: TObject); procedure TMainForm.BtnStartStopClick(Sender: TObject);
var var
Idx, i: Integer; i: Integer;
Dev: THPSDRDevice; Dev: THPSDRDevice;
GenPkt: TGeneralPacket;
G_Settings: TGlobalSettings;
PreG: TGlobalSettings; PreG: TGlobalSettings;
PreBands: array[0..CFG_BAND_COUNT-1] of TBandSettings; PreBands: array[0..CFG_BAND_COUNT-1] of TBandSettings;
PreloadRate: Integer; PreloadRate: Integer;
@@ -3641,6 +3624,7 @@ begin
begin begin
if not Assigned(FDeviceDialog) then if not Assigned(FDeviceDialog) then
FDeviceDialog := TDeviceDialog.Create(Self); FDeviceDialog := TDeviceDialog.Create(Self);
FDeviceDialog.Store := FController.FDeviceStore;
FDeviceDialog.OnDiscover := BtnDiscoverFromDialog; FDeviceDialog.OnDiscover := BtnDiscoverFromDialog;
if FLightTheme then FDeviceDialog.SetTheme(LightTheme) if FLightTheme then FDeviceDialog.SetTheme(LightTheme)
else FDeviceDialog.SetTheme(DarkTheme); else FDeviceDialog.SetTheme(DarkTheme);
@@ -3664,13 +3648,13 @@ begin
// создаём WDSPEngine сразу с нужной частотой. // создаём WDSPEngine сразу с нужной частотой.
PreloadRate := FController.FSampleRate; PreloadRate := FController.FSampleRate;
FoundByIP := False; FoundByIP := False;
for i := 0 to High(FDevices) do i := FController.FDeviceStore.FindDiscoveredByIP(Dev.IPAddress);
if SameText(FDevices[i].Dev.IPAddress, Dev.IPAddress) then if i >= 0 then
begin begin
FoundByIP := True; FoundByIP := True;
if FController.FSettings.LoadDevice(FDevices[i].Dev.MAC, PreG, PreBands) and (PreG.SampleRate > 0) then if FController.FSettings.LoadDevice(FController.FDeviceStore.Discovered(i).MAC, PreG, PreBands)
and (PreG.SampleRate > 0) then
PreloadRate := PreG.SampleRate; PreloadRate := PreG.SampleRate;
Break;
end; end;
if FoundByIP and (PreloadRate > 0) then if FoundByIP and (PreloadRate > 0) then
begin begin
+4 -1
View File
@@ -50,7 +50,7 @@ uses
Classes, SysUtils, Math, Classes, SysUtils, Math,
HPSDRProtocol, HPSDRNetwork, HPSDRProtocol, HPSDRNetwork,
WDSPEngine, AudioOutput, AudioInput, WDSPEngine, AudioOutput, AudioInput,
Settings, ChannelStore, FMRepeater, BoardUtils; Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
type type
// Поле, которое изменилось — для гранулярных уведомлений наружу. // Поле, которое изменилось — для гранулярных уведомлений наружу.
@@ -128,6 +128,7 @@ type
FAudioIn: TAudioInput; FAudioIn: TAudioInput;
FSettings: TSettingsManager; FSettings: TSettingsManager;
FChannelStore: TChannelStore; FChannelStore: TChannelStore;
FDeviceStore: TDeviceStore; // общий список устройств (saved+discovered)
FWDSPReady: Boolean; FWDSPReady: Boolean;
// ---- Частоты / режим / фильтр / AGC ---- // ---- Частоты / режим / фильтр / AGC ----
@@ -413,6 +414,7 @@ constructor TRadioController.Create;
begin begin
inherited Create; inherited Create;
FSettings := TSettingsManager.Create; // владелец настроек (GUI и демон) FSettings := TSettingsManager.Create; // владелец настроек (GUI и демон)
FDeviceStore := TDeviceStore.Create; // общий список устройств (desktop+web)
// Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны). // Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны).
FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0; FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0;
FMode := 1; FFilter := 5; FFilterBW := 2700; FMode := 1; FFilter := 5; FFilterBW := 2700;
@@ -459,6 +461,7 @@ end;
destructor TRadioController.Destroy; destructor TRadioController.Destroy;
begin begin
FreeEngines; FreeEngines;
FreeAndNil(FDeviceStore);
FreeAndNil(FSettings); FreeAndNil(FSettings);
inherited Destroy; inherited Destroy;
end; end;