Initial Commit

This commit is contained in:
Joshua King
2026-02-09 11:41:12 -05:00
commit 558c209b6c
31 changed files with 1399 additions and 0 deletions

133
src/net/WebUI.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include "WebUI.h"
#include "../util/Version.h"
#include <ArduinoJson.h>
void WebUI::begin(Settings& settings,
WiFiManager& wifi,
MoistureSensor& moisture,
FaceRenderer& face,
WebhookService& webhook,
unsigned long bootMs) {
auto sendPortalRedirect = [&]() {
String target = String("http://") + wifi.apIp().toString() + "/";
_server.sendHeader("Location", target, true);
_server.send(302, "text/plain", "");
};
_server.on("/", HTTP_GET, [&]() {
String mode = (wifi.mode() == NET_AP_SETUP) ? "Setup AP" : "Station";
String wifiSsid = settings.wifiSsid();
String currentSsid = wifi.ssid();
String page =
"<h2>FacePlant</h2>"
"<p>Firmware v" + String(PB_VERSION) + "</p>"
"<h3>Wi-Fi</h3>"
"<p>Mode: " + mode + "</p>"
"<p>Connected SSID: " + (currentSsid.length() ? currentSsid : "(not connected)") + "</p>"
"<p>Saved SSID: " + (wifiSsid.length() ? wifiSsid : "(none)") + "</p>"
"<p>Setup AP: " + String(wifi.setupSsid()) + " / " + wifi.apIp().toString() + "</p>"
"<form method='POST' action='/wifi'>"
"<label>SSID</label><br>"
"<input name='ssid' size='30' value='" + wifiSsid + "'><br>"
"<label>Password</label><br>"
"<input type='password' name='pass' size='30' value=''><br>"
"<small>Leave password blank to keep saved password for the same SSID.</small><br><br>"
"<button type='submit' name='action' value='connect'>Connect Wi-Fi</button> "
"<button type='submit' name='action' value='forget'>Forget Wi-Fi</button>"
"</form><br>"
"<form method='POST' action='/config'>"
"<label>Plant profile</label><br>"
"<select name='plant'>"
"<option value='house'>Houseplant</option>"
"<option value='succulent'>Succulent</option>"
"<option value='herbs'>Herbs</option>"
"<option value='fern'>Fern</option>"
"<option value='tropical'>Tropical</option>"
"</select><br><br>"
"<label><input type='checkbox' name='kids' " + String(settings.kidsMode() ? "checked" : "") + "> Kids Mode</label><br><br>"
"<label>Webhook URL</label><br>"
"<input name='wh' size='40' value='" + settings.webhookUrl() + "'><br>"
"<label><input type='checkbox' name='wh_en' " + String(settings.webhookEnabled() ? "checked" : "") + "> Enable webhook</label><br><br>"
"<button type='submit'>Save</button>"
"</form>"
"<p><a href='/status'>Status (JSON)</a></p>"
"<p><a href='/update'>OTA Update</a></p>";
_server.send(200, "text/html", page);
});
_server.on("/wifi", HTTP_POST, [&]() {
String action = _server.hasArg("action") ? _server.arg("action") : "connect";
if (action == "forget") {
wifi.clearAndStartSetupAP();
} else {
String ssid = _server.hasArg("ssid") ? _server.arg("ssid") : settings.wifiSsid();
String pass = "";
if (_server.hasArg("pass") && _server.arg("pass").length() > 0) {
pass = _server.arg("pass");
} else if (ssid == settings.wifiSsid()) {
pass = settings.wifiPass();
}
if (ssid.length() > 0) wifi.saveAndConnect(ssid, pass);
}
_server.sendHeader("Location", "/");
_server.send(303);
});
_server.on("/config", HTTP_POST, [&]() {
if (_server.hasArg("plant")) settings.setPlantProfile(_server.arg("plant"));
settings.setKidsMode(_server.hasArg("kids"));
settings.setWebhookEnabled(_server.hasArg("wh_en"));
if (_server.hasArg("wh")) settings.setWebhookUrl(_server.arg("wh"));
_server.sendHeader("Location", "/");
_server.send(303);
});
_server.on("/status", HTTP_GET, [&]() {
JsonDocument doc;
doc["device"] = "FacePlant";
doc["version"] = PB_VERSION;
doc["net_mode"] = (wifi.mode() == NET_AP_SETUP) ? "setup_ap" : "sta";
doc["setup_ssid"] = wifi.setupSsid();
doc["ip"] = wifi.ip().toString();
doc["ssid"] = wifi.ssid();
doc["saved_ssid"] = settings.wifiSsid();
doc["wifi_connected"] = wifi.connected();
doc["moisture_pct"] = moisture.percent();
doc["raw"] = moisture.raw();
doc["kids_mode"] = settings.kidsMode();
doc["dead_mode"] = face.isDeadMode();
doc["uptime_ms"] = millis() - bootMs;
String out;
serializeJson(doc, out);
_server.send(200, "application/json", out);
});
// Captive portal compatibility endpoints used by common OS network checkers.
_server.on("/generate_204", HTTP_GET, [&]() { sendPortalRedirect(); }); // Android
_server.on("/gen_204", HTTP_GET, [&]() { sendPortalRedirect(); }); // Android alt
_server.on("/hotspot-detect.html", HTTP_GET, [&]() { sendPortalRedirect(); }); // iOS/macOS
_server.on("/library/test/success.html", HTTP_GET, [&]() { sendPortalRedirect(); });
_server.on("/ncsi.txt", HTTP_GET, [&]() { sendPortalRedirect(); }); // Windows
_server.on("/connecttest.txt", HTTP_GET, [&]() { sendPortalRedirect(); }); // Windows alt
_server.on("/fwlink", HTTP_GET, [&]() { sendPortalRedirect(); }); // Windows alt
_server.onNotFound([&]() {
if (wifi.mode() == NET_AP_SETUP) {
sendPortalRedirect();
return;
}
_server.send(404, "text/plain", "Not found");
});
_server.begin();
}
void WebUI::loop() {
_server.handleClient();
}