mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 07:23:45 +08:00
AAwireless: Account for Failed DHCP
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
import re
|
||||
|
||||
DEFAULTS = {"connect_delay_s": 0.0, "max_window": 2, "phone_services": False, "phone_frames": False,
|
||||
"bootstrap_experiments": False, "stay_while_projecting": False}
|
||||
"bootstrap_experiments": False, "stay_while_projecting": False, "dhcp_patience_s": 0.0}
|
||||
|
||||
FINGERPRINT_KEYS = ("head_unit_make", "head_unit_model", "car_make", "car_model", "car_year")
|
||||
|
||||
RULES: list[dict] = [
|
||||
{"id": "aawireless", "label": "AAWireless", "match": {},
|
||||
"tuning": {"max_window": 30, "phone_services": True, "phone_frames": True, "stay_while_projecting": True}},
|
||||
"tuning": {"max_window": 30, "phone_services": True, "phone_frames": True, "stay_while_projecting": True,
|
||||
"dhcp_patience_s": 6.0}},
|
||||
{"id": "unofficial", "label": "Unofficial dongle", "match": {}, "tuning": {"bootstrap_experiments": True}},
|
||||
]
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@ NM_AP_IFACE = "org.freedesktop.NetworkManager.AccessPoint"
|
||||
DEVICE_TYPE_WIFI = 2
|
||||
DEVICE_STATE_DISCONNECTED = 30
|
||||
DEVICE_STATE_PREPARE = 40
|
||||
DEVICE_STATE_IP_CONFIG = 70
|
||||
DEVICE_STATE_ACTIVATED = 100
|
||||
DHCP_RESTARTS = 3
|
||||
DEVICE_STATES_NOT_MANAGED = (10, 20) # NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_UNAVAILABLE
|
||||
DEVICE_READY_TIMEOUT_S = 5.0
|
||||
# A shared interface can be briefly unmanaged (for example while the Wi-Fi mode is being switched). Bugsink showed 19 joins
|
||||
@@ -152,6 +154,7 @@ class NetworkLease:
|
||||
self.local_ip = ""
|
||||
self.device_ready = False
|
||||
self.autoconnect_paused = False
|
||||
self.dhcp_patience = 0.0
|
||||
self.marker = AUTOCONNECT_MARKER
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@@ -206,13 +209,15 @@ class NetworkLease:
|
||||
with self._lock:
|
||||
self._prepare_device(cancelled)
|
||||
|
||||
def acquire(self, credentials: WifiCredentials, timeout: float = JOIN_TIMEOUT_S, cancelled: Callable[[], bool] = lambda: False) -> str:
|
||||
def acquire(self, credentials: WifiCredentials, timeout: float = JOIN_TIMEOUT_S, cancelled: Callable[[], bool] = lambda: False,
|
||||
dhcp_patience: float = 0.0) -> str:
|
||||
"""Join the projection network and return the comma's IPv4 address on it.
|
||||
|
||||
The BSSID the car reports is tried first (fast, unambiguous); if that
|
||||
attempt fails, one retry matches the SSID alone in case the reported BSSID
|
||||
belongs to a different radio of the head unit.
|
||||
"""
|
||||
self.dhcp_patience = dhcp_patience
|
||||
attempts = [credentials]
|
||||
if credentials.bssid:
|
||||
attempts.append(replace(credentials, bssid=""))
|
||||
@@ -246,6 +251,22 @@ class NetworkLease:
|
||||
self.active_path = self.connection_path = ""
|
||||
|
||||
def _acquire_once(self, credentials: WifiCredentials, timeout: float, cancelled: Callable[[], bool]) -> str:
|
||||
deadline = time.monotonic() + timeout
|
||||
restarts = 0
|
||||
while True:
|
||||
self._activate(credentials, cancelled)
|
||||
if self._wait_activated(deadline, timeout, cancelled, restarts < DHCP_RESTARTS):
|
||||
break
|
||||
restarts += 1
|
||||
self.log("wifi_dhcp_restart", interface=self.interface, patience_s=self.dhcp_patience, restarts=restarts)
|
||||
self._drop_attempt()
|
||||
self.local_ip = self._ipv4()
|
||||
if self.dedicated:
|
||||
self._check_overlap()
|
||||
self.log("wifi_joined", interface=self.interface, local_ip=self.local_ip, **self._radio())
|
||||
return self.local_ip
|
||||
|
||||
def _activate(self, credentials: WifiCredentials, cancelled: Callable[[], bool]) -> None:
|
||||
with self._lock:
|
||||
if not self._get(NM_PATH, NM_IFACE, "WirelessEnabled"):
|
||||
raise NetworkError("Wi-Fi is turned off on the comma")
|
||||
@@ -271,7 +292,9 @@ class NetworkLease:
|
||||
{"persist": ("s", "volatile")}), timeout=15.0)
|
||||
self.connection_path, self.active_path = body[0], body[1]
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
def _wait_activated(self, deadline: float, timeout: float, cancelled: Callable[[], bool], may_restart: bool) -> bool:
|
||||
"""True once active; False when DHCP sat unanswered past dhcp_patience and the join should start over."""
|
||||
ip_config_since = 0.0
|
||||
while True:
|
||||
if cancelled():
|
||||
raise NetworkError("cancelled")
|
||||
@@ -280,17 +303,20 @@ class NetworkLease:
|
||||
except NetworkError as error:
|
||||
raise NetworkError("The projection network connection was removed (wrong key or out of range?)") from error
|
||||
if state == ACTIVE_STATE_ACTIVATED:
|
||||
break
|
||||
return True
|
||||
if state == ACTIVE_STATE_DEACTIVATED:
|
||||
raise NetworkError("NetworkManager could not join the projection network")
|
||||
if time.monotonic() >= deadline:
|
||||
now = time.monotonic()
|
||||
if now >= deadline:
|
||||
raise JoinTimeout(f"Timed out joining the projection network after {timeout:.0f} s")
|
||||
if may_restart and self.dhcp_patience > 0:
|
||||
if self._get(self.device_path, NM_DEVICE_IFACE, "State") == DEVICE_STATE_IP_CONFIG:
|
||||
ip_config_since = ip_config_since or now
|
||||
if now - ip_config_since >= self.dhcp_patience:
|
||||
return False
|
||||
else:
|
||||
ip_config_since = 0.0
|
||||
time.sleep(0.25)
|
||||
self.local_ip = self._ipv4()
|
||||
if self.dedicated:
|
||||
self._check_overlap()
|
||||
self.log("wifi_joined", interface=self.interface, local_ip=self.local_ip, **self._radio())
|
||||
return self.local_ip
|
||||
|
||||
def _frequency(self, path: str) -> int:
|
||||
access_point = self._get(path, NM_WIRELESS_IFACE, "ActiveAccessPoint")
|
||||
|
||||
@@ -1487,7 +1487,9 @@ class Supervisor:
|
||||
# a car that once took 70 s to be ready gets more than the standard wait for the Wi-Fi handshake
|
||||
patience=max(strategy.patience, self._ladder.ready_patience(address, 20.0)))
|
||||
self._set(state="wifi_start")
|
||||
result = boot.run(lambda credentials: lease.acquire(credentials, cancelled=boot.join_is_cancelled), cancelled=self._stop.is_set)
|
||||
patience = car_profiles.tuning(config)["dhcp_patience_s"]
|
||||
join_options = {"dhcp_patience": patience} if patience else {}
|
||||
result = boot.run(lambda credentials: lease.acquire(credentials, cancelled=boot.join_is_cancelled, **join_options), cancelled=self._stop.is_set)
|
||||
except Exception as error:
|
||||
if _stray_link(error):
|
||||
raise StrayLinkError(f"Bluetooth link to the car is stuck: {error}") from error # says nothing about the cached channel
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_aawireless_tuning_applies_only_when_chosen():
|
||||
chosen = config(car_profile_override="aawireless", receiver_name="AAWireless-4F2A")
|
||||
assert car_profiles.resolve(chosen) == ("aawireless", "override")
|
||||
assert car_profiles.tuning(chosen) == {"connect_delay_s": 0.0, "max_window": 30, "phone_services": True, "phone_frames": True,
|
||||
"bootstrap_experiments": False, "stay_while_projecting": True}
|
||||
"bootstrap_experiments": False, "stay_while_projecting": True, "dhcp_patience_s": 6.0}
|
||||
assert car_profiles.summary(chosen)["car_profile_label"] == "AAWireless"
|
||||
for other in (config(receiver_name="AAWireless-4F2A"), config(car_profile_override="default")):
|
||||
assert car_profiles.tuning(other) == car_profiles.DEFAULTS
|
||||
|
||||
@@ -1012,8 +1012,9 @@ def test_supervisor_stop_while_waiting_for_car(identity, tmp_path, monkeypatch):
|
||||
# ------------------------------------------------------------------- network
|
||||
|
||||
class FakeNetworkManager:
|
||||
def __init__(self, fail_with_bssid=False):
|
||||
def __init__(self, fail_with_bssid=False, stalled_dhcp=0):
|
||||
self.fail_with_bssid = fail_with_bssid
|
||||
self.stalled_dhcp = stalled_dhcp
|
||||
self.active_device_connection = "/active/home"
|
||||
self.states = {}
|
||||
self.added = []
|
||||
@@ -1031,6 +1032,8 @@ class FakeNetworkManager:
|
||||
active = f"/active/aa{len(self.added)}"
|
||||
locked = "bssid" in settings["802-11-wireless"]
|
||||
self.states[active] = network.ACTIVE_STATE_DEACTIVATED if (locked and self.fail_with_bssid) else network.ACTIVE_STATE_ACTIVATED
|
||||
if len(self.added) <= self.stalled_dhcp:
|
||||
self.states[active] = 1 # activating: the access point never answers DHCP
|
||||
self.active_device_connection = active
|
||||
return [f"/settings/aa{len(self.added)}", active]
|
||||
if member == "GetSettings":
|
||||
@@ -1043,6 +1046,8 @@ class FakeNetworkManager:
|
||||
values = {"WirelessEnabled": True, "DeviceType": 2, "Interface": "wlan0", "ActiveConnection": self.active_device_connection,
|
||||
"Connection": "/settings/home", "Ip4Config": "/ip4/1", "Autoconnect": self.autoconnect,
|
||||
"ActiveAccessPoint": "/ap/1", "Frequency": 5745}
|
||||
if name == "State" and interface.endswith("Device"):
|
||||
return 70
|
||||
if name == "State":
|
||||
return self.states.get(path, 4)
|
||||
if name == "AddressData":
|
||||
@@ -1113,6 +1118,22 @@ def test_network_lease_waits_for_a_slow_honda_access_point(monkeypatch):
|
||||
assert budgets == [("AA:BB:CC:DD:EE:FF", 90.0), ("", 30.0)]
|
||||
|
||||
|
||||
def test_network_lease_restarts_a_join_whose_dhcp_goes_unanswered():
|
||||
nm = FakeNetworkManager(stalled_dhcp=2)
|
||||
lease = make_lease(nm)
|
||||
assert lease.acquire(credentials(), timeout=10.0, dhcp_patience=0.3) == "192.168.50.23"
|
||||
assert len(nm.added) == 3
|
||||
|
||||
|
||||
def test_network_lease_waits_for_dhcp_without_patience():
|
||||
from openpilot.starpilot.system.starport import network
|
||||
nm = FakeNetworkManager(stalled_dhcp=2)
|
||||
lease = make_lease(nm)
|
||||
with pytest.raises(network.JoinTimeout):
|
||||
lease.acquire(credentials(), timeout=1.0)
|
||||
assert len(nm.added) == 2 # only the SSID-only retry, never a DHCP restart
|
||||
|
||||
|
||||
def test_network_lease_does_not_undo_user_network_change():
|
||||
nm = FakeNetworkManager()
|
||||
lease = make_lease(nm)
|
||||
|
||||
Reference in New Issue
Block a user