wifi: add WPA2 AP setup API and pairing roadmap

This commit is contained in:
2026-06-07 22:11:40 -06:00
parent 594edc3dd9
commit b665bf1c82
5 changed files with 289 additions and 7 deletions
@@ -2,6 +2,7 @@
#include <WebServer.h>
#include <Preferences.h>
#include <esp_wifi.h>
#include <esp_system.h>
#include <ArduinoJson.h>
#include "config.h"
@@ -781,6 +782,13 @@ HardwareSerial DashboardSerial(2);
String uartLineBuffer;
Preferences wifiPrefs;
Preferences apPrefs;
String apSsid = "OverlandController";
String apPassword = "";
const int AP_PASSWORD_MIN_LEN = 8;
const int AP_PASSWORD_MAX_LEN = 63;
const int MAX_WIFI_NETWORKS = 3;
String staSsids[MAX_WIFI_NETWORKS];
@@ -854,6 +862,147 @@ void clearWifiConfig() {
}
}
String generateDefaultApPassword() {
uint64_t mac = ESP.getEfuseMac();
uint32_t a = esp_random();
uint32_t b = ((uint32_t)(mac >> 16)) ^ esp_random();
char buffer[17];
snprintf(buffer, sizeof(buffer), "%08lX%08lX", (unsigned long)a, (unsigned long)b);
return String(buffer);
}
bool validApSsid(const String& ssid) {
return ssid.length() > 0 && ssid.length() <= 32;
}
bool validApPassword(const String& password) {
return password.length() >= AP_PASSWORD_MIN_LEN && password.length() <= AP_PASSWORD_MAX_LEN;
}
void loadApConfig() {
apPrefs.begin("ap", false);
apSsid = apPrefs.getString("ssid", "OverlandController");
apPassword = apPrefs.getString("password", "");
if (!validApSsid(apSsid)) {
apSsid = "OverlandController";
apPrefs.putString("ssid", apSsid);
}
if (!validApPassword(apPassword)) {
apPassword = generateDefaultApPassword();
apPrefs.putString("password", apPassword);
}
apPrefs.end();
}
void saveApConfig(const String& ssid, const String& password) {
apSsid = ssid;
apPassword = password;
apPrefs.begin("ap", false);
apPrefs.putString("ssid", apSsid);
apPrefs.putString("password", apPassword);
apPrefs.end();
}
bool startAccessPoint() {
bool ok = WiFi.softAP(apSsid.c_str(), apPassword.c_str());
if (ok) {
Serial.println("AP Started");
Serial.print("AP SSID: ");
Serial.println(apSsid);
Serial.print("AP Password: ");
Serial.println(apPassword);
Serial.print("AP IP: ");
Serial.println(WiFi.softAPIP());
} else {
Serial.println("AP failed to start");
}
return ok;
}
void restartAccessPoint() {
WiFi.softAPdisconnect(true);
delay(250);
startAccessPoint();
}
void sendSimpleJsonError(int status, const char* error, const char* detail) {
DynamicJsonDocument doc(512);
doc["ok"] = false;
doc["error"] = error;
if (detail && detail[0]) {
JsonArray details = doc.createNestedArray("details");
details.add(detail);
}
String output;
serializeJson(doc, output);
server.send(status, "application/json", output);
}
void handleGetApConfig() {
DynamicJsonDocument response(512);
response["ok"] = true;
response["ssid"] = apSsid;
response["password_set"] = validApPassword(apPassword);
response["password_min_length"] = AP_PASSWORD_MIN_LEN;
response["password_max_length"] = AP_PASSWORD_MAX_LEN;
response["auth"] = "wpa2";
String output;
serializeJson(response, output);
server.send(200, "application/json", output);
}
void handleUpdateApConfig() {
DynamicJsonDocument doc(512);
DeserializationError err = deserializeJson(doc, server.arg("plain"));
if (err) {
sendSimpleJsonError(400, "invalid_json", err.c_str());
return;
}
String ssid = doc["ssid"] | apSsid;
String password = doc["password"] | "";
ssid.trim();
password.trim();
if (!validApSsid(ssid)) {
sendSimpleJsonError(400, "invalid_ap_ssid", "AP SSID must be 1-32 characters.");
return;
}
if (!validApPassword(password)) {
sendSimpleJsonError(400, "invalid_ap_password", "AP password must be 8-63 characters.");
return;
}
saveApConfig(ssid, password);
restartAccessPoint();
DynamicJsonDocument response(512);
response["ok"] = true;
response["ssid"] = apSsid;
response["password_set"] = true;
response["auth"] = "wpa2";
response["ap_ip"] = WiFi.softAPIP().toString();
String output;
serializeJson(response, output);
server.send(200, "application/json", output);
}
int findNextWifiIndexByPriority(bool tried[]) {
int bestIndex = -1;
int bestPriority = 1000000;
@@ -1327,6 +1476,8 @@ void buildStatusDocument(JsonDocument& doc) {
network["wifi_enabled"] = true;
network["uart_connected"] = true;
network["ap_enabled"] = true;
network["ap_ssid"] = apSsid;
network["ap_auth"] = "wpa2";
network["ap_ip"] = WiFi.softAPIP().toString();
network["sta_enabled"] = wifiNetworkCount > 0;
network["sta_connected"] = WiFi.status() == WL_CONNECTED;
@@ -2229,6 +2380,8 @@ void handleHealth() {
JsonObject network = doc.createNestedObject("network");
network["ap_enabled"] = true;
network["ap_ssid"] = apSsid;
network["ap_auth"] = "wpa2";
network["ap_ip"] = WiFi.softAPIP().toString();
network["sta_enabled"] = wifiNetworkCount > 0;
network["sta_connected"] = WiFi.status() == WL_CONNECTED;
@@ -2836,15 +2989,10 @@ void setup() {
initBms();
loadWifiConfig();
loadApConfig();
WiFi.mode(WIFI_AP_STA);
bool apResult = WiFi.softAP("OverlandController");
if (apResult) {
Serial.println("AP Started");
Serial.print("AP IP: ");
Serial.println(WiFi.softAPIP());
}
startAccessPoint();
connectStaWifi();
@@ -2861,6 +3009,8 @@ void setup() {
server.on(API_V1("/config/export"), HTTP_GET, handleExportConfig);
server.on(API_V1("/config/import"), HTTP_POST, handleImportConfig);
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_POST, handleUpdateApConfig);
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);