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
This commit is contained in:
Shane Smiskol
2026-02-18 22:42:05 -08:00
committed by GitHub
parent 3c4ddba992
commit 612c518dd6
5 changed files with 165 additions and 92 deletions
@@ -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
@@ -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
+1
View File
@@ -25,6 +25,7 @@ class NMDeviceStateReason(IntEnum):
UNKNOWN = 1
NO_SECRETS = 7
SUPPLICANT_DISCONNECT = 8
CONNECTION_REMOVED = 38
NEW_ACTIVATION = 60
+131 -65
View File
@@ -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()
+2 -2
View File
@@ -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"