diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index 6d070c2..9938a0a 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -174,9 +174,44 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
+
+
+
+
Settings
+
Configure the Cargo ESP32 controller. The Cargo ESP remains the source of truth for relays, sensors, WiFi, and AP settings.
+
+
+
+
-
WiFi Config
-
AP remains available at 192.168.4.1. Lower priority number is tried first.
+
+
+
Access Point
+
This is the local camp network hosted by the Cargo ESP32. Phones and the dashboard connect here when no Starlink/home WiFi is available.
+
+
+
+
Current AP--
+
Security--
+ +
SSID must be 1-32 characters.
+ +
Password must be 8-63 characters. The current password is never displayed.
+
+ + +
+
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.
+
+
+ +
+
+
+
Home / Starlink WiFi
+
Optional client networks the Cargo ESP can join. Lower priority number is tried first. The AP remains available for local access.
+
+
@@ -193,15 +228,23 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
-
Relay Config
-
Rename relay outputs. IDs stay fixed for API/UART compatibility.
+
+
+
Relay Outputs
+
Rename relay outputs. IDs stay fixed for API/UART compatibility.
+
+
-
Temperature Assignment
-
Scan probes, then assign a discovered sensor to a configured temp slot.
+
+
+
Temperature Assignment
+
Scan probes, then assign a discovered sensor to a configured temp slot.
+
+
@@ -219,7 +262,12 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
-
Device Config
+
+
+
Device
+
Controller identity and BMS connection settings.
+
+
@@ -309,6 +357,59 @@ function weatherEmoji(f){ if(f < 85) return "☀️"; return "🔥"; } + +function toggleApPassword(){ + const input=$("apPasswordInput"); + if(!input) return; + input.type=input.type==="password" ? "text" : "password"; +} + +function renderApConfig(data){ + const n=data.network||{}; + if($("apCurrentSsid")) $("apCurrentSsid").textContent=n.ap_ssid||"--"; + if($("apSecurity")) $("apSecurity").textContent=n.ap_auth||"--"; + if($("apSsidInput") && !$("apSsidInput").dataset.touched){ + $("apSsidInput").value=n.ap_ssid||""; + } +} + +async function saveApConfig(){ + const ssid=$("apSsidInput")?.value.trim()||""; + const password=$("apPasswordInput")?.value||""; + + if(!ssid){ + alert("AP SSID is required."); + return; + } + + if(password.length<8 || password.length>63){ + alert("AP password must be 8-63 characters."); + return; + } + + if(!confirm("Saving AP settings will restart the controller access point. Your phone may disconnect. Continue?")){ + return; + } + + const r=await fetch(api("/config/ap"),{ + method:"POST", + headers:{"Content-Type":"application/json"}, + body:JSON.stringify({ssid,password}) + }); + + const text=await r.text(); + let body=null; + try{ body=JSON.parse(text); }catch(e){} + + if(!r.ok || body?.ok===false){ + alert("Failed to save AP settings: "+(body?.error||text||r.status)); + return; + } + + $("apPasswordInput").value=""; + alert("AP settings saved. Reconnect to the new WiFi network if this page stops updating."); +} + function renderWeatherBadge(data){ const badge=$("weatherBadge"); if(!badge) return; @@ -431,6 +532,7 @@ function render(data){ $("conn").className="pill good"; $("device").textContent=data.config?.device_name||"Overland Controller"; renderWeatherBadge(data); + renderApConfig(data); const b=data.battery||{}; $("soc").textContent=fmt(b.soc,0); diff --git a/tests/test_http_api_contract.py b/tests/test_http_api_contract.py index 34f6e1d..d87d159 100644 --- a/tests/test_http_api_contract.py +++ b/tests/test_http_api_contract.py @@ -329,3 +329,26 @@ def test_cargo_ap_password_is_not_returned_by_get_config_ap(): assert 'response["password_set"]' in get_body assert 'response["password"]' not in get_body + + +def test_embedded_webui_has_ap_settings_controls(): + source = firmware_source() + + assert "Access Point" in source + assert 'id="apSsidInput"' in source + assert 'id="apPasswordInput"' in source + assert "function saveApConfig()" in source + assert 'api("/config/ap")' in source + assert "toggleApPassword" in source + assert "renderApConfig(data)" in source + assert "Dashboard auto-migration is planned but not implemented yet" in source + + +def test_embedded_webui_config_page_is_grouped(): + source = firmware_source() + + assert "configGroupTitle" in source + assert "Home / Starlink WiFi" in source + assert "Relay Outputs" in source + assert "Temperature Assignment" in source + assert "Maintenance" in source