added webserver

This commit is contained in:
2026-04-20 16:40:49 -04:00
parent d938069aa6
commit b5412bf388
2 changed files with 314 additions and 42 deletions

259
vtech.org
View File

@@ -18624,3 +18624,262 @@ void loop() {
try hardcoded station connect try hardcoded station connect
if fail, start softap + web portal if fail, start softap + web portal
from portal, optionally save new ssid/pwd and switch to station mode from portal, optionally save new ssid/pwd and switch to station mode
* webserver
#+begin_src c++
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <driver/touch_pad.h>
Preferences prefs;
WebServer server(80);
// touch pad used as boot override / setup trigger
const touch_pad_t touch_pin = TOUCH_PAD_NUM4; // gpio13
const int touch_threshold = 500;
// softap fallback
const char* ap_ssid = "baby-monitor-setup";
const char* ap_password = "setup1234";
// saved / editable config
String wifi_ssid = "";
String wifi_password = "";
String dest_ip_str = "192.168.1.190";
uint16_t udp_port = 5004;
bool config_mode = false;
const unsigned long wifi_connect_timeout_ms = 15000;
String html_escape(const String& s) {
String out = s;
out.replace("&", "&amp;");
out.replace("<", "&lt;");
out.replace(">", "&gt;");
out.replace("\"", "&quot;");
return out;
}
void load_config() {
prefs.begin("bmcfg", true);
wifi_ssid = prefs.getString("ssid", "");
wifi_password = prefs.getString("pwd", "");
dest_ip_str = prefs.getString("destip", "192.168.1.190");
udp_port = prefs.getUShort("port", 5004);
prefs.end();
}
void save_config(const String& ssid, const String& pwd, const String& dest_ip, uint16_t port) {
prefs.begin("bmcfg", false);
prefs.putString("ssid", ssid);
prefs.putString("pwd", pwd);
prefs.putString("destip", dest_ip);
prefs.putUShort("port", port);
prefs.end();
}
void clear_config() {
prefs.begin("bmcfg", false);
prefs.clear();
prefs.end();
}
bool force_setup_requested() {
// give touch pad a moment to settle
delay(100);
uint16_t touch_val = 0;
touch_pad_read(touch_pin, &touch_val);
Serial.printf("touch boot check: %u\n", touch_val);
return touch_val < touch_threshold;
}
void handle_root() {
String page =
"<!doctype html>"
"<html><head>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<title>baby monitor setup</title>"
"<style>"
"body{font-family:sans-serif;max-width:520px;margin:2rem auto;padding:0 1rem;}"
"input{width:100%;padding:.6rem;margin:.2rem 0 1rem 0;box-sizing:border-box;}"
"button{padding:.7rem 1rem;margin-right:.5rem;}"
"code{background:#eee;padding:.1rem .3rem;}"
"</style>"
"</head><body>"
"<h2>baby monitor setup</h2>"
"<p>connect to ap <code>" + String(ap_ssid) + "</code> then browse to <code>192.168.4.1</code>.</p>"
"<form method='post' action='/save'>"
"<label>wifi ssid</label>"
"<input name='ssid' value='" + html_escape(wifi_ssid) + "'>"
"<label>wifi password</label>"
"<input name='pwd' type='password' value='" + html_escape(wifi_password) + "'>"
"<label>destination ip</label>"
"<input name='destip' value='" + html_escape(dest_ip_str) + "'>"
"<label>udp port</label>"
"<input name='port' value='" + String(udp_port) + "'>"
"<button type='submit'>save and reboot</button>"
"</form>"
"<form method='post' action='/clear' onsubmit='return confirm(\"clear saved config?\");'>"
"<button type='submit'>clear saved config</button>"
"</form>"
"<p><a href='/status'>status json</a></p>"
"</body></html>";
server.send(200, "text/html", page);
}
void handle_status() {
String json = "{";
json += "\"config_mode\":" + String(config_mode ? "true" : "false") + ",";
json += "\"ap_ssid\":\"" + String(ap_ssid) + "\",";
json += "\"saved_ssid\":\"" + html_escape(wifi_ssid) + "\",";
json += "\"dest_ip\":\"" + html_escape(dest_ip_str) + "\",";
json += "\"udp_port\":" + String(udp_port);
json += "}";
server.send(200, "application/json", json);
}
bool valid_ipv4(const String& s) {
IPAddress ip;
return ip.fromString(s);
}
void handle_save() {
String new_ssid = server.arg("ssid");
String new_pwd = server.arg("pwd");
String new_dest = server.arg("destip");
uint16_t new_port = (uint16_t)server.arg("port").toInt();
if (new_ssid.length() == 0) {
server.send(400, "text/plain", "ssid required");
return;
}
if (!valid_ipv4(new_dest)) {
server.send(400, "text/plain", "invalid destination ip");
return;
}
if (new_port == 0) {
server.send(400, "text/plain", "invalid udp port");
return;
}
save_config(new_ssid, new_pwd, new_dest, new_port);
server.send(200, "text/html",
"<html><body><h3>saved. rebooting...</h3></body></html>");
delay(1000);
esp_restart();
}
void handle_clear() {
clear_config();
server.send(200, "text/html",
"<html><body><h3>config cleared. rebooting...</h3></body></html>");
delay(1000);
esp_restart();
}
void start_config_ap() {
config_mode = true;
WiFi.disconnect(true, true);
delay(250);
WiFi.mode(WIFI_AP);
WiFi.softAP(ap_ssid, ap_password);
IPAddress ip = WiFi.softAPIP();
Serial.printf("config ap active\n");
Serial.printf(" ssid: %s\n", ap_ssid);
Serial.printf(" pass: %s\n", ap_password);
Serial.printf(" ip: %s\n", ip.toString().c_str());
server.on("/", HTTP_GET, handle_root);
server.on("/save", HTTP_POST, handle_save);
server.on("/clear", HTTP_POST, handle_clear);
server.on("/status", HTTP_GET, handle_status);
server.begin();
}
bool try_connect_wifi() {
if (wifi_ssid.length() == 0) {
Serial.println("no saved wifi ssid");
return false;
}
WiFi.disconnect(true, true);
delay(250);
WiFi.mode(WIFI_STA);
WiFi.begin(wifi_ssid.c_str(), wifi_password.c_str());
Serial.printf("connecting to wifi: %s\n", wifi_ssid.c_str());
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED &&
millis() - start < wifi_connect_timeout_ms) {
delay(500);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("connected\n");
Serial.printf(" ip: %s\n", WiFi.localIP().toString().c_str());
Serial.printf(" dest ip: %s\n", dest_ip_str.c_str());
Serial.printf(" udp port: %u\n", udp_port);
return true;
}
Serial.println("wifi connect failed");
return false;
}
void setup() {
Serial.begin(115200);
delay(200);
touch_pad_init();
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(touch_pin, 0);
load_config();
if (force_setup_requested()) {
Serial.println("force setup mode requested");
start_config_ap();
return;
}
if (!try_connect_wifi()) {
start_config_ap();
}
}
void loop() {
if (config_mode) {
server.handleClient();
delay(2);
return;
}
// placeholder for later normal-mode behavior
static unsigned long last_print = 0;
if (millis() - last_print > 5000) {
Serial.printf("normal mode alive. wifi=%s ip=%s dest=%s:%u\n",
wifi_ssid.c_str(),
WiFi.localIP().toString().c_str(),
dest_ip_str.c_str(),
udp_port);
last_print = millis();
}
delay(10);
}
#+end_src

View File

@@ -1,27 +1,29 @@
#include <WiFi.h> #include <WiFi.h>
#include <WebServer.h> #include <WebServer.h>
#include <Preferences.h> #include <Preferences.h>
#include <driver/touch_pad.h>
preferences prefs; Preferences prefs;
webserver server(80); WebServer server(80);
// touch pad used as boot override / setup trigger // dedicated reset/setup button: wire gpio27 -> momentary button -> gnd
const touch_pad_t touch_pin = TOUCH_PAD_NUM4; // gpio13 const int RESET_PIN = 27;
const int touch_threshold = 500; const unsigned long RESET_HOLD_MS = 1000;
// softap fallback // softap fallback
const char* ap_ssid = "baby-monitor-setup"; const char* AP_SSID = "baby-monitor-setup";
const char* ap_password = "setup1234"; const char* AP_PASSWORD = "setup1234";
// saved / editable config // saved / editable config
String wifi_ssid = ""; String wifi_ssid = "";
String wifi_password = ""; String wifi_password = "";
String dest_ip_str = "192.168.1.190"; String dest_ip_str = "192.168.1.190";
uint16_t udp_port = 5004; uint16_t udp_port = 5004;
c:\Users\moses\proj\vtech\mic\mic.ino
bool config_mode = false; bool config_mode = false;
const unsigned long wifi_connect_timeout_ms = 15000; const unsigned long WIFI_CONNECT_TIMEOUT_MS = 15000;
unsigned long reset_press_start = 0;
bool reset_armed = false;
String html_escape(const String& s) { String html_escape(const String& s) {
String out = s; String out = s;
@@ -56,15 +58,9 @@ void clear_config() {
prefs.end(); prefs.end();
} }
bool force_setup_requested() { bool valid_ipv4(const String& s) {
// give touch pad a moment to settle IPAddress ip;
delay(100); return ip.fromString(s);
uint16_t touch_val = 0;
touch_pad_read(touch_pin, &touch_val);
Serial.printf("touch boot check: %u\n", touch_val);
return touch_val < touch_threshold;
} }
void handle_root() { void handle_root() {
@@ -81,7 +77,7 @@ void handle_root() {
"</style>" "</style>"
"</head><body>" "</head><body>"
"<h2>baby monitor setup</h2>" "<h2>baby monitor setup</h2>"
"<p>connect to ap <code>" + String(ap_ssid) + "</code> then browse to <code>192.168.4.1</code>.</p>" "<p>connect to ap <code>" + String(AP_SSID) + "</code> then browse to <code>192.168.4.1</code>.</p>"
"<form method='post' action='/save'>" "<form method='post' action='/save'>"
"<label>wifi ssid</label>" "<label>wifi ssid</label>"
"<input name='ssid' value='" + html_escape(wifi_ssid) + "'>" "<input name='ssid' value='" + html_escape(wifi_ssid) + "'>"
@@ -96,7 +92,7 @@ void handle_root() {
"<form method='post' action='/clear' onsubmit='return confirm(\"clear saved config?\");'>" "<form method='post' action='/clear' onsubmit='return confirm(\"clear saved config?\");'>"
"<button type='submit'>clear saved config</button>" "<button type='submit'>clear saved config</button>"
"</form>" "</form>"
"<p><a href='/status'>status json</a></p>" "<p><a href='/status'>view status json</a></p>"
"</body></html>"; "</body></html>";
server.send(200, "text/html", page); server.send(200, "text/html", page);
@@ -105,7 +101,7 @@ void handle_root() {
void handle_status() { void handle_status() {
String json = "{"; String json = "{";
json += "\"config_mode\":" + String(config_mode ? "true" : "false") + ","; json += "\"config_mode\":" + String(config_mode ? "true" : "false") + ",";
json += "\"ap_ssid\":\"" + String(ap_ssid) + "\","; json += "\"ap_ssid\":\"" + String(AP_SSID) + "\",";
json += "\"saved_ssid\":\"" + html_escape(wifi_ssid) + "\","; json += "\"saved_ssid\":\"" + html_escape(wifi_ssid) + "\",";
json += "\"dest_ip\":\"" + html_escape(dest_ip_str) + "\","; json += "\"dest_ip\":\"" + html_escape(dest_ip_str) + "\",";
json += "\"udp_port\":" + String(udp_port); json += "\"udp_port\":" + String(udp_port);
@@ -114,9 +110,11 @@ void handle_status() {
server.send(200, "application/json", json); server.send(200, "application/json", json);
} }
bool valid_ipv4(const String& s) { void reboot_to_setup_mode() {
ipaddress ip; Serial.println("clearing config and rebooting to setup mode...");
return ip.fromString(s); clear_config();
delay(250);
ESP.restart();
} }
void handle_save() { void handle_save() {
@@ -145,7 +143,7 @@ void handle_save() {
server.send(200, "text/html", server.send(200, "text/html",
"<html><body><h3>saved. rebooting...</h3></body></html>"); "<html><body><h3>saved. rebooting...</h3></body></html>");
delay(1000); delay(1000);
esp_restart(); ESP.restart();
} }
void handle_clear() { void handle_clear() {
@@ -153,7 +151,7 @@ void handle_clear() {
server.send(200, "text/html", server.send(200, "text/html",
"<html><body><h3>config cleared. rebooting...</h3></body></html>"); "<html><body><h3>config cleared. rebooting...</h3></body></html>");
delay(1000); delay(1000);
esp_restart(); ESP.restart();
} }
void start_config_ap() { void start_config_ap() {
@@ -163,12 +161,12 @@ void start_config_ap() {
delay(250); delay(250);
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
WiFi.softAP(ap_ssid, ap_password); WiFi.softAP(AP_SSID, AP_PASSWORD);
IPAddress ip = WiFi.softAPIP(); IPAddress ip = WiFi.softAPIP();
Serial.printf("config ap active\n"); Serial.println("config ap active");
Serial.printf(" ssid: %s\n", ap_ssid); Serial.printf(" ssid: %s\n", AP_SSID);
Serial.printf(" pass: %s\n", ap_password); Serial.printf(" pass: %s\n", AP_PASSWORD);
Serial.printf(" ip: %s\n", ip.toString().c_str()); Serial.printf(" ip: %s\n", ip.toString().c_str());
server.on("/", HTTP_GET, handle_root); server.on("/", HTTP_GET, handle_root);
@@ -194,14 +192,14 @@ bool try_connect_wifi() {
unsigned long start = millis(); unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && while (WiFi.status() != WL_CONNECTED &&
millis() - start < wifi_connect_timeout_ms) { millis() - start < WIFI_CONNECT_TIMEOUT_MS) {
delay(500); delay(500);
Serial.print("."); Serial.print(".");
} }
Serial.println(); Serial.println();
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
Serial.printf("connected\n"); Serial.println("connected");
Serial.printf(" ip: %s\n", WiFi.localIP().toString().c_str()); Serial.printf(" ip: %s\n", WiFi.localIP().toString().c_str());
Serial.printf(" dest ip: %s\n", dest_ip_str.c_str()); Serial.printf(" dest ip: %s\n", dest_ip_str.c_str());
Serial.printf(" udp port: %u\n", udp_port); Serial.printf(" udp port: %u\n", udp_port);
@@ -212,28 +210,43 @@ bool try_connect_wifi() {
return false; return false;
} }
void check_reset_button() {
bool pressed = (digitalRead(RESET_PIN) == LOW);
if (pressed) {
if (!reset_armed) {
reset_armed = true;
reset_press_start = millis();
Serial.println("reset button pressed...");
} else if (millis() - reset_press_start >= RESET_HOLD_MS) {
Serial.println("reset hold met");
reboot_to_setup_mode();
}
} else {
if (reset_armed) {
Serial.println("reset button released");
}
reset_armed = false;
reset_press_start = 0;
}
}
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
delay(200); delay(200);
touch_pad_init(); pinMode(RESET_PIN, INPUT_PULLUP);
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(touch_pin, 0);
load_config(); load_config();
if (force_setup_requested()) {
Serial.println("force setup mode requested");
start_config_ap();
return;
}
if (!try_connect_wifi()) { if (!try_connect_wifi()) {
start_config_ap(); start_config_ap();
} }
} }
void loop() { void loop() {
check_reset_button();
if (config_mode) { if (config_mode) {
server.handleClient(); server.handleClient();
delay(2); delay(2);