openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

257 lines
9.1 KiB

import asyncio
import concurrent.futures
import copy
import threading
import time
import uuid
from collections.abc import Callable
from dataclasses import dataclass
from enum import IntEnum
from typing import TypeVar
from dbus_next.aio import MessageBus
from dbus_next import BusType, Variant, Message
from dbus_next.errors import DBusError
from dbus_next.constants import MessageType
from openpilot.system.ui.lib.networkmanager import (NM, NM_PATH, NM_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE,
NM_CONNECTION_IFACE, NM_WIRELESS_IFACE, NM_PROPERTIES_IFACE,
NM_DEVICE_IFACE, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT,
NMDeviceState)
try:
from openpilot.common.params import Params
except ImportError:
# Params/Cythonized modules are not available in zipapp
Params = None
from openpilot.common.swaglog import cloudlog
T = TypeVar("T")
TETHERING_IP_ADDRESS = "192.168.43.1"
DEFAULT_TETHERING_PASSWORD = "swagswagcomma"
class SecurityType(IntEnum):
OPEN = 0
WPA = 1
WPA2 = 2
WPA3 = 3
UNSUPPORTED = 4
@dataclass
class NetworkInfo:
ssid: str
strength: int
is_connected: bool
security_type: SecurityType
path: str
bssid: str
is_saved: bool = False
# saved_path: str
@dataclass
class WifiManagerCallbacks:
need_auth: Callable[[str], None] | None = None
activated: Callable[[], None] | None = None
forgotten: Callable[[str], None] | None = None
networks_updated: Callable[[list[NetworkInfo]], None] | None = None
connection_failed: Callable[[str, str], None] | None = None # Added for error feedback
class WifiManager:
async def add_tethering_connection(self, ssid: str, password: str = "12345678") -> bool:
"""Create a WiFi tethering connection."""
if len(password) < 8:
print("Tethering password must be at least 8 characters")
return False
try:
# First, check if a hotspot connection already exists
settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
connection_paths = await settings_iface.call_list_connections()
# Look for an existing hotspot connection
for path in connection_paths:
try:
settings = await self._get_connection_settings(path)
conn_type = settings.get('connection', {}).get('type', Variant('s', '')).value
wifi_mode = settings.get('802-11-wireless', {}).get('mode', Variant('s', '')).value
if conn_type == '802-11-wireless' and wifi_mode == 'ap':
# Extract the SSID to check
connection_ssid = self._extract_ssid(settings)
if connection_ssid == ssid:
return True
except DBusError:
continue
connection = {
'connection': {
'id': Variant('s', 'Hotspot'),
'uuid': Variant('s', str(uuid.uuid4())),
'type': Variant('s', '802-11-wireless'),
'interface-name': Variant('s', 'wlan0'),
'autoconnect': Variant('b', False),
},
'802-11-wireless': {
'band': Variant('s', 'bg'),
'mode': Variant('s', 'ap'),
'ssid': Variant('ay', ssid.encode('utf-8')),
},
'802-11-wireless-security': {
'group': Variant('as', ['ccmp']),
'key-mgmt': Variant('s', 'wpa-psk'),
'pairwise': Variant('as', ['ccmp']),
'proto': Variant('as', ['rsn']),
'psk': Variant('s', password),
},
'ipv4': {
'method': Variant('s', 'shared'),
'address-data': Variant('aa{sv}', [{'address': Variant('s', TETHERING_IP_ADDRESS), 'prefix': Variant('u', 24)}]),
'gateway': Variant('s', TETHERING_IP_ADDRESS),
'never-default': Variant('b', True),
},
'ipv6': {
'method': Variant('s', 'ignore'),
},
}
settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE)
new_connection = await settings_iface.call_add_connection(connection)
print(f"Added tethering connection with path: {new_connection}")
return True
except DBusError as e:
print(f"Failed to add tethering connection: {e}")
return False
except Exception as e:
print(f"Unexpected error adding tethering connection: {e}")
return False
async def get_tethering_password(self) -> str:
"""Get the current tethering password."""
try:
hotspot_path = self.saved_connections.get(self._tethering_ssid)
if hotspot_path:
conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE)
secrets = await conn_iface.call_get_secrets('802-11-wireless-security')
if secrets and '802-11-wireless-security' in secrets:
psk = secrets.get('802-11-wireless-security', {}).get('psk', Variant('s', '')).value
return str(psk) if psk is not None else ""
return ""
except DBusError as e:
print(f"Failed to get tethering password: {e}")
return ""
except Exception as e:
print(f"Unexpected error getting tethering password: {e}")
return ""
async def set_tethering_password(self, password: str) -> bool:
"""Set the tethering password."""
if len(password) < 8:
cloudlog.error("Tethering password must be at least 8 characters")
return False
try:
hotspot_path = self.saved_connections.get(self._tethering_ssid)
if not hotspot_path:
print("No hotspot connection found")
return False
# Update the connection settings with new password
settings = await self._get_connection_settings(hotspot_path)
if '802-11-wireless-security' not in settings:
settings['802-11-wireless-security'] = {}
settings['802-11-wireless-security']['psk'] = Variant('s', password)
# Apply changes
conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE)
await conn_iface.call_update(settings)
# Check if connection is active and restart if needed
is_active = False
nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
active_connections = await nm_iface.get_active_connections()
for conn_path in active_connections:
props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE)
conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection')
if conn_id_path.value == hotspot_path:
is_active = True
await nm_iface.call_deactivate_connection(conn_path)
break
if is_active:
await nm_iface.call_activate_connection(hotspot_path, self.device_path, "/")
print("Tethering password updated successfully")
return True
except DBusError as e:
print(f"Failed to set tethering password: {e}")
return False
except Exception as e:
print(f"Unexpected error setting tethering password: {e}")
return False
async def is_tethering_active(self) -> bool:
"""Check if tethering is active for the specified SSID."""
try:
hotspot_path = self.saved_connections.get(self._tethering_ssid)
if not hotspot_path:
return False
nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE)
active_connections = await nm_iface.get_active_connections()
for conn_path in active_connections:
props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE)
conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection')
if conn_id_path.value == hotspot_path:
return True
return False
except Exception:
return False
def _extract_ssid(self, settings: dict) -> str | None:
"""Extract SSID from connection settings."""
ssid_variant = settings.get('802-11-wireless', {}).get('ssid', Variant('ay', b'')).value
return bytes(ssid_variant).decode('utf-8') if ssid_variant else None
async def _add_match_rule(self, rule):
"""Add a match rule on the bus."""
reply = await self.bus.call(
Message(
message_type=MessageType.METHOD_CALL,
destination='org.freedesktop.DBus',
interface="org.freedesktop.DBus",
path='/org/freedesktop/DBus',
member='AddMatch',
signature='s',
body=[rule],
)
)
assert reply.message_type == MessageType.METHOD_RETURN
return reply
async def _get_interface(self, bus_name: str, path: str, name: str):
introspection = await self.bus.introspect(bus_name, path)
proxy = self.bus.get_proxy_object(bus_name, path, introspection)
return proxy.get_interface(name)
def _get_security_type(self, flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType:
"""Determine the security type based on flags."""
if flags == 0 and not (wpa_flags or rsn_flags):
return SecurityType.OPEN
if rsn_flags & 0x200: # SAE (WPA3 Personal)
# TODO: support WPA3
return SecurityType.UNSUPPORTED
if rsn_flags: # RSN indicates WPA2 or higher
return SecurityType.WPA2
if wpa_flags: # WPA flags indicate WPA
return SecurityType.WPA
return SecurityType.UNSUPPORTED