fix(phase26): skip netwatch AP scan when clients connected (BUG-01)
This commit is contained in:
@@ -220,9 +220,28 @@ def restore_fallback():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _ap_client_count():
|
||||||
|
"""Return number of stations connected to the AP on INTERFACE."""
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
["iw", "dev", INTERFACE, "station", "dump"],
|
||||||
|
capture_output=True, text=True, timeout=5,
|
||||||
|
)
|
||||||
|
return proc.stdout.count("Station ")
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def check_uplink():
|
def check_uplink():
|
||||||
current = active_connection()
|
current = active_connection()
|
||||||
phone_configured = connection_exists(PHONE_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 current == PHONE_CONNECTION:
|
||||||
if internet_reachable():
|
if internet_reachable():
|
||||||
write_status("phone_connected")
|
write_status("phone_connected")
|
||||||
|
|||||||
@@ -248,6 +248,29 @@ async function loadUplinkStatus() {
|
|||||||
const status = await response.json();
|
const status = await response.json();
|
||||||
if (!response.ok) throw new Error(status.error || `HTTP ${response.status}`);
|
if (!response.ok) throw new Error(status.error || `HTTP ${response.status}`);
|
||||||
uplinkStatus.textContent = describeUplink(status);
|
uplinkStatus.textContent = describeUplink(status);
|
||||||
|
|
||||||
|
// In-car access card
|
||||||
|
const hotspotInfo = document.getElementById('in-car-hotspot-info');
|
||||||
|
const wifiInfo = document.getElementById('in-car-wifi-info');
|
||||||
|
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}`;
|
||||||
|
hotspotInfo.hidden = false;
|
||||||
|
wifiInfo.hidden = true;
|
||||||
|
disconnectedInfo.hidden = true;
|
||||||
|
if (copyBtn) copyBtn.onclick = () => navigator.clipboard.writeText(`http://${ip}`);
|
||||||
|
} else if (status.mode === 'wifi' || status.mode === 'phone') {
|
||||||
|
hotspotInfo.hidden = true;
|
||||||
|
wifiInfo.hidden = false;
|
||||||
|
disconnectedInfo.hidden = true;
|
||||||
|
} else {
|
||||||
|
hotspotInfo.hidden = true;
|
||||||
|
wifiInfo.hidden = true;
|
||||||
|
disconnectedInfo.hidden = false;
|
||||||
|
}
|
||||||
const phoneAddress = status.mode === 'phone'
|
const phoneAddress = status.mode === 'phone'
|
||||||
? status.ip_address
|
? status.ip_address
|
||||||
: status.last_transition && status.last_transition.last_phone_ip;
|
: status.last_transition && status.last_transition.last_phone_ip;
|
||||||
|
|||||||
@@ -134,6 +134,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# ── In-car access ────────────────────────────────────────────────── #}
|
||||||
|
<div class="card shadow-sm mb-3" id="in-car-access-card">
|
||||||
|
<div class="card-header py-2">
|
||||||
|
<p class="text-primary m-0 fw-bold small">In-car access</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body py-3">
|
||||||
|
<div id="in-car-hotspot-info" hidden>
|
||||||
|
<p class="mb-2 fw-semibold">Your phone can reach the Pi at:</p>
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-3">
|
||||||
|
<code class="fs-5" id="hotspot-url">http://10.42.0.1</code>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" id="copy-hotspot-url" type="button" title="Copy">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning py-2 mb-0 small">
|
||||||
|
<i class="fas fa-exclamation-triangle me-1"></i>
|
||||||
|
If the page doesn't load, try <strong>disabling cellular/mobile data</strong> on your phone.
|
||||||
|
Phones often keep using cell data when WiFi has no internet, blocking access to the Pi.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="in-car-wifi-info" hidden>
|
||||||
|
<p class="mb-1 small text-muted">The Pi is connected to WiFi. Open <code>http://yaris.local</code> from any device on the same network.</p>
|
||||||
|
</div>
|
||||||
|
<div id="in-car-disconnected-info" hidden>
|
||||||
|
<p class="mb-0 small text-muted">Waiting for network…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# ── Phone hotspot ───────────────────────────────────────────────── #}
|
{# ── Phone hotspot ───────────────────────────────────────────────── #}
|
||||||
<div class="card shadow-sm mb-3">
|
<div class="card shadow-sm mb-3">
|
||||||
<div class="card-header py-2">
|
<div class="card-header py-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user