From 66c9fa63b98cb8e2c941592c8a2ff25a1fa011be Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 21 Jul 2026 22:43:32 +0300 Subject: [PATCH] =?UTF-8?q?fix(settings):=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B0=20=D1=83=D0=B5=D0=B7=D0=B6=D0=B0=D0=BB=20=D0=B2=20?= =?UTF-8?q?=D0=BD=D1=83=D0=BB=D0=B5=D0=B2=D0=BE=D0=B9=20MAC=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D1=81=D1=82=D0=B0=D1=80=D1=82=D0=B5=20=D0=BF=D0=BE=20?= =?UTF-8?q?AutoStart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Настройки персистятся секцией по MAC радио, но ResolveDevice брал MAC только из списка найденных дискавери. У сохранённых устройств поля MAC не было вовсе, и START по AutoStart/CONNECT без дискавери шёл с MAC 00:00:00:00:00:00 — грузился и сохранялся чужой профиль. Пользователь видел «настройки сбросились»: выключался wideband, менялся sample rate, терялись банды и паны, причём в зависимости от того, нажимал ли он перед стартом DISCOVER. - DeviceStore: у TSavedDevice поле MAC + persist в hpsdr_devices.ini (MAC=..), FindSavedByAddr/SetSavedMac (пишет ini только при изменении), MacIsZero. - RadioController.ResolveDevice: saved-ветка восстанавливает и MAC. - RadioController.EnsureDeviceMac: добор неизвестного MAC — найденное в этой сессии → ini → короткий служебный поиск в сети (700 мс, unicast на DirectIP, OnDeviceFound временно снят, чтобы не трогать список устройств в UI). - ConnectDevice: добор MAC до Connect, после успеха MAC запоминается за сохранённым устройством, так что следующий AutoStart идёт без пробника. Проверено на железе (ANAN 172.16.2.200): в hpsdr_devices.ini появился MAC=04:91:62:FD:7B:86, секция нулевого MAC больше не создаётся, старт по AutoStart и старт после DISCOVER дают один и тот же профиль. Co-Authored-By: Claude Opus 4.8 --- DeviceStore.pas | 85 +++++++++++++++++++++++++++++++++++++++++++++ RadioController.pas | 75 +++++++++++++++++++++++++++++++++++++-- ewsdr.lpi | 6 ++-- 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/DeviceStore.pas b/DeviceStore.pas index e7eab44..8f0dcae 100644 --- a/DeviceStore.pas +++ b/DeviceStore.pas @@ -26,6 +26,10 @@ type Kind: TBackendKind; // bkHPSDR / bkPluto URI: string; // Pluto: 'ip:..' / 'usb:..' Serial: string; // Pluto serial + // MAC радио, узнанный при прошлом подключении. Настройки персистятся по MAC, + // а старт по сохранённому IP (AutoStart/CONNECT) идёт без дискавери — без + // этого поля профиль уезжал бы в «нулевой» MAC. Пусто = ещё не знаем. + MAC: array[0..5] of Byte; end; // Найденное устройство (результат discovery) @@ -78,10 +82,53 @@ type function Discovered(Idx: Integer): TDiscoveredDevice; // IP найденного устройства по IP (для preload-rate lookup и т.п.) function FindDiscoveredByIP(const IP: string): Integer; + + // ---- MAC сохранённого устройства ---- + // Поиск сохранённой записи по IP или URI (Pluto хранит адрес в обоих полях). + function FindSavedByAddr(const Addr: string): Integer; + // Запоминает MAC за адресом (после успешного connect). Пишет ini только при + // изменении, чтобы не дёргать диск на каждом старте. + procedure SetSavedMac(const Addr: string; const MAC: array of Byte); end; +// MAC == 00:00:00:00:00:00 — «неизвестен». +function MacIsZero(const MAC: array of Byte): Boolean; + implementation +function MacIsZero(const MAC: array of Byte): Boolean; +var I: Integer; +begin + Result := True; + for I := 0 to High(MAC) do + if MAC[I] <> 0 then Exit(False); +end; + +function MacToIniStr(const MAC: array of Byte): string; +var I: Integer; +begin + Result := ''; + for I := 0 to 5 do + begin + if I > 0 then Result := Result + ':'; + Result := Result + IntToHex(MAC[I], 2); + end; +end; + +procedure IniStrToMac(const S: string; out MAC: array of Byte); +var Parts: TStringArray; I, V: Integer; +begin + FillChar(MAC[0], 6, 0); + Parts := S.Split([':']); + if Length(Parts) <> 6 then Exit; + for I := 0 to 5 do + begin + V := StrToIntDef('$' + Parts[I], -1); + if (V < 0) or (V > 255) then begin FillChar(MAC[0], 6, 0); Exit; end; + MAC[I] := Byte(V); + end; +end; + { TDeviceStore } constructor TDeviceStore.Create; @@ -116,6 +163,7 @@ begin FSaved[I].Kind := TBackendKind(Ini.ReadInteger(Section, 'Kind', 0)); FSaved[I].URI := Ini.ReadString (Section, 'URI', ''); FSaved[I].Serial := Ini.ReadString (Section, 'Serial', ''); + IniStrToMac(Ini.ReadString(Section, 'MAC', ''), FSaved[I].MAC); Inc(FSavedCount); end; finally @@ -142,6 +190,10 @@ begin Ini.WriteInteger(Section, 'Kind', Ord(FSaved[I].Kind)); Ini.WriteString (Section, 'URI', FSaved[I].URI); Ini.WriteString (Section, 'Serial', FSaved[I].Serial); + if MacIsZero(FSaved[I].MAC) then + Ini.DeleteKey(Section, 'MAC') + else + Ini.WriteString(Section, 'MAC', MacToIniStr(FSaved[I].MAC)); end; finally Ini.Free; @@ -161,6 +213,7 @@ begin begin Result.Name := ''; Result.IPAddress := ''; Result.BoardType := 0; Result.AutoStart := False; + FillChar(Result.MAC, SizeOf(Result.MAC), 0); end; end; @@ -177,6 +230,7 @@ begin FSaved[Result].Kind := bkHPSDR; FSaved[Result].URI := ''; FSaved[Result].Serial := ''; + FillChar(FSaved[Result].MAC, SizeOf(FSaved[Result].MAC), 0); SaveSaved; end; @@ -193,6 +247,7 @@ begin FSaved[Result].Kind := bkPluto; FSaved[Result].URI := Trim(AURI); FSaved[Result].Serial := Trim(ASerial); + FillChar(FSaved[Result].MAC, SizeOf(FSaved[Result].MAC), 0); SaveSaved; end; @@ -330,4 +385,34 @@ begin end; end; +function TDeviceStore.FindSavedByAddr(const Addr: string): Integer; +var + I: Integer; +begin + Result := -1; + if Trim(Addr) = '' then Exit; + for I := 0 to FSavedCount - 1 do + if SameText(FSaved[I].IPAddress, Addr) or SameText(FSaved[I].URI, Addr) then + begin + Result := I; + Exit; + end; +end; + +procedure TDeviceStore.SetSavedMac(const Addr: string; const MAC: array of Byte); +var + Idx, J: Integer; + Cur: array[0..5] of Byte; +begin + if MacIsZero(MAC) then Exit; + Idx := FindSavedByAddr(Addr); + if Idx < 0 then Exit; + FillChar(Cur, SizeOf(Cur), 0); + for J := 0 to High(MAC) do + if J <= 5 then Cur[J] := MAC[J]; + if CompareByte(Cur, FSaved[Idx].MAC, 6) = 0 then Exit; // уже записан + Move(Cur[0], FSaved[Idx].MAC[0], 6); + SaveSaved; +end; + end. diff --git a/RadioController.pas b/RadioController.pas index b91d18e..7f5f6c7 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -821,6 +821,12 @@ type // Kind/URI/Serial/BoardType/MAC. Нужно для Pluto (открывается по URI, выбор // бэкенда — по Kind). Не найдено → простой HPSDR по IP. Общий путь GUI+демон. function ResolveDevice(const IP: string): THPSDRDevice; + // Добирает MAC устройства, если он неизвестен (старт по сохранённому IP / + // AutoStart идёт без дискавери). MAC — ключ профиля настроек, без него + // настройки уезжают в «нулевой» профиль. Источники по порядку: список + // найденного в этой сессии → MAC из hpsdr_devices.ini → короткий поиск в + // сети (ответ discovery несёт MAC). False = добыть не удалось. + function EnsureDeviceMac(var Dev: THPSDRDevice): Boolean; // Сохраняет устройство по введённому адресу. Схема URI ('ip:'/'usb:'/'local:') // → Pluto (AddSavedPluto), иначе HPSDR-IP. Общий путь web-dev_add для GUI и // демона (десктоп-диалог сохраняет Pluto своим путём через AddSavedPluto). @@ -5104,11 +5110,66 @@ begin Result.URI := sd.URI; Result.Serial := sd.Serial; Result.BoardType := sd.BoardType; + Move(sd.MAC[0], Result.MAC[0], 6); // MAC с прошлого connect — ключ профиля настроек Exit; end; end; end; +function TRadioController.EnsureDeviceMac(var Dev: THPSDRDevice): Boolean; +// См. комментарий в объявлении. Поиск в сети делаем с временно снятым +// OnDeviceFound: это служебный пробник, а не пользовательский DISCOVER — +// список устройств в UI он трогать не должен. +const + MAC_PROBE_MS = 700; // хватает на LAN: пакет уходит сразу, дальше только ожидание +var + i: Integer; + sd: TSavedDevice; + SavedCb: TOnDeviceFound; + SavedIP: string; + Found: THPSDRDeviceArray; +begin + Result := not MacIsZero(Dev.MAC); + if Result then Exit; + + i := FDeviceStore.FindDiscoveredByIP(Dev.IPAddress); + if (i >= 0) and (not MacIsZero(FDeviceStore.Discovered(i).MAC)) then + begin + Move(FDeviceStore.Discovered(i).MAC[0], Dev.MAC[0], 6); + Exit(True); + end; + + i := FDeviceStore.FindSavedByAddr(Dev.IPAddress); + if i >= 0 then + begin + sd := FDeviceStore.Saved(i); + if not MacIsZero(sd.MAC) then + begin + Move(sd.MAC[0], Dev.MAC[0], 6); + Exit(True); + end; + end; + + SavedCb := FNetwork.OnDeviceFound; + SavedIP := FNetwork.DirectIP; + FNetwork.OnDeviceFound := nil; + FNetwork.DirectIP := Dev.IPAddress; // unicast — достанет радио и вне broadcast-домена + try + Found := FNetwork.Discover(MAC_PROBE_MS); + finally + FNetwork.OnDeviceFound := SavedCb; + FNetwork.DirectIP := SavedIP; + end; + + for i := 0 to High(Found) do + if SameText(Found[i].IPAddress, Dev.IPAddress) and (not MacIsZero(Found[i].MAC)) then + begin + Move(Found[i].MAC[0], Dev.MAC[0], 6); + if Dev.BoardType = 0 then Dev.BoardType := Found[i].BoardType; + Exit(True); + end; +end; + procedure TRadioController.AddSavedDevice(const AName, AAddr: string); // web-dev_add: Pluto по схеме URI ('ip:'/'usb:'/'local:'), иначе HPSDR-IP. // Serial при ручном добавлении неизвестен (допишется при connect/probe). @@ -5133,11 +5194,21 @@ function TRadioController.ConnectDevice(const Dev: THPSDRDevice): Boolean; // для рендера полей, которых нет в контроллере (FPS/FreqMhzDigits/CAT). var i: Integer; + D: THPSDRDevice; begin + D := Dev; // Подбираем бэкенд под тип устройства (HPSDR/Pluto) до подключения. - EnsureBackend(Dev.Kind); - Result := FNetwork.Connect(Dev); + EnsureBackend(D.Kind); + // MAC — ключ профиля настроек. Старт по сохранённому IP (AutoStart/CONNECT без + // дискавери) приносит нулевой MAC, и весь профиль устройства уехал бы в чужую + // секцию конфига; добираем MAC до Connect. + if D.Kind = bkHPSDR then EnsureDeviceMac(D); + Result := FNetwork.Connect(D); if not Result then Exit; + // Узнанный MAC запоминаем за сохранённым устройством — следующий старт по + // AutoStart возьмёт его без поиска в сети. + if D.IPAddress <> '' then FDeviceStore.SetSavedMac(D.IPAddress, D.MAC) + else FDeviceStore.SetSavedMac(D.URI, D.MAC); // --- Загружаем настройки устройства по MAC --- Move(FNetwork.Device.MAC[0], FDevMAC[0], 6); diff --git a/ewsdr.lpi b/ewsdr.lpi index 2c969dc..421591e 100644 --- a/ewsdr.lpi +++ b/ewsdr.lpi @@ -17,9 +17,9 @@ - + - + @@ -128,6 +128,8 @@ + +