From 612c518dd68b994186bf3d29970c44e673523afb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 22:42:05 -0800 Subject: [PATCH] WifiManager: signal-driven connection status (#37258) * signal driven wifi state * copy exactly * copy signal handler * remove is_connected * Revert "remove is_connected" This reverts commit f2246a70f4a29e9f3405947ca43d9404578c9d2d. * do 3 network * missing reason * do wifiui * clean up mici updater * rest * or not connecting * clean up is_connected * clean up wifiui * match wifiui state more exactly in network panel for wifi button * update active connection info after activation (used to do in _update_networks) * clean up prints * more * rm * not needed * clean up state machine a bit * more * more * indent * final clean up * debug * debug * wait for ip? * more * revert * just to see * ensure we emit activated even if we fail to get conn path from dbus * hmm * fine * back * back * Revert "back" This reverts commit 6464abe243c2a3bbf62b8f9a109b72ec3ddb3817. * debug flickering on forget then connect to another. commit before this is good * fix rare flicker when forgetting network and immediately connecting to another * clean up * clean up router stuff now * ugh wtf * stash -- wtf * Revert "stash -- wtf" This reverts commit 756a92a9c0530a16917303424e26447f258f17e4. * Revert "fix rare flicker when forgetting network and immediately connecting to" This reverts commit 90c5fc14551726765ab2524e7866ee8b3c5dee7c. * remove debug * fix * add issues * add flow * match previous behavior * it doesn't fix the flikcer * more atomic * Revert "more atomic" This reverts commit ead87c5a7a4030719b64138c12b9154ec82e73d9. * last test! last test! * really the race is here? * atomic wifi_state replace * not slow * clean up --- .../mici/layouts/settings/network/__init__.py | 16 +- .../mici/layouts/settings/network/wifi_ui.py | 40 ++-- system/ui/lib/networkmanager.py | 1 + system/ui/lib/wifi_manager.py | 196 ++++++++++++------ system/ui/widgets/network.py | 4 +- 5 files changed, 165 insertions(+), 92 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 659cd5ff3..1940b680e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus class NetworkPanelType(IntEnum): @@ -125,15 +125,13 @@ class NetworkLayoutMici(NavWidget): # Update wi-fi button with ssid and ip address # TODO: make sure we handle hidden ssids - connecting_ssid = self._wifi_manager.connecting_to_ssid - connected_network = next((network for network in self._wifi_manager.networks if network.is_connected), None) - if connecting_ssid: - display_network = next((n for n in self._wifi_manager.networks if n.ssid == connecting_ssid), None) - self._wifi_button.set_text(normalize_ssid(connecting_ssid)) + wifi_state = self._wifi_manager.wifi_state + display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) + if wifi_state.status == ConnectStatus.CONNECTING: + self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) self._wifi_button.set_value("connecting...") - elif connected_network is not None: - display_network = connected_network - self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) + elif wifi_state.status == ConnectStatus.CONNECTED: + self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) self._wifi_button.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") else: display_network = None diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index dd8b26e54..66b8e352f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -8,7 +8,7 @@ from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState def normalize_ssid(ssid: str) -> str: @@ -94,7 +94,7 @@ class WifiIcon(Widget): class WifiItem(BigDialogOptionButton): LEFT_MARGIN = 20 - def __init__(self, network: Network): + def __init__(self, network: Network, wifi_state_callback: Callable[[], WifiState]): super().__init__(network.ssid) self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT)) @@ -102,6 +102,7 @@ class WifiItem(BigDialogOptionButton): self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) self._network = network + self._wifi_state_callback = wifi_state_callback self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) @@ -119,7 +120,8 @@ class WifiItem(BigDialogOptionButton): def _render(self, _): disabled_alpha = 0.35 if not self.enabled else 1.0 - if self._network.is_connected: + # connecting or connected + if self._wifi_state_callback().ssid == self._network.ssid: selected_x = int(self._rect.x - self._selected_txt.width / 2) selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) @@ -214,7 +216,8 @@ class ForgetButton(Widget): class NetworkInfoPage(NavWidget): - def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable): + def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable, + connecting_callback: Callable[[], str | None], connected_callback: Callable[[], str | None]): super().__init__() self._wifi_manager = wifi_manager @@ -235,7 +238,8 @@ class NetworkInfoPage(NavWidget): # State self._network: Network | None = None - self._connecting: Callable[[], str | None] | None = None + self._connecting_callback = connecting_callback + self._connected_callback = connected_callback def show_event(self): super().show_event() @@ -263,7 +267,7 @@ class NetworkInfoPage(NavWidget): if self._is_connecting: self._connect_btn.set_label("connecting...") self._connect_btn.set_enabled(False) - elif self._network.is_connected: + elif self._is_connected: self._connect_btn.set_label("connected") self._connect_btn.set_enabled(False) elif self._network.security_type == SecurityType.UNSUPPORTED: @@ -285,16 +289,20 @@ class NetworkInfoPage(NavWidget): self._network = network self._wifi_icon.set_current_network(network) - def set_connecting(self, is_connecting: Callable[[], str | None]): - self._connecting = is_connecting - @property def _is_connecting(self): - if self._connecting is None or self._network is None: + if self._network is None: return False - is_connecting = self._connecting() == self._network.ssid + is_connecting = self._connecting_callback() == self._network.ssid return is_connecting + @property + def _is_connected(self): + if self._network is None: + return False + is_connected = self._connected_callback() == self._network.ssid + return is_connected + def _render(self, _): self._wifi_icon.render(rl.Rectangle( self._rect.x + 32, @@ -342,8 +350,8 @@ class WifiUIMici(BigMultiOptionDialog): # Set up back navigation self.set_back_callback(back_callback) - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page) - self._network_info_page.set_connecting(lambda: wifi_manager.connecting_to_ssid) + self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page, + lambda: wifi_manager.connecting_to_ssid, lambda: wifi_manager.connected_ssid) self._loading_animation = LoadingAnimation() @@ -385,11 +393,11 @@ class WifiUIMici(BigMultiOptionDialog): # Update network on existing button self._scroller._items[network_button_idx].set_current_network(network) else: - network_button = WifiItem(network) + network_button = WifiItem(network, lambda: self._wifi_manager.wifi_state) self._scroller.add_widget(network_button) - # Move connected network to the start - connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if btn._network.is_connected), None) + # Move connecting/connected network to the start + connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if self._wifi_manager.wifi_state.ssid == btn._network.ssid), None) if connected_btn_idx is not None and connected_btn_idx > 0: self._scroller._items.insert(0, self._scroller._items.pop(connected_btn_idx)) self._scroller._layout() # fixes selected style single frame stutter diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index f16271a50..e04e3eead 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -25,6 +25,7 @@ class NMDeviceStateReason(IntEnum): UNKNOWN = 1 NO_SECRETS = 7 SUPPLICANT_DISCONNECT = 8 + CONNECTION_REMOVED = 38 NEW_ACTIVATION = 60 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 52246beb7..479bad7ec 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -4,7 +4,7 @@ import time import uuid import subprocess from collections.abc import Callable -from dataclasses import dataclass +from dataclasses import dataclass, replace from enum import IntEnum from typing import Any @@ -88,24 +88,19 @@ def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityTyp class Network: ssid: str strength: int - is_connected: bool security_type: SecurityType is_saved: bool ip_address: str = "" # TODO: implement @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool, active_connection: bool) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) - # fall back to ActiveConnection during momentary AP roaming or low strength networks. matches GNOME shell behavior - # https://github.com/GNOME/gnome-shell/blob/3f8b174274fac7d69477523d4873ef8253e1ed49/js/ui/status/network.js#L810-L819 - is_connected = any(ap.is_connected for ap in aps) or active_connection security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) return cls( ssid=ssid, strength=strongest_ap.strength, - is_connected=is_connected and is_saved, security_type=security_type, is_saved=is_saved, ) @@ -116,14 +111,13 @@ class AccessPoint: ssid: str bssid: str strength: int - is_connected: bool flags: int wpa_flags: int rsn_flags: int ap_path: str @classmethod - def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str, active_ap_path: str) -> "AccessPoint": + def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str) -> "AccessPoint": ssid = bytes(ap_props['Ssid'][1]).decode("utf-8", "replace") bssid = str(ap_props['HwAddress'][1]) strength = int(ap_props['Strength'][1]) @@ -135,7 +129,6 @@ class AccessPoint: ssid=ssid, bssid=bssid, strength=strength, - is_connected=ap_path == active_ap_path, flags=flags, wpa_flags=wpa_flags, rsn_flags=rsn_flags, @@ -143,6 +136,18 @@ class AccessPoint: ) +class ConnectStatus(IntEnum): + DISCONNECTED = 0 + CONNECTING = 1 + CONNECTED = 2 + + +@dataclass +class WifiState: + ssid: str | None = None + status: ConnectStatus = ConnectStatus.DISCONNECTED + + class WifiManager: def __init__(self): self._networks: list[Network] = [] # a network can be comprised of multiple APs @@ -166,8 +171,7 @@ class WifiManager: # State self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals - self._connecting_to_ssid: str | None = None - self._prev_connecting_to_ssid: str | None = None + self._wifi_state: WifiState = WifiState() self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" @@ -199,18 +203,41 @@ class WifiManager: def worker(): self._wait_for_wifi_device() - self._scan_thread.start() - self._state_thread.start() - self._init_connections() if Params is not None and self._tethering_ssid not in self._connections: self._add_tethering_connection() + self._init_wifi_state() + + self._scan_thread.start() + self._state_thread.start() + self._tethering_password = self._get_tethering_password() cloudlog.debug("WifiManager initialized") threading.Thread(target=worker, daemon=True).start() + def _init_wifi_state(self, block: bool = True): + def worker(): + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) + dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] + + wifi_state = WifiState() + if NMDeviceState.PREPARE <= dev_state <= NMDeviceState.SECONDARIES and dev_state != NMDeviceState.NEED_AUTH: + wifi_state.status = ConnectStatus.CONNECTING + elif dev_state == NMDeviceState.ACTIVATED: + wifi_state.status = ConnectStatus.CONNECTED + + conn_path, _ = self._get_active_wifi_connection() + if conn_path: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + self._wifi_state = wifi_state + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + def add_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, forgotten: Callable[[str], None] | None = None, @@ -231,6 +258,10 @@ class WifiManager: def networks(self) -> list[Network]: return self._networks + @property + def wifi_state(self) -> WifiState: + return self._wifi_state + @property def ipv4_address(self) -> str: return self._ipv4_address @@ -241,15 +272,18 @@ class WifiManager: @property def connecting_to_ssid(self) -> str | None: - return self._connecting_to_ssid + return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTING else None + + @property + def connected_ssid(self) -> str | None: + return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTED else None @property def tethering_password(self) -> str: return self._tethering_password - def _set_connecting(self, ssid: str): - self._prev_connecting_to_ssid = self._connecting_to_ssid - self._connecting_to_ssid = ssid + def _set_connecting(self, ssid: str | None): + self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: @@ -264,8 +298,9 @@ class WifiManager: def set_active(self, active: bool): self._active = active - # Update networks immediately when activating for UI + # Update networks and WiFi state (to self-heal) immediately when activating for UI if active: + self._init_wifi_state(block=False) self._update_networks(block=False) def _monitor_state(self): @@ -325,43 +360,77 @@ class WifiManager: self._update_networks() # Device state changes + # TODO: known race conditions when switching networks (e.g. forget A, connect to B): + # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B + # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. + # DEACTIVATING(CONNECTION_REMOVED): wifi_state (B, CONNECTING) -> (None, DISCONNECTED) + # Fix: make DEACTIVATING a no-op, and guard DISCONNECTED with + # `if wifi_state.ssid not in _connections` (NewConnection arrives between the two). + # 2. PREPARE/CONFIG ssid lookup: DBus may return stale A's conn_path, overwriting B. + # PREPARE(0): wifi_state (B, CONNECTING) -> (A, CONNECTING) + # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); + # user-initiated connections already have ssid set via _set_connecting. while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body - # BAD PASSWORD - use prev if current has already moved on to a new connection - # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT - # - weak/gone network fails with FAILED+NO_SECRETS - if ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or - (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): - failed_ssid = self._prev_connecting_to_ssid or self._connecting_to_ssid - if failed_ssid: - self._enqueue_callbacks(self._need_auth, failed_ssid) - self.forget_connection(failed_ssid, block=True) - self._prev_connecting_to_ssid = None - if self._connecting_to_ssid == failed_ssid: - self._connecting_to_ssid = None + if new_state == NMDeviceState.DISCONNECTED: + if change_reason != NMDeviceStateReason.NEW_ACTIVATION: + # catches CONNECTION_REMOVED reason when connection is forgotten + self._set_connecting(None) - elif new_state == NMDeviceState.PREPARE and self._connecting_to_ssid is None: + elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): # Set connecting status when NetworkManager connects to known networks on its own + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) if conn_path is None: - cloudlog.warning("Failed to get active wifi connection during PREPARE state") - continue + cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - if ssid: - self._set_connecting(ssid) + self._wifi_state = wifi_state + + # BAD PASSWORD + # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT + # - weak/gone network fails with FAILED+NO_SECRETS + elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): + + if self._wifi_state.ssid: + self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) + + self._set_connecting(None) + + elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, NMDeviceState.FAILED): + pass elif new_state == NMDeviceState.ACTIVATED: + # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results self._update_networks() - self._enqueue_callbacks(self._activated) - self._prev_connecting_to_ssid = None - self._connecting_to_ssid = None - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NMDeviceStateReason.NEW_ACTIVATION: - self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) - self._prev_connecting_to_ssid = None - self._connecting_to_ssid = None + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + + # Persist volatile connections (created by AddAndActivateConnection2) to disk + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) + if save_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") + + elif new_state == NMDeviceState.DEACTIVATING: + if change_reason == NMDeviceStateReason.CONNECTION_REMOVED: + # When connection is forgotten + self._set_connecting(None) def _network_scanner(self): while not self._exit: @@ -417,8 +486,6 @@ class WifiManager: ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": self._connections[ssid] = conn_path - if ssid != self._tethering_ssid: - self.activate_connection(ssid, block=True) def _connection_removed(self, conn_path: str): self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} @@ -529,13 +596,19 @@ class WifiManager: 'psk': ('s', password), } - settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) - reply = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) + # Volatile connection auto-deletes on disconnect (wrong password, user switches networks) + # Persisted to disk on ACTIVATED via Save() + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + self._set_connecting(None) + return + + reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'AddAndActivateConnection2', 'a{sa{sv}}ooa{sv}', + (connection, self._wifi_device, "/", {'persist': ('s', 'volatile')}))) if reply.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to add connection for {ssid}: {reply}") - self._connecting_to_ssid = None - self._prev_connecting_to_ssid = None + cloudlog.warning(f"Failed to add and activate connection for {ssid}: {reply}") + self._set_connecting(None) threading.Thread(target=worker, daemon=True).start() @@ -549,8 +622,7 @@ class WifiManager: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - if len(self._forgotten): - self._update_networks() + self._update_networks() self._enqueue_callbacks(self._forgotten, ssid) if block: @@ -590,10 +662,8 @@ class WifiManager: return def is_tethering_active(self) -> bool: - for network in self._networks: - if network.is_connected: - return bool(network.ssid == self._tethering_ssid) - return False + # Check ssid, not connected_ssid, to also catch connecting state + return self._wifi_state.ssid == self._tethering_ssid def set_tethering_password(self, password: str): def worker(): @@ -708,7 +778,6 @@ class WifiManager: # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - active_ap_path = wifi_props.get('ActiveAccessPoint', ('o', '/'))[1] ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} @@ -723,7 +792,7 @@ class WifiManager: continue try: - ap = AccessPoint.from_dbus(ap_props.body[0], ap_path, active_ap_path) + ap = AccessPoint.from_dbus(ap_props.body[0], ap_path) if ap.ssid == "": continue @@ -735,11 +804,8 @@ class WifiManager: # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - active_wifi_connection, _ = self._get_active_wifi_connection() - networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections, - active_wifi_connection is not None and - self._connections.get(ssid) == active_wifi_connection) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -n.strength, n.ssid.lower())) + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] + networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, -n.is_saved, -n.strength, n.ssid.lower())) self._networks = networks self._update_active_connection_info() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 9de44c585..186164916 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -400,7 +400,7 @@ class WifiManagerUI(Widget): self.state = UIState.NEEDS_AUTH self._state_network = network self._password_retry = False - elif not network.is_connected: + elif self._wifi_manager.wifi_state.ssid != network.ssid: self.connect_to_network(network) def _forget_networks_buttons_callback(self, network): @@ -410,7 +410,7 @@ class WifiManagerUI(Widget): def _draw_status_icon(self, rect, network: Network): """Draw the status icon based on network's connection state""" icon_file = None - if network.is_connected and self.state != UIState.CONNECTING: + if self._wifi_manager.connected_ssid == network.ssid and self.state != UIState.CONNECTING: icon_file = "icons/checkmark.png" elif network.security_type == SecurityType.UNSUPPORTED: icon_file = "icons/circled_slash.png"