From edfd6021e1f62a42385ea12f5ad278596ea6a183 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 4 Jun 2026 13:17:07 -0600 Subject: [PATCH] Add WiFi HTTP and UART configuration parity --- docs/UART_PROTOCOL.md | 30 ++++ .../overland-controller.ino | 140 ++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/docs/UART_PROTOCOL.md b/docs/UART_PROTOCOL.md index f7d35ff..91ede25 100644 --- a/docs/UART_PROTOCOL.md +++ b/docs/UART_PROTOCOL.md @@ -160,3 +160,33 @@ The UART protocol should eventually support the same management capabilities as - HTTP is useful for setup, debugging, and future web UI. - USB serial remains the emergency service/debug interface. - The Pico should not depend on WiFi for normal control. + +## WiFi Config Request + +Pico sends: + + {"type":"wifi_request"} + +ESP32 responds: + + {"type":"wifi_config_response","ok":true,"wifi":{}} + +## WiFi Config Update + +Pico sends: + + {"type":"config_wifi","ssid":"Starlink","password":"password_here"} + +## WiFi Connect + +Pico sends: + + {"type":"wifi_connect"} + +## WiFi Clear + +Pico sends: + + {"type":"wifi_clear"} + +AP mode remains enabled as the recovery path. diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index 3ca844a..3153b85 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -606,6 +606,80 @@ void handleUartMessage(const String& line) { return; } + if (strcmp(type, "wifi_request") == 0) { + DynamicJsonDocument response(1024); + response["type"] = "wifi_config_response"; + response["ok"] = true; + + 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_connected"] = WiFi.status() == WL_CONNECTED; + wifi["ap_ip"] = WiFi.softAPIP().toString(); + + if (WiFi.status() == WL_CONNECTED) { + wifi["sta_ip"] = WiFi.localIP().toString(); + } else { + wifi["sta_ip"] = ""; + } + + serializeJson(response, DashboardSerial); + DashboardSerial.println(); + return; + } + + if (strcmp(type, "config_wifi") == 0) { + if (doc["ssid"].is()) { + staSsid = doc["ssid"].as(); + } + + if (doc["password"].is()) { + staPassword = doc["password"].as(); + } + + saveWifiConfig(); + + DynamicJsonDocument response(512); + response["type"] = "wifi_config_response"; + response["ok"] = true; + response["ssid"] = staSsid; + response["password_set"] = staPassword.length() > 0; + + serializeJson(response, DashboardSerial); + DashboardSerial.println(); + return; + } + + if (strcmp(type, "wifi_connect") == 0) { + connectStaWifi(); + + DynamicJsonDocument response(512); + response["type"] = "wifi_config_response"; + response["ok"] = true; + response["sta_connected"] = WiFi.status() == WL_CONNECTED; + response["sta_ip"] = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : ""; + + serializeJson(response, DashboardSerial); + DashboardSerial.println(); + return; + } + + if (strcmp(type, "wifi_clear") == 0) { + clearWifiConfig(); + + DynamicJsonDocument response(512); + response["type"] = "wifi_config_response"; + response["ok"] = true; + response["ssid"] = ""; + response["password_set"] = false; + + serializeJson(response, DashboardSerial); + DashboardSerial.println(); + return; + } + if (strcmp(type, "save_config") == 0) { saveConfig(); sendConfigResponse(DashboardSerial); @@ -1038,6 +1112,67 @@ void handleFactoryResetConfig() { } + +void sendWifiConfigJson() { + DynamicJsonDocument doc(1024); + + 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_connected"] = WiFi.status() == WL_CONNECTED; + wifi["ap_ip"] = WiFi.softAPIP().toString(); + + if (WiFi.status() == WL_CONNECTED) { + wifi["sta_ip"] = WiFi.localIP().toString(); + } else { + wifi["sta_ip"] = ""; + } + + String output; + serializeJson(doc, output); + server.send(200, "application/json", output); +} + +void handleGetWifiConfig() { + sendWifiConfigJson(); +} + +void handleUpdateWifiConfig() { + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, server.arg("plain")); + + if (error) { + server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}"); + return; + } + + if (doc["ssid"].is()) { + staSsid = doc["ssid"].as(); + } + + if (doc["password"].is()) { + staPassword = doc["password"].as(); + } + + saveWifiConfig(); + sendWifiConfigJson(); +} + +void handleWifiConnect() { + connectStaWifi(); + sendWifiConfigJson(); +} + +void handleWifiClear() { + clearWifiConfig(); + sendWifiConfigJson(); +} + void handleSaveConfig() { saveConfig(); sendConfigJson(); @@ -1264,6 +1399,11 @@ void setup() { server.on("/relay/relay_2/off", HTTP_GET, handleGenericRelayRoute); server.on("/config", HTTP_GET, handleGetConfig); + server.on("/config/wifi", HTTP_GET, handleGetWifiConfig); + server.on("/config/wifi", HTTP_POST, handleUpdateWifiConfig); + server.on("/wifi/connect", HTTP_POST, handleWifiConnect); + server.on("/wifi/clear", HTTP_POST, handleWifiClear); + server.on("/config/device", HTTP_POST, handleUpdateDeviceConfig); server.on("/config/relay", HTTP_POST, handleUpdateRelayConfig); server.on("/config/bms", HTTP_POST, handleUpdateBmsConfig);