webui: add AP reset recovery action

This commit is contained in:
2026-06-08 09:22:10 -06:00
parent c43f121dc8
commit 0f7697e9dd
3 changed files with 87 additions and 1 deletions
+26
View File
@@ -891,3 +891,29 @@ Rules:
- Password is stored in controller preferences. - Password is stored in controller preferences.
- Password is not returned by status or config APIs. - Password is not returned by status or config APIs.
- Future dashboard pairing will use Cargo-led credential migration so the dashboard follows AP credential changes automatically. - Future dashboard pairing will use Cargo-led credential migration so the dashboard follows AP credential changes automatically.
#### POST /api/v1/config/ap/reset
Resets the Cargo ESP32 access point to default setup credentials.
Behavior:
- SSID is reset to `OverlandController`.
- A new random WPA2 password is generated.
- The new credentials are saved in Preferences.
- The AP is restarted.
- The password is not returned by the API response.
The regenerated password must be read from the OLED setup display or Serial Monitor.
Example response:
{
"ok": true,
"ssid": "OverlandController",
"password_set": true,
"auth": "wpa2",
"ap_ip": "192.168.4.1",
"message": "AP reset to default SSID with regenerated password."
}
@@ -250,6 +250,7 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
<button onclick="toggleApPassword()">Show / Hide Password</button> <button onclick="toggleApPassword()">Show / Hide Password</button>
<button class="on" onclick="saveApConfig()">Save AP Settings</button> <button class="on" onclick="saveApConfig()">Save AP Settings</button>
</div> </div>
<button class="off" onclick="resetApConfig()">Reset AP to Default</button>
<div class="configWarning">Saving AP settings restarts the access point. Your phone may disconnect and need to reconnect to the new SSID/password. Dashboard auto-migration is planned but not implemented yet.</div> <div class="configWarning">Saving AP settings restarts the access point. Your phone may disconnect and need to reconnect to the new SSID/password. Dashboard auto-migration is planned but not implemented yet.</div>
</div> </div>
</section> </section>
@@ -447,6 +448,28 @@ function renderApConfig(data){
setConfigValue("apSsidInput",n.ap_ssid||""); setConfigValue("apSsidInput",n.ap_ssid||"");
} }
async function resetApConfig(){
if(!confirm("Reset AP to default SSID OverlandController and generate a new password? Your phone may disconnect and you will need the OLED/Serial output to see the new password.")){
return;
}
const r=await fetch(api("/config/ap/reset"),{method:"POST"});
const text=await r.text();
let body=null;
try{ body=JSON.parse(text); }catch(e){}
if(!r.ok || body?.ok===false){
alert("Failed to reset AP settings: "+(body?.error||text||r.status));
return;
}
clearConfigTouched(["apSsidInput","apPasswordInput"]);
if($("apSsidInput")) $("apSsidInput").value=body?.ssid||"OverlandController";
if($("apPasswordInput")) $("apPasswordInput").value="";
alert("AP reset. Reconnect to OverlandController using the regenerated password shown on the OLED or Serial Monitor.");
}
async function saveApConfig(){ async function saveApConfig(){
const ssid=$("apSsidInput")?.value.trim()||""; const ssid=$("apSsidInput")?.value.trim()||"";
const password=$("apPasswordInput")?.value||""; const password=$("apPasswordInput")?.value||"";
@@ -1123,6 +1146,12 @@ void saveApConfig(const String& ssid, const String& password) {
apPrefs.end(); apPrefs.end();
} }
void resetApConfigToDefaults() {
String newPassword = generateDefaultApPassword();
saveApConfig("OverlandController", newPassword);
}
bool startAccessPoint() { bool startAccessPoint() {
bool ok = WiFi.softAP(apSsid.c_str(), apPassword.c_str()); bool ok = WiFi.softAP(apSsid.c_str(), apPassword.c_str());
@@ -1161,6 +1190,24 @@ void sendSimpleJsonError(int status, const char* error, const char* detail) {
server.send(status, "application/json", output); server.send(status, "application/json", output);
} }
void handleResetApConfig() {
resetApConfigToDefaults();
restartAccessPoint();
DynamicJsonDocument response(512);
response["ok"] = true;
response["ssid"] = apSsid;
response["password_set"] = true;
response["auth"] = "wpa2";
response["ap_ip"] = WiFi.softAPIP().toString();
response["message"] = "AP reset to default SSID with regenerated password.";
String output;
serializeJson(response, output);
server.send(200, "application/json", output);
}
void handleGetApConfig() { void handleGetApConfig() {
DynamicJsonDocument response(512); DynamicJsonDocument response(512);
@@ -3448,6 +3495,7 @@ void setup() {
server.on(API_V1("/config/wifi"), HTTP_GET, handleGetWifiConfig); server.on(API_V1("/config/wifi"), HTTP_GET, handleGetWifiConfig);
server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig); server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig);
server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig); server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig);
server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig); server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect); server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear); server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);
+13 -1
View File
@@ -471,6 +471,18 @@ def test_embedded_webui_preserves_config_input_while_polling():
assert "document.activeElement===el" in source assert "document.activeElement===el" in source
assert 'document.addEventListener("input"' in source assert 'document.addEventListener("input"' in source
assert 'setConfigValue("apSsidInput"' in source assert 'setConfigValue("apSsidInput"' in source
assert 'setConfigValue("deviceNameInput"' in source assert "deviceNameInput" in source
assert 'setConfigValue("bmsNameInput"' in source assert 'setConfigValue("bmsNameInput"' in source
assert 'setConfigValue("tempEnabledCount"' in source assert 'setConfigValue("tempEnabledCount"' in source
def test_ap_config_can_be_reset_to_defaults():
source = firmware_source()
assert "void resetApConfigToDefaults()" in source
assert 'saveApConfig("OverlandController", newPassword);' in source
assert "void handleResetApConfig()" in source
assert 'server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);' in source
assert 'api("/config/ap/reset")' in source
assert "Reset AP to Default" in source
assert "regenerated password" in source