diff --git a/deploy/yaris_uplink.py b/deploy/yaris_uplink.py index 95fe449..bfe8e25 100644 --- a/deploy/yaris_uplink.py +++ b/deploy/yaris_uplink.py @@ -199,32 +199,19 @@ def write_status(action, error=None): def restore_fallback(): + """Try home-wifi on wlan1. wlan0 AP is permanent and never touched.""" if activate(HOME_CONNECTION): write_status("connected_home") return True - # If the primary interface (wlan1) failed, try the wlan0 fallback profiles - if INTERFACE == PRIMARY_INTERFACE and _interface_exists(FALLBACK_INTERFACE): - # Temporarily try connections bound to wlan0 - for conn in (f"{HOME_CONNECTION}-fb", AP_CONNECTION): - proc = subprocess.run( - ["nmcli", "--wait", "30", "connection", "up", conn], - capture_output=True, text=True, timeout=35, - ) - if proc.returncode == 0: - write_status(f"fallback_wlan0_{conn}") - return True - if activate(AP_CONNECTION): - write_status("restored_local_hotspot") - return True - write_status("recovery_failed", "Could not activate home WiFi or local hotspot.") - return False + write_status("no_connection", "No home WiFi or phone hotspot available.") + return True def _ap_client_count(): - """Return number of stations connected to the AP on INTERFACE.""" + """Return number of stations connected to the AP on wlan0.""" try: proc = subprocess.run( - ["iw", "dev", INTERFACE, "station", "dump"], + ["iw", "dev", "wlan0", "station", "dump"], capture_output=True, text=True, timeout=5, ) return proc.stdout.count("Station ") @@ -233,15 +220,10 @@ def _ap_client_count(): def check_uplink(): + """Manage internet uplink on wlan1 only. wlan0 is a permanent AP, never touched.""" current = active_connection() phone_configured = connection_exists(PHONE_CONNECTION) - # Never tear down the AP if a client is connected — the user is actively - # using the hotspot (e.g. phone browsing the web UI while driving). - # Skip this netwatch cycle and let the next one try again. - if current == AP_CONNECTION and _ap_client_count() > 0: - write_status("ap_has_clients_skipped_check") - return True if current == PHONE_CONNECTION: if internet_reachable(): write_status("phone_connected") @@ -255,18 +237,20 @@ def check_uplink(): "Phone hotspot is connected but internet is not reachable.", ) return True - return restore_fallback() + return _connect_home_or_idle() if phone_configured: - if current == AP_CONNECTION: - run_nmcli("connection", "down", AP_CONNECTION, check=False) + # Only take down the phone hotspot if it's up — never touch the AP on wlan0 + was_phone = current == PHONE_CONNECTION + if was_phone: + run_nmcli("connection", "down", PHONE_CONNECTION, check=False) run_nmcli("device", "wifi", "rescan", "ifname", INTERFACE, check=False) time.sleep(5) if not phone_is_visible(): - if current != AP_CONNECTION and active_connection(): + if not was_phone and active_connection(): write_status("phone_not_in_range_current_connection_preserved") return True - return restore_fallback() + return _connect_home_or_idle() if activate(PHONE_CONNECTION): time.sleep(3) if internet_reachable(): @@ -281,20 +265,28 @@ def check_uplink(): "Phone hotspot is connected but internet is not reachable.", ) return True - return restore_fallback() + return _connect_home_or_idle() if active_connection() == HOME_CONNECTION: write_status("phone_unavailable_home_preserved") return True - return restore_fallback() + return _connect_home_or_idle() - if current != AP_CONNECTION: - write_status("phone_not_configured") + # Phone not configured. Home wifi is the only uplink on wlan1. + if current == HOME_CONNECTION: + write_status("home_connected_no_phone") return True - run_nmcli("connection", "down", AP_CONNECTION, check=False) - run_nmcli("device", "wifi", "rescan", "ifname", INTERFACE, check=False) - time.sleep(5) - return restore_fallback() + # Not on home-wifi — try to connect + return _connect_home_or_idle() + + +def _connect_home_or_idle(): + """Try home-wifi on wlan1. wlan0 AP is never touched.""" + if activate(HOME_CONNECTION): + write_status("connected_home") + return True + write_status("no_uplink_available", "No home WiFi or phone hotspot in range.") + return True def wait_for_phone(timeout=120): @@ -304,11 +296,8 @@ def wait_for_phone(timeout=120): write_status("phone_connected") return True - original_connection = active_connection() write_status("waiting_for_phone") time.sleep(10) - if original_connection == AP_CONNECTION: - run_nmcli("connection", "down", AP_CONNECTION, check=False) deadline = time.monotonic() + timeout while time.monotonic() < deadline: @@ -317,13 +306,7 @@ def wait_for_phone(timeout=120): if phone_is_visible(): return check_uplink() - if original_connection != AP_CONNECTION and active_connection(): - write_status( - "phone_wait_timed_out_current_connection_preserved", - "Phone hotspot did not appear within two minutes.", - ) - return True - restored = restore_fallback() + restored = _connect_home_or_idle() if restored: write_status( "phone_wait_timed_out_fallback_restored", diff --git a/modules/app_network.py b/modules/app_network.py index 1979c93..6d2522c 100644 --- a/modules/app_network.py +++ b/modules/app_network.py @@ -134,17 +134,35 @@ def get_status(): connectivity = values[2] phone_ssid = _phone_ssid() internet_reachable = connectivity == "4 (full)" + + # Check wlan0 AP state (permanent hotspot) + ap_active = False + ap_ip = "" + try: + ap_active = subprocess.run( + ["nmcli", "-g", "GENERAL.CONNECTION", "device", "show", "wlan0"], + capture_output=True, text=True, timeout=5, + ).stdout.strip() == "Hotspot-local" + if ap_active: + ap_ip = subprocess.run( + ["nmcli", "-g", "IP4.ADDRESS", "device", "show", "wlan0"], + capture_output=True, text=True, timeout=5, + ).stdout.strip().split("/", 1)[0] + except (OSError, subprocess.SubprocessError): + pass + + # Mode reflects wlan1 uplink state, not wlan0 AP + uplink_mode = ( + "phone" if active == PHONE_CONNECTION + else "wifi" if active + else "disconnected" + ) + return { "active_connection": active, - "mode": ( - "phone" - if active == PHONE_CONNECTION - else "local_hotspot" - if active == "Hotspot" - else "wifi" - if active - else "disconnected" - ), + "mode": uplink_mode, + "ap_active": ap_active, + "ap_ip": ap_ip, "state": values[1], "internet_reachable": internet_reachable, "connectivity": connectivity, diff --git a/static/js/settings.js b/static/js/settings.js index 833f19f..80c8ed1 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -255,13 +255,12 @@ async function loadUplinkStatus() { const disconnectedInfo = document.getElementById('in-car-disconnected-info'); const hotspotUrl = document.getElementById('hotspot-url'); const copyBtn = document.getElementById('copy-hotspot-url'); - if (status.mode === 'local_hotspot') { - const ip = status.ip_address || '10.42.0.1'; - hotspotUrl.textContent = `http://${ip}`; + if (status.ap_active && status.ap_ip) { + hotspotUrl.textContent = `http://${status.ap_ip}`; hotspotInfo.hidden = false; wifiInfo.hidden = true; disconnectedInfo.hidden = true; - if (copyBtn) copyBtn.onclick = () => navigator.clipboard.writeText(`http://${ip}`); + if (copyBtn) copyBtn.onclick = () => navigator.clipboard.writeText(`http://${status.ap_ip}`); } else if (status.mode === 'wifi' || status.mode === 'phone') { hotspotInfo.hidden = true; wifiInfo.hidden = false;