unit DeviceStore; {$mode objfpc}{$H+} // Общее хранилище устройств — единый источник правды для десктоп-диалога // (DeviceForm) и web-оверлея. Владеет сохранёнными устройствами (persist в // hpsdr_devices.ini) и текущим списком найденных при discovery. // Контроллер (TRadioController) держит один экземпляр; фронтенды работают // через него, поэтому списки не расходятся. interface uses Classes, SysUtils, IniFiles, BoardUtils, PlatformUtils, RadioBackend; const DEVICE_CFG_NAME = 'hpsdr_devices.ini'; type // Запись о сохранённом устройстве TSavedDevice = record Name: string; // пользовательское имя IPAddress: string; BoardType: Integer; AutoStart: Boolean; // запускать автоматически при старте Kind: TBackendKind; // bkHPSDR / bkPluto URI: string; // Pluto: 'ip:..' / 'usb:..' Serial: string; // Pluto serial end; // Найденное устройство (результат discovery) TDiscoveredDevice = record IPAddress: string; DisplayName: string; BoardType: Integer; MAC: array[0..5] of Byte; // нужен для preload-rate lookup (LoadDevice по MAC) Kind: TBackendKind; // bkHPSDR / bkPluto URI: string; // Pluto: context URI Serial: string; // Pluto serial 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; // Добавляет Pluto-устройство (Kind=bkPluto) с URI/Serial. function AddSavedPluto(const AName, AURI, ASerial: string): 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; Kind: TBackendKind = bkHPSDR; const URI: string = ''; const Serial: string = ''); 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); FSaved[I].Kind := TBackendKind(Ini.ReadInteger(Section, 'Kind', 0)); FSaved[I].URI := Ini.ReadString (Section, 'URI', ''); FSaved[I].Serial := Ini.ReadString (Section, 'Serial', ''); 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); Ini.WriteInteger(Section, 'Kind', Ord(FSaved[I].Kind)); Ini.WriteString (Section, 'URI', FSaved[I].URI); Ini.WriteString (Section, 'Serial', FSaved[I].Serial); 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; FSaved[Result].Kind := bkHPSDR; FSaved[Result].URI := ''; FSaved[Result].Serial := ''; SaveSaved; end; function TDeviceStore.AddSavedPluto(const AName, AURI, ASerial: string): Integer; begin Result := FSavedCount; Inc(FSavedCount); SetLength(FSaved, FSavedCount); if Trim(AName) <> '' then FSaved[Result].Name := Trim(AName) else FSaved[Result].Name := 'PlutoSDR'; FSaved[Result].IPAddress := Trim(AURI); // в списке показываем URI FSaved[Result].BoardType := 0; FSaved[Result].AutoStart := False; FSaved[Result].Kind := bkPluto; FSaved[Result].URI := Trim(AURI); FSaved[Result].Serial := Trim(ASerial); 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; Kind: TBackendKind; const URI: string; const Serial: string); var Idx, J: Integer; begin Idx := FDiscoveredCount; Inc(FDiscoveredCount); SetLength(FDiscovered, FDiscoveredCount); FDiscovered[Idx].IPAddress := IP; FDiscovered[Idx].DisplayName := DisplayName; FDiscovered[Idx].BoardType := BoardType; FDiscovered[Idx].Kind := Kind; FDiscovered[Idx].URI := URI; FDiscovered[Idx].Serial := Serial; 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.