Add network status and multi STA WiFi configuration

This commit is contained in:
2026-06-04 13:32:38 -06:00
parent edfd6021e1
commit 31ba93c68b
@@ -33,6 +33,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
.v{font-weight:650}.good{color:var(--good)}.warn{color:var(--warn)}.bad{color:var(--bad)}.muted{color:var(--muted)}
.bar{height:10px;background:#0e141c;border-radius:999px;overflow:hidden;border:1px solid var(--line)}.fill{height:100%;background:linear-gradient(90deg,var(--good),#9be15d);width:0%}
button{width:100%;border:0;border-radius:12px;padding:12px;background:var(--btn);color:var(--text);font-weight:700;font-size:15px}
input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;background:#0f151d;color:var(--text);font-size:15px}
button.on{background:#17462a;color:#dfffea}button.off{background:#46202a;color:#ffe2e8}.relayBtns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px}
.tempList,.alarmList{display:grid;gap:8px;margin-top:12px}.item{display:flex;justify-content:space-between;gap:8px;background:#0f151d;border:1px solid var(--line);border-radius:12px;padding:10px}
@media(max-width:760px){.span6,.span4{grid-column:span 12}.big{font-size:36px}.wrap{padding:12px}}
@@ -76,6 +77,23 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
<div class="tempList" id="relays"></div>
</section>
<section class="card span12">
<div class="label">WiFi</div>
<div class="sub">AP stays available at 192.168.4.1. STA networks are tried in order.</div>
<div class="tempList" style="margin-top:12px">
<input id="w1s" placeholder="Network 1 SSID">
<input id="w1p" placeholder="Network 1 password" type="password">
<input id="w2s" placeholder="Network 2 SSID">
<input id="w2p" placeholder="Network 2 password" type="password">
<input id="w3s" placeholder="Network 3 SSID">
<input id="w3p" placeholder="Network 3 password" type="password">
<button onclick="saveWifi()">Save WiFi</button>
<button onclick="connectWifi()">Connect Now</button>
</div>
<div class="row"><span class="muted">STA</span><span class="v" id="wifiSta">--</span></div>
<div class="row"><span class="muted">STA IP</span><span class="v" id="wifiIp">--</span></div>
</section>
<section class="card span12">
<div class="label">Alarms / System</div>
<div class="alarmList" id="alarms"></div>
@@ -136,9 +154,43 @@ function render(data){
<div class="item"><span>${k.replaceAll("_"," ")}</span><strong class="${clsAlarm(alarms[k])}">${alarms[k]?"ALARM":"OK"}</strong></div>`).join("");
const s=data.system||{};
const n=data.network||{};
$("wifiSta").textContent=n.sta_connected ? (n.sta_ssid||"connected") : "Not connected";
$("wifiIp").textContent=n.sta_ip||"--";
$("fw").textContent=(s.firmware_name||"--")+" "+(s.firmware_version||"");
$("uptime").textContent=(s.uptime_seconds||0)+" sec";
}
async function loadWifiConfig(){
try{
const r=await fetch("/config/wifi",{cache:"no-store"});
const d=await r.json();
const nets=d.wifi?.networks||[];
for(let i=0;i<3;i++){
const n=nets[i]||{};
const s=$("w"+(i+1)+"s");
if(s) s.value=n.ssid||"";
}
}catch(e){}
}
async function saveWifi(){
const networks=[];
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});
}
await fetch("/config/wifi",{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({networks})
});
await loadWifiConfig();
await load();
}
async function connectWifi(){
await fetch("/wifi/connect",{method:"POST"});
await load();
}
async function load(){
try{
const r=await fetch("/status",{cache:"no-store"});
@@ -149,6 +201,7 @@ async function load(){
}
}
load();
loadWifiConfig();
setInterval(load,3000);
</script>
</body>
@@ -161,20 +214,55 @@ HardwareSerial DashboardSerial(2);
String uartLineBuffer;
Preferences wifiPrefs;
String staSsid = "";
String staPassword = "";
const int MAX_WIFI_NETWORKS = 3;
String staSsids[MAX_WIFI_NETWORKS];
String staPasswords[MAX_WIFI_NETWORKS];
int wifiNetworkCount = 0;
String activeStaSsid = "";
void loadWifiConfig() {
wifiPrefs.begin("wifi", true);
staSsid = wifiPrefs.getString("ssid", "");
staPassword = wifiPrefs.getString("password", "");
wifiNetworkCount = wifiPrefs.getInt("count", 0);
if (wifiNetworkCount < 0) wifiNetworkCount = 0;
if (wifiNetworkCount > MAX_WIFI_NETWORKS) wifiNetworkCount = MAX_WIFI_NETWORKS;
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(), "");
}
// Migration path from old single-network config
if (wifiNetworkCount == 0) {
String oldSsid = wifiPrefs.getString("ssid", "");
String oldPassword = wifiPrefs.getString("password", "");
if (oldSsid.length() > 0) {
staSsids[0] = oldSsid;
staPasswords[0] = oldPassword;
wifiNetworkCount = 1;
}
}
wifiPrefs.end();
}
void saveWifiConfig() {
wifiPrefs.begin("wifi", false);
wifiPrefs.putString("ssid", staSsid);
wifiPrefs.putString("password", staPassword);
wifiPrefs.putInt("count", wifiNetworkCount);
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]);
}
// Keep legacy keys updated for easier debugging
wifiPrefs.putString("ssid", wifiNetworkCount > 0 ? staSsids[0] : "");
wifiPrefs.putString("password", wifiNetworkCount > 0 ? staPasswords[0] : "");
wifiPrefs.end();
}
@@ -182,44 +270,67 @@ void clearWifiConfig() {
wifiPrefs.begin("wifi", false);
wifiPrefs.clear();
wifiPrefs.end();
staSsid = "";
staPassword = "";
wifiNetworkCount = 0;
activeStaSsid = "";
for (int i = 0; i < MAX_WIFI_NETWORKS; i++) {
staSsids[i] = "";
staPasswords[i] = "";
}
}
void connectStaWifi() {
if (staSsid.length() == 0) {
if (wifiNetworkCount <= 0) {
Serial.println("STA WiFi not configured.");
return;
}
Serial.print("Connecting STA WiFi to: ");
Serial.println(staSsid);
WiFi.disconnect(false);
activeStaSsid = "";
WiFi.begin(staSsid.c_str(), staPassword.c_str());
for (int i = 0; i < wifiNetworkCount; i++) {
if (staSsids[i].length() == 0) {
continue;
}
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
delay(500);
Serial.print(".");
Serial.print("Connecting STA WiFi to: ");
Serial.println(staSsids[i]);
WiFi.begin(staSsids[i].c_str(), staPasswords[i].c_str());
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
delay(500);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
activeStaSsid = staSsids[i];
Serial.println("STA WiFi connected");
Serial.print("STA SSID: ");
Serial.println(activeStaSsid);
Serial.print("STA IP: ");
Serial.println(WiFi.localIP());
return;
}
Serial.println("STA WiFi attempt failed.");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.println("STA WiFi connected");
Serial.print("STA IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("STA WiFi failed. AP remains available.");
}
Serial.println("All STA WiFi attempts failed. AP remains available.");
}
void printNetworkStatus() {
Serial.println("Network:");
Serial.print(" AP IP: ");
Serial.println(WiFi.softAPIP());
Serial.print(" STA SSID: ");
Serial.println(staSsid.length() ? staSsid : "(not configured)");
Serial.print(" Saved STA Networks: ");
Serial.println(wifiNetworkCount);
Serial.print(" Active STA SSID: ");
Serial.println(activeStaSsid.length() ? activeStaSsid : "(none)");
Serial.print(" STA Connected: ");
Serial.println(WiFi.status() == WL_CONNECTED ? "true" : "false");
if (WiFi.status() == WL_CONNECTED) {
@@ -323,6 +434,13 @@ void buildStatusDocument(JsonDocument& doc) {
JsonObject network = doc.createNestedObject("network");
network["wifi_enabled"] = true;
network["uart_connected"] = true;
network["ap_enabled"] = true;
network["ap_ip"] = WiFi.softAPIP().toString();
network["sta_enabled"] = wifiNetworkCount > 0;
network["sta_connected"] = WiFi.status() == WL_CONNECTED;
network["sta_ssid"] = activeStaSsid;
network["sta_ip"] = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "";
network["saved_network_count"] = wifiNetworkCount;
JsonObject alarmObj = doc.createNestedObject("alarms");
alarmObj["low_soc"] = alarms.lowSoc;
@@ -613,12 +731,20 @@ void handleUartMessage(const String& line) {
JsonObject wifi = response.createNestedObject("wifi");
wifi["ap_enabled"] = true;
wifi["sta_enabled"] = staSsid.length() > 0;
wifi["ssid"] = staSsid;
wifi["password_set"] = staPassword.length() > 0;
wifi["sta_enabled"] = wifiNetworkCount > 0;
wifi["network_count"] = wifiNetworkCount;
wifi["active_ssid"] = activeStaSsid;
wifi["sta_connected"] = WiFi.status() == WL_CONNECTED;
wifi["ap_ip"] = WiFi.softAPIP().toString();
JsonArray networks = wifi.createNestedArray("networks");
for (int i = 0; i < wifiNetworkCount; i++) {
JsonObject network = networks.createNestedObject();
network["index"] = i + 1;
network["ssid"] = staSsids[i];
network["password_set"] = staPasswords[i].length() > 0;
}
if (WiFi.status() == WL_CONNECTED) {
wifi["sta_ip"] = WiFi.localIP().toString();
} else {
@@ -631,12 +757,34 @@ void handleUartMessage(const String& line) {
}
if (strcmp(type, "config_wifi") == 0) {
if (doc["ssid"].is<String>()) {
staSsid = doc["ssid"].as<String>();
}
JsonArray networks = doc["networks"].as<JsonArray>();
if (doc["password"].is<String>()) {
staPassword = doc["password"].as<String>();
if (!networks.isNull()) {
wifiNetworkCount = 0;
for (JsonObject network : networks) {
if (wifiNetworkCount >= MAX_WIFI_NETWORKS) break;
String ssid = network["ssid"] | "";
String password = network["password"] | "";
ssid.trim();
if (ssid.length() == 0) continue;
staSsids[wifiNetworkCount] = ssid;
staPasswords[wifiNetworkCount] = password;
wifiNetworkCount++;
}
} else {
if (doc["ssid"].is<String>()) {
staSsids[0] = doc["ssid"].as<String>();
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
}
if (doc["password"].is<String>()) {
staPasswords[0] = doc["password"].as<String>();
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
}
}
saveWifiConfig();
@@ -644,8 +792,7 @@ void handleUartMessage(const String& line) {
DynamicJsonDocument response(512);
response["type"] = "wifi_config_response";
response["ok"] = true;
response["ssid"] = staSsid;
response["password_set"] = staPassword.length() > 0;
response["network_count"] = wifiNetworkCount;
serializeJson(response, DashboardSerial);
DashboardSerial.println();
@@ -810,15 +957,40 @@ void handleDebugSerial() {
}
if (command.startsWith("wifi ssid ")) {
staSsid = command.substring(10);
Serial.println("OK WiFi SSID updated");
staSsids[0] = command.substring(10);
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
Serial.println("OK WiFi SSID 1 updated");
Serial.println("Run: wifi save");
return;
}
if (command.startsWith("wifi pass ")) {
staPassword = command.substring(10);
Serial.println("OK WiFi password updated");
staPasswords[0] = command.substring(10);
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
Serial.println("OK WiFi password 1 updated");
Serial.println("Run: wifi save");
return;
}
if (command.startsWith("wifi add ")) {
String rest = command.substring(9);
int split = rest.indexOf('|');
if (split < 0) {
Serial.println("Invalid command. Use: wifi add SSID|PASSWORD");
return;
}
if (wifiNetworkCount >= MAX_WIFI_NETWORKS) {
Serial.println("WiFi network list full");
return;
}
staSsids[wifiNetworkCount] = rest.substring(0, split);
staPasswords[wifiNetworkCount] = rest.substring(split + 1);
wifiNetworkCount++;
Serial.println("OK WiFi network added");
Serial.println("Run: wifi save");
return;
}
@@ -1114,16 +1286,16 @@ void handleFactoryResetConfig() {
void sendWifiConfigJson() {
DynamicJsonDocument doc(1024);
DynamicJsonDocument doc(2048);
doc["type"] = "wifi_config_response";
doc["ok"] = true;
JsonObject wifi = doc.createNestedObject("wifi");
wifi["ap_enabled"] = true;
wifi["sta_enabled"] = staSsid.length() > 0;
wifi["ssid"] = staSsid;
wifi["password_set"] = staPassword.length() > 0;
wifi["sta_enabled"] = wifiNetworkCount > 0;
wifi["network_count"] = wifiNetworkCount;
wifi["active_ssid"] = activeStaSsid;
wifi["sta_connected"] = WiFi.status() == WL_CONNECTED;
wifi["ap_ip"] = WiFi.softAPIP().toString();
@@ -1133,6 +1305,14 @@ void sendWifiConfigJson() {
wifi["sta_ip"] = "";
}
JsonArray networks = wifi.createNestedArray("networks");
for (int i = 0; i < wifiNetworkCount; i++) {
JsonObject network = networks.createNestedObject();
network["index"] = i + 1;
network["ssid"] = staSsids[i];
network["password_set"] = staPasswords[i].length() > 0;
}
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
@@ -1143,7 +1323,7 @@ void handleGetWifiConfig() {
}
void handleUpdateWifiConfig() {
DynamicJsonDocument doc(1024);
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, server.arg("plain"));
if (error) {
@@ -1151,12 +1331,42 @@ void handleUpdateWifiConfig() {
return;
}
if (doc["ssid"].is<String>()) {
staSsid = doc["ssid"].as<String>();
}
JsonArray networks = doc["networks"].as<JsonArray>();
if (doc["password"].is<String>()) {
staPassword = doc["password"].as<String>();
if (!networks.isNull()) {
wifiNetworkCount = 0;
for (JsonObject network : networks) {
if (wifiNetworkCount >= MAX_WIFI_NETWORKS) break;
String ssid = network["ssid"] | "";
String password = network["password"] | "";
ssid.trim();
if (ssid.length() == 0) {
continue;
}
staSsids[wifiNetworkCount] = ssid;
staPasswords[wifiNetworkCount] = password;
wifiNetworkCount++;
}
for (int i = wifiNetworkCount; i < MAX_WIFI_NETWORKS; i++) {
staSsids[i] = "";
staPasswords[i] = "";
}
} else {
if (doc["ssid"].is<String>()) {
staSsids[0] = doc["ssid"].as<String>();
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
}
if (doc["password"].is<String>()) {
staPasswords[0] = doc["password"].as<String>();
if (wifiNetworkCount < 1) wifiNetworkCount = 1;
}
}
saveWifiConfig();