259 lines
8.7 KiB
C++
259 lines
8.7 KiB
C++
#include "web_server.h"
|
|
#include "web_ui.h"
|
|
#include "log/app_log.h"
|
|
#include "wifi/wifi_manager.h"
|
|
#include "version.h"
|
|
#include "ble/ble_remote_client.h"
|
|
#include "audio/audio_pipeline.h"
|
|
#include "keymap/key_state_machine.h"
|
|
#include "keymap/key_config_storage.h"
|
|
#include "nvs/nvs_manager.h"
|
|
#include <WebServer.h>
|
|
#include <ArduinoJson.h>
|
|
|
|
static WebServer s_server(80);
|
|
extern key_mapper_engine_t g_key_engine;
|
|
|
|
static void handle_root() {
|
|
s_server.sendHeader("Connection", "close");
|
|
s_server.send(200, "text/html; charset=utf-8", INDEX_HTML);
|
|
}
|
|
|
|
static void handle_status() {
|
|
JsonDocument doc;
|
|
doc["firmware"] = FIRMWARE_NAME;
|
|
doc["version"] = FIRMWARE_VERSION;
|
|
doc["uptime_sec"] = millis() / 1000;
|
|
doc["ble_state"] = (int)ble_remote_get_state();
|
|
doc["frames_decoded"] = g_audio_pipeline.total_frames_decoded;
|
|
doc["samples_pushed"] = g_audio_pipeline.total_samples_pushed;
|
|
doc["free_heap"] = ESP.getFreeHeap();
|
|
doc["free_psram"] = ESP.getFreePsram();
|
|
doc["ap_ip"] = wifi_manager_get_ap_ip();
|
|
doc["sta_ip"] = wifi_manager_get_sta_ip();
|
|
doc["sta_connected"] = wifi_manager_is_sta_connected();
|
|
doc["ap_ssid"] = AP_SSID;
|
|
String ap_pass = wifi_manager_get_ap_pass();
|
|
doc["ap_secured"] = (ap_pass.length() >= 8);
|
|
doc["ap_pass"] = ap_pass;
|
|
|
|
String out;
|
|
serializeJson(doc, out);
|
|
s_server.send(200, "application/json", out);
|
|
}
|
|
|
|
static void handle_logs() {
|
|
String json = app_log_get_json();
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_logs_clear() {
|
|
app_log_clear();
|
|
s_server.send(200, "application/json", "{\"status\":\"cleared\"}");
|
|
}
|
|
|
|
static void handle_wifi_scan() {
|
|
String json = wifi_manager_scan_json();
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_wifi_config() {
|
|
if (!s_server.hasArg("plain")) {
|
|
s_server.send(400, "application/json", "{\"error\":\"missing_body\"}");
|
|
return;
|
|
}
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, s_server.arg("plain"));
|
|
if (err) {
|
|
s_server.send(400, "application/json", "{\"error\":\"invalid_json\"}");
|
|
return;
|
|
}
|
|
|
|
String ssid = doc["ssid"] | "";
|
|
String pass = doc["pass"] | "";
|
|
|
|
if (ssid.length() == 0) {
|
|
s_server.send(400, "application/json", "{\"error\":\"empty_ssid\"}");
|
|
return;
|
|
}
|
|
|
|
wifi_manager_save_sta_config(ssid, pass);
|
|
s_server.send(200, "application/json", "{\"status\":\"ok\"}");
|
|
}
|
|
|
|
static void handle_wifi_ap_config() {
|
|
if (!s_server.hasArg("plain")) {
|
|
s_server.send(400, "application/json", "{\"error\":\"missing_body\"}");
|
|
return;
|
|
}
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, s_server.arg("plain"));
|
|
if (err) {
|
|
s_server.send(400, "application/json", "{\"error\":\"invalid_json\"}");
|
|
return;
|
|
}
|
|
|
|
String ap_pass = doc["ap_pass"] | "";
|
|
ap_pass.trim();
|
|
|
|
if (ap_pass.length() > 0 && ap_pass.length() < 8) {
|
|
s_server.send(400, "application/json", "{\"error\":\"password_too_short\",\"message\":\"AP 密码至少需要 8 位字符,或留空设置为开放热点\"}");
|
|
return;
|
|
}
|
|
|
|
if (wifi_manager_save_ap_config(ap_pass)) {
|
|
s_server.send(200, "application/json", "{\"status\":\"ok\",\"secured\":" + String(ap_pass.length() >= 8 ? "true" : "false") + "}");
|
|
} else {
|
|
s_server.send(500, "application/json", "{\"error\":\"save_failed\"}");
|
|
}
|
|
}
|
|
|
|
static void handle_keymap_get() {
|
|
String json = key_config_to_json(&g_key_engine);
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_keymap_save() {
|
|
if (!s_server.hasArg("plain")) {
|
|
s_server.send(400, "application/json", "{\"error\":\"missing_body\"}");
|
|
return;
|
|
}
|
|
bool ok = key_config_from_json(&g_key_engine, s_server.arg("plain"));
|
|
if (ok) {
|
|
key_config_storage_save(&g_key_engine);
|
|
s_server.send(200, "application/json", "{\"status\":\"saved\"}");
|
|
} else {
|
|
s_server.send(400, "application/json", "{\"error\":\"invalid_keymap_format\"}");
|
|
}
|
|
}
|
|
|
|
static void handle_keymap_reset() {
|
|
key_config_storage_reset_defaults(&g_key_engine);
|
|
app_log("KEYMAP", "Reset keymap to factory defaults via Web API");
|
|
s_server.send(200, "application/json", "{\"status\":\"reset_ok\"}");
|
|
}
|
|
|
|
static void handle_keymap_telemetry() {
|
|
String json = key_telemetry_to_json(&g_key_engine);
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_ble_scan() {
|
|
String json = ble_remote_scan_devices_json();
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_ble_connect() {
|
|
if (!s_server.hasArg("plain")) {
|
|
s_server.send(400, "application/json", "{\"error\":\"missing_body\"}");
|
|
return;
|
|
}
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, s_server.arg("plain"));
|
|
if (err) {
|
|
s_server.send(400, "application/json", "{\"error\":\"invalid_json\"}");
|
|
return;
|
|
}
|
|
|
|
String mac = doc["mac"] | "";
|
|
if (mac.length() == 0) {
|
|
s_server.send(400, "application/json", "{\"error\":\"empty_mac\"}");
|
|
return;
|
|
}
|
|
|
|
bool ok = ble_remote_connect_mac(mac);
|
|
s_server.send(200, "application/json", ok ? "{\"status\":\"connected\"}" : "{\"status\":\"failed\"}");
|
|
}
|
|
|
|
static void handle_ble_unpair() {
|
|
ble_remote_unpair();
|
|
s_server.send(200, "application/json", "{\"status\":\"unpaired\"}");
|
|
}
|
|
|
|
static void handle_ble_info() {
|
|
String json = ble_remote_get_connected_info();
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_ble_reconnect() {
|
|
app_log("BLE", "Triggered manual reconnect scan via Web API");
|
|
ble_remote_trigger_reconnect();
|
|
s_server.send(200, "application/json", "{\"status\":\"reconnecting\"}");
|
|
}
|
|
|
|
static void handle_system_restart() {
|
|
app_log("SYSTEM", "Rebooting ESP32 via Web API...");
|
|
s_server.send(200, "application/json", "{\"status\":\"rebooting\"}");
|
|
delay(500);
|
|
ESP.restart();
|
|
}
|
|
|
|
static void handle_nvs_get() {
|
|
String json = nvs_manager_dump_json();
|
|
s_server.send(200, "application/json", json);
|
|
}
|
|
|
|
static void handle_nvs_save() {
|
|
s_server.send(403, "application/json", "{\"error\":\"disabled\",\"message\":\"全量 NVS 直接回写功能已被禁用,按键配置请使用 /api/keymap/save 导入。\"}");
|
|
}
|
|
|
|
|
|
static void handle_nvs_reset() {
|
|
bool ok = nvs_manager_erase_all();
|
|
if (ok) {
|
|
s_server.send(200, "application/json", "{\"status\":\"erased\",\"message\":\"NVS已清空,系统即将重启...\"}");
|
|
delay(500);
|
|
ESP.restart();
|
|
} else {
|
|
s_server.send(500, "application/json", "{\"error\":\"erase_failed\"}");
|
|
}
|
|
}
|
|
|
|
static void handle_captive_portal() {
|
|
String host = s_server.hostHeader();
|
|
if (host != "192.168.4.1" && host != "remotemapper.local") {
|
|
s_server.sendHeader("Location", "http://192.168.4.1/", true);
|
|
s_server.send(302, "text/plain", "");
|
|
return;
|
|
}
|
|
handle_root();
|
|
}
|
|
|
|
void web_server_init(void) {
|
|
// API Routes
|
|
s_server.on("/", HTTP_GET, handle_root);
|
|
s_server.on("/api/status", HTTP_GET, handle_status);
|
|
s_server.on("/api/logs", HTTP_GET, handle_logs);
|
|
s_server.on("/api/logs/clear", HTTP_POST, handle_logs_clear);
|
|
s_server.on("/api/wifi/scan", HTTP_GET, handle_wifi_scan);
|
|
s_server.on("/api/wifi/config", HTTP_POST, handle_wifi_config);
|
|
s_server.on("/api/wifi/ap", HTTP_POST, handle_wifi_ap_config);
|
|
s_server.on("/api/keymap", HTTP_GET, handle_keymap_get);
|
|
s_server.on("/api/keymap/save", HTTP_POST, handle_keymap_save);
|
|
s_server.on("/api/keymap/reset", HTTP_POST, handle_keymap_reset);
|
|
s_server.on("/api/keymap/telemetry", HTTP_GET, handle_keymap_telemetry);
|
|
s_server.on("/api/ble/scan", HTTP_GET, handle_ble_scan);
|
|
s_server.on("/api/ble/connect", HTTP_POST, handle_ble_connect);
|
|
s_server.on("/api/ble/unpair", HTTP_POST, handle_ble_unpair);
|
|
s_server.on("/api/ble/info", HTTP_GET, handle_ble_info);
|
|
s_server.on("/api/ble/reconnect", HTTP_POST, handle_ble_reconnect);
|
|
s_server.on("/api/nvs", HTTP_GET, handle_nvs_get);
|
|
s_server.on("/api/nvs/save", HTTP_POST, handle_nvs_save);
|
|
s_server.on("/api/nvs/reset", HTTP_POST, handle_nvs_reset);
|
|
s_server.on("/api/system/restart", HTTP_POST, handle_system_restart);
|
|
|
|
// Captive Portal probe redirects
|
|
s_server.on("/generate_204", HTTP_GET, handle_captive_portal);
|
|
s_server.on("/hotspot-detect.html", HTTP_GET, handle_captive_portal);
|
|
s_server.on("/canonical.html", HTTP_GET, handle_captive_portal);
|
|
s_server.on("/connecttest.txt", HTTP_GET, handle_captive_portal);
|
|
s_server.onNotFound(handle_captive_portal);
|
|
|
|
s_server.begin();
|
|
app_log("WEB", "HTTP Web Server started on port 80");
|
|
}
|
|
|
|
void web_server_task(void) {
|
|
s_server.handleClient();
|
|
}
|