static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
static const char *ADMIN_TOKEN = "admin123";
static bool pending_reboot = false;
static unsigned long reboot_trigger_ms = 0;
static const char ADMIN_HTML[] PROGMEM = R"rawhtml(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Admin Node Console</title>
<style>
/* ... full dark-theme CSS (variables, cards, grid, modal) in the .ino ... */
</style>
</head>
<body>
<!-- header, a token input, three stat cards (Memory / System / Network),
Refresh + Restart buttons, and a confirm-reboot modal (see the .ino) -->
<script>
// Every API call carries the token typed into the page.
const getHeaders = () => ({ 'X-Admin-Token': document.getElementById('token').value });
async function loadStats() { // GET /api/sysinfo -> fill the cards
try {
const res = await fetch('/api/sysinfo', { headers: getHeaders() });
if (res.status === 401) { alert('Unauthorized: Invalid Access Token!'); return; }
const data = await res.json();
document.getElementById('free-heap').innerText = Math.round(data.free_heap / 1024) + ' KB';
/* ...the rest of the fields are filled the same way... */
} catch (err) { console.error('Failed to load system stats', err); }
}
async function triggerReboot() { // POST /api/restart
closeRebootModal();
try {
const res = await fetch('/api/restart', { method: 'POST', headers: getHeaders() });
if (res.status === 200) { alert('Reboot accepted. Reconnecting...'); setTimeout(() => location.reload(), 5000); }
else { alert('Error: Reboot rejected by node.'); }
} catch (err) { alert('Connection lost. Node is rebooting...'); }
}
window.onload = loadStats;
</script>
</body>
</html>
)rawhtml";
const char *get_reset_reason_string(esp_reset_reason_t reason)
{
switch (reason)
{
case ESP_RST_POWERON: return "Power On";
case ESP_RST_EXT: return "External Pin Reset";
case ESP_RST_SW: return "Software Reboot";
case ESP_RST_PANIC: return "Software Panic / Crash";
case ESP_RST_INT_WDT: return "Interrupt Watchdog";
case ESP_RST_TASK_WDT: return "Task Watchdog";
case ESP_RST_WDT: return "Generic Watchdog";
case ESP_RST_DEEPSLEEP: return "Deep Sleep Exit";
case ESP_RST_BROWNOUT: return "Brownout Event";
case ESP_RST_SDIO: return "SDIO Interface Reset";
default: return "Unknown";
}
}
bool verify_admin_privileges(
const HttpReq *req)
{
return (token && strcmp(token, ADMIN_TOKEN) == 0);
}
void handle_serve_dashboard(uint8_t slot_id,
HttpReq *req)
{
server.
send(slot_id, 200,
"text/html", ADMIN_HTML);
}
void handle_get_sysinfo(uint8_t slot_id,
HttpReq *req)
{
if (!verify_admin_privileges(req))
{
server.
send(slot_id, 401,
"application/json",
"{\"error\":\"Unauthorized\"}");
return;
}
char response_buf[384];
snprintf(response_buf, sizeof(response_buf),
"{"
"\"free_heap\":%u,"
"\"min_free_heap\":%u,"
"\"max_alloc_heap\":%u,"
"\"uptime_ms\":%lu,"
"\"reset_reason\":\"%s\","
"\"chip_revision\":%d,"
"\"cpu_freq_mhz\":%d,"
"\"wifi_rssi\":%d,"
"\"wifi_ssid\":\"%s\","
"\"wifi_channel\":%d,"
"\"ip_address\":\"%s\""
"}",
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(), millis(),
get_reset_reason_string(esp_reset_reason()), ESP.getChipRevision(), ESP.getCpuFreqMHz(),
pc_net_rssi(),
server.
send(slot_id, 200,
"application/json", response_buf);
}
void handle_post_restart(uint8_t slot_id,
HttpReq *req)
{
if (!verify_admin_privileges(req))
{
server.
send(slot_id, 401,
"application/json",
"{\"error\":\"Unauthorized\"}");
return;
}
server.
send(slot_id, 200,
"application/json",
"{\"status\":\"rebooting\"}");
pending_reboot = true;
reboot_trigger_ms = millis();
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("\n--- PC SysAdmin Control Console ---");
{
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Online!");
Serial.print("Access the dashboard via: http://");
Serial.printf("IP: %u.%u.%u.%u\n", (unsigned)(ip & 0xFF), (unsigned)((ip >> 8) & 0xFF),
(unsigned)((ip >> 16) & 0xFF), (unsigned)((ip >> 24) & 0xFF));
int32_t result = server.
begin(80);
if (result < 0)
{
Serial.printf("begin() failed (error %d)\n", result);
return;
}
Serial.println("SysAdmin console initialized on port 80");
}
void loop()
{
if (pending_reboot && (millis() - reboot_trigger_ms >= 1000))
{
Serial.println("Reboot sequence triggered. Rebooting now!");
delay(100);
ESP.restart();
}
}
Single-port HTTP server with deterministic, zero-allocation execution.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
void set_cors(const char *origin)
Enable CORS by pre-building the Access-Control headers.
bool init_wifi_physical(const char *, const char *)
Connect to a WiFi access point.
uint32_t pc_net_egress_ip(void)
IPv4 (network byte order) of the current egress interface, or 0 if none.
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
uint8_t pc_net_channel(void)
Station WiFi channel (1..14), or 0 if not associated (and on host builds).
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Layer 7 (Application) - public HTTP routing API.
@ HTTP_POST
Non-idempotent create / action.
@ HTTP_GET
Safe, idempotent read.