diff --git a/docs/UART_PROTOCOL.md b/docs/UART_PROTOCOL.md index 91ede25..df30a1f 100644 --- a/docs/UART_PROTOCOL.md +++ b/docs/UART_PROTOCOL.md @@ -190,3 +190,18 @@ Pico sends: {"type":"wifi_clear"} AP mode remains enabled as the recovery path. + +## WiFi Priority + +WiFi configuration supports multiple saved networks. + +Lower priority numbers are tried first. + +Example: + + {"type":"config_wifi","networks":[{"ssid":"Starlink","password":"password","priority":1},{"ssid":"Home WiFi","password":"password","priority":2}]} + +Runtime behavior: + + If STA disconnects, the ESP32 retries saved networks by priority every 30 seconds. + AP mode remains available as a recovery path. diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index 7452610..0d4e4e3 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -83,10 +83,13 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
+ + +
@@ -169,6 +172,8 @@ async function loadWifiConfig(){ const n=nets[i]||{}; const s=$("w"+(i+1)+"s"); if(s) s.value=n.ssid||""; + const pr=$("w"+(i+1)+"r"); + if(pr) pr.value=n.priority||i+1; } }catch(e){} } @@ -177,7 +182,8 @@ async function saveWifi(){ for(let i=1;i<=3;i++){ const ssid=$("w"+i+"s").value.trim(); const password=$("w"+i+"p").value; - if(ssid) networks.push({ssid,password}); + const priority=parseInt($("w"+i+"r").value||i,10); + if(ssid) networks.push({ssid,password,priority}); } await fetch("/config/wifi",{ method:"POST", @@ -218,8 +224,11 @@ Preferences wifiPrefs; const int MAX_WIFI_NETWORKS = 3; String staSsids[MAX_WIFI_NETWORKS]; String staPasswords[MAX_WIFI_NETWORKS]; +int staPriorities[MAX_WIFI_NETWORKS]; int wifiNetworkCount = 0; String activeStaSsid = ""; +unsigned long lastStaReconnectAttempt = 0; +const unsigned long STA_RECONNECT_INTERVAL_MS = 30000; void loadWifiConfig() { wifiPrefs.begin("wifi", true); @@ -232,6 +241,7 @@ void loadWifiConfig() { for (int i = 0; i < MAX_WIFI_NETWORKS; i++) { staSsids[i] = wifiPrefs.getString(("ssid" + String(i)).c_str(), ""); staPasswords[i] = wifiPrefs.getString(("pass" + String(i)).c_str(), ""); + staPriorities[i] = wifiPrefs.getInt(("priority" + String(i)).c_str(), i + 1); } // Migration path from old single-network config @@ -242,6 +252,7 @@ void loadWifiConfig() { if (oldSsid.length() > 0) { staSsids[0] = oldSsid; staPasswords[0] = oldPassword; + staPriorities[0] = 1; wifiNetworkCount = 1; } } @@ -257,6 +268,7 @@ void saveWifiConfig() { for (int i = 0; i < MAX_WIFI_NETWORKS; i++) { wifiPrefs.putString(("ssid" + String(i)).c_str(), staSsids[i]); wifiPrefs.putString(("pass" + String(i)).c_str(), staPasswords[i]); + wifiPrefs.putInt(("priority" + String(i)).c_str(), staPriorities[i]); } // Keep legacy keys updated for easier debugging @@ -277,9 +289,30 @@ void clearWifiConfig() { for (int i = 0; i < MAX_WIFI_NETWORKS; i++) { staSsids[i] = ""; staPasswords[i] = ""; + staPriorities[i] = i + 1; } } +int findNextWifiIndexByPriority(bool tried[]) { + int bestIndex = -1; + int bestPriority = 1000000; + + for (int i = 0; i < wifiNetworkCount; i++) { + if (tried[i]) continue; + if (staSsids[i].length() == 0) continue; + + int priority = staPriorities[i]; + if (priority <= 0) priority = i + 1; + + if (priority < bestPriority) { + bestPriority = priority; + bestIndex = i; + } + } + + return bestIndex; +} + void connectStaWifi() { if (wifiNetworkCount <= 0) { Serial.println("STA WiFi not configured."); @@ -289,12 +322,20 @@ void connectStaWifi() { WiFi.disconnect(false); activeStaSsid = ""; - for (int i = 0; i < wifiNetworkCount; i++) { - if (staSsids[i].length() == 0) { - continue; + bool tried[MAX_WIFI_NETWORKS] = {false, false, false}; + + for (int attempt = 0; attempt < wifiNetworkCount; attempt++) { + int i = findNextWifiIndexByPriority(tried); + + if (i < 0) { + break; } - Serial.print("Connecting STA WiFi to: "); + tried[i] = true; + + Serial.print("Connecting STA WiFi priority "); + Serial.print(staPriorities[i]); + Serial.print(" to: "); Serial.println(staSsids[i]); WiFi.begin(staSsids[i].c_str(), staPasswords[i].c_str()); @@ -323,6 +364,19 @@ void connectStaWifi() { Serial.println("All STA WiFi attempts failed. AP remains available."); } +void maintainStaWifi() { + if (wifiNetworkCount <= 0) return; + + if (WiFi.status() == WL_CONNECTED) return; + + if (millis() - lastStaReconnectAttempt < STA_RECONNECT_INTERVAL_MS) return; + + lastStaReconnectAttempt = millis(); + + Serial.println("STA WiFi disconnected. Trying saved networks by priority..."); + connectStaWifi(); +} + void printNetworkStatus() { Serial.println("Network:"); Serial.print(" AP IP: "); @@ -441,6 +495,14 @@ void buildStatusDocument(JsonDocument& doc) { network["sta_ssid"] = activeStaSsid; network["sta_ip"] = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : ""; network["saved_network_count"] = wifiNetworkCount; + JsonArray savedNetworks = network.createNestedArray("saved_networks"); + for (int i = 0; i < wifiNetworkCount; i++) { + JsonObject saved = savedNetworks.createNestedObject(); + saved["index"] = i + 1; + saved["ssid"] = staSsids[i]; + saved["priority"] = staPriorities[i]; + saved["active"] = staSsids[i] == activeStaSsid; + } JsonObject alarmObj = doc.createNestedObject("alarms"); alarmObj["low_soc"] = alarms.lowSoc; @@ -958,6 +1020,7 @@ void handleDebugSerial() { if (command.startsWith("wifi ssid ")) { staSsids[0] = command.substring(10); + staPriorities[0] = 1; if (wifiNetworkCount < 1) wifiNetworkCount = 1; Serial.println("OK WiFi SSID 1 updated"); Serial.println("Run: wifi save"); @@ -966,6 +1029,7 @@ void handleDebugSerial() { if (command.startsWith("wifi pass ")) { staPasswords[0] = command.substring(10); + staPriorities[0] = 1; if (wifiNetworkCount < 1) wifiNetworkCount = 1; Serial.println("OK WiFi password 1 updated"); Serial.println("Run: wifi save"); @@ -986,8 +1050,21 @@ void handleDebugSerial() { return; } - staSsids[wifiNetworkCount] = rest.substring(0, split); - staPasswords[wifiNetworkCount] = rest.substring(split + 1); + String ssid = rest.substring(0, split); + String passAndPriority = rest.substring(split + 1); + int secondSplit = passAndPriority.indexOf('|'); + String password = passAndPriority; + int priority = wifiNetworkCount + 1; + + if (secondSplit >= 0) { + password = passAndPriority.substring(0, secondSplit); + priority = passAndPriority.substring(secondSplit + 1).toInt(); + if (priority <= 0) priority = wifiNetworkCount + 1; + } + + staSsids[wifiNetworkCount] = ssid; + staPasswords[wifiNetworkCount] = password; + staPriorities[wifiNetworkCount] = priority; wifiNetworkCount++; Serial.println("OK WiFi network added"); @@ -1310,6 +1387,7 @@ void sendWifiConfigJson() { JsonObject network = networks.createNestedObject(); network["index"] = i + 1; network["ssid"] = staSsids[i]; + network["priority"] = staPriorities[i]; network["password_set"] = staPasswords[i].length() > 0; } @@ -1348,23 +1426,30 @@ void handleUpdateWifiConfig() { continue; } + int priority = network["priority"] | (wifiNetworkCount + 1); + if (priority <= 0) priority = wifiNetworkCount + 1; + staSsids[wifiNetworkCount] = ssid; staPasswords[wifiNetworkCount] = password; + staPriorities[wifiNetworkCount] = priority; wifiNetworkCount++; } for (int i = wifiNetworkCount; i < MAX_WIFI_NETWORKS; i++) { staSsids[i] = ""; staPasswords[i] = ""; + staPriorities[i] = i + 1; } } else { if (doc["ssid"].is()) { staSsids[0] = doc["ssid"].as(); + staPriorities[0] = 1; if (wifiNetworkCount < 1) wifiNetworkCount = 1; } if (doc["password"].is()) { staPasswords[0] = doc["password"].as(); + staPriorities[0] = 1; if (wifiNetworkCount < 1) wifiNetworkCount = 1; } } @@ -1635,6 +1720,7 @@ void setup() { void loop() { server.handleClient(); + maintainStaWifi(); handleDebugSerial(); updateBms(); updateAlarms();