mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-21 00:03:45 +08:00
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:
@@ -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
|
||||
|
||||
@@ -25,6 +25,7 @@ class NMDeviceStateReason(IntEnum):
|
||||
UNKNOWN = 1
|
||||
NO_SECRETS = 7
|
||||
SUPPLICANT_DISCONNECT = 8
|
||||
CONNECTION_REMOVED = 38
|
||||
NEW_ACTIVATION = 60
|
||||
|
||||
|
||||
|
||||
+131
-65
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user