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();