fix(companion-wifi): quiesce STA scanning so the recovery AP is joinable

With saved credentials present (seeded from the WIFI_SSID build flag on
first boot), the recovery AP came up in AP+STA mode while auto-reconnect
and the 10s manual retry kept the station scanning. STA scans drag the
shared radio across channels, so joining clients' WPA2 handshakes timed
out - reported by phones as a wrong password.

Disable auto-reconnect and pause the 10s retry loop while the recovery
AP is active; retry the configured network once a minute instead, so the
AP stays stable between attempts and the node still self-recovers (and
shuts the AP down) when its network returns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jared Dohrman
2026-08-09 18:34:19 +10:00
co-authored by Claude Fable 5
parent 5054b02c62
commit cfb4cc9bc6
3 changed files with 26 additions and 2 deletions
+19
View File
@@ -2382,6 +2382,9 @@ void MyMesh::checkCLIRescueCmd() {
#ifndef RECOVERY_CLI_PORT
#define RECOVERY_CLI_PORT 23
#endif
#ifndef RECOVERY_STA_RETRY_MS
#define RECOVERY_STA_RETRY_MS 60000
#endif
void MyMesh::startRecoveryAP() {
if (_recovery_ap_active) return;
@@ -2393,7 +2396,14 @@ void MyMesh::startRecoveryAP() {
pwd = ap_pwd;
}
// A continuously scanning/reconnecting STA drags the AP's radio across channels,
// so joining clients time out their WPA2 handshake (phones report "wrong password").
// Quiesce the STA while the AP is up; loopRecoveryAP() retries it once a minute.
WiFi.setAutoReconnect(false);
WiFi.mode(_prefs.wifi_ssid[0] ? WIFI_AP_STA : WIFI_AP); // keep trying STA if creds exist
if (_prefs.wifi_ssid[0]) {
WiFi.disconnect();
}
WiFi.softAP(RECOVERY_AP_SSID, pwd);
if (_rescue_server == NULL) {
@@ -2401,6 +2411,7 @@ void MyMesh::startRecoveryAP() {
}
_rescue_server->begin();
rescue_cmd[0] = 0;
_next_sta_retry = millis() + RECOVERY_STA_RETRY_MS;
_recovery_ap_active = true;
Serial.printf("WiFi: recovery AP '%s' up (%s), rescue CLI on %s:%d\n", RECOVERY_AP_SSID,
@@ -2414,6 +2425,7 @@ void MyMesh::stopRecoveryAP() {
_rescue_server->stop();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
_recovery_ap_active = false;
Serial.println("WiFi: recovery AP stopped");
}
@@ -2421,6 +2433,13 @@ void MyMesh::stopRecoveryAP() {
void MyMesh::loopRecoveryAP() {
if (!_recovery_ap_active) return;
// periodic STA retry: brief AP disruption once a minute beats a permanently
// unjoinable AP, and lets the node self-recover when its network comes back
if (_prefs.wifi_ssid[0] && WiFi.status() != WL_CONNECTED && (long)(millis() - _next_sta_retry) >= 0) {
WiFi.begin(_prefs.wifi_ssid, _prefs.wifi_pwd);
_next_sta_retry = millis() + RECOVERY_STA_RETRY_MS;
}
if (!_rescue_client || !_rescue_client.connected()) {
WiFiClient next = _rescue_server->available();
if (next) {
+1
View File
@@ -250,6 +250,7 @@ private:
WiFiServer* _rescue_server = NULL;
WiFiClient _rescue_client;
char rescue_cmd[80];
unsigned long _next_sta_retry = 0;
#endif
uint8_t app_target_ver;
uint8_t *sign_data;
+6 -2
View File
@@ -275,8 +275,12 @@ void loop() {
}
#if defined(ESP32) && defined(WIFI_SSID)
// Safely attempt to reconnect every 10 seconds if flagged
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
// Safely attempt to reconnect every 10 seconds if flagged.
// Skipped while the recovery AP is up: constant STA rescans drag the AP's radio
// across channels and break joining clients' WPA2 handshakes; loopRecoveryAP()
// owns the (slower) STA retry cadence in that state.
if (!the_mesh.isRecoveryAPActive() &&
wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
WiFi.disconnect();
WiFi.reconnect();