#ifdef WITH_MQTT #include "WebConfig.h" #include "web_ui.h" #include WebConfig::WebConfig(WiFiManager& wifi, MQTTBridge& mqtt, uint16_t port) : _server(port), _wifi(wifi), _mqtt(mqtt), _wifi_config(nullptr), _mqtt_config(nullptr), _prefs(nullptr), _node_name(nullptr), _firmware_version(nullptr), _save_callback(nullptr), _reboot_callback(nullptr), _initialized(false) { memset(&_stats, 0, sizeof(_stats)); memset(_node_id, 0, sizeof(_node_id)); } void WebConfig::begin(WiFiConfig* wifi_config, MQTTConfig* mqtt_config, NodePrefs* prefs) { _wifi_config = wifi_config; _mqtt_config = mqtt_config; _prefs = prefs; setupRoutes(); _server.begin(); _initialized = true; Serial.printf("[WebConfig] Server started on port 80\n"); } void WebConfig::setNodeInfo(const uint8_t* pubkey, const char* name, const char* version) { for (int i = 0; i < 8; i++) { sprintf(&_node_id[i * 2], "%02x", pubkey[i]); } _node_name = name; _firmware_version = version; } void WebConfig::updateStats(const WebConfigStats& stats) { _stats = stats; } void WebConfig::end() { if (_initialized) { _server.end(); _initialized = false; } } void WebConfig::setupRoutes() { // Serve main page _server.on("/", HTTP_GET, [this](AsyncWebServerRequest* request) { handleRoot(request); }); // API endpoints _server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest* request) { handleStatus(request); }); _server.on("/api/wifi", HTTP_GET, [this](AsyncWebServerRequest* request) { handleWiFiGet(request); }); // WiFi POST - use body handler for JSON _server.on("/api/wifi", HTTP_POST, // Request handler (called after body is received) [this](AsyncWebServerRequest* request) {}, // Upload handler (not used) nullptr, // Body handler [this](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { if (index == 0 && len == total) { // Complete body received JsonDocument doc; DeserializationError error = deserializeJson(doc, data, len); if (error) { sendErrorResponse(request, "Invalid JSON"); return; } if (doc["ssid"].is()) { strncpy(_wifi_config->ssid, doc["ssid"].as(), sizeof(_wifi_config->ssid) - 1); } if (doc["password"].is()) { strncpy(_wifi_config->password, doc["password"].as(), sizeof(_wifi_config->password) - 1); } if (_save_callback) _save_callback(); JsonDocument resp; resp["success"] = true; resp["message"] = "WiFi config saved. Reconnecting..."; sendJsonResponse(request, resp); // Schedule reconnect after response _wifi.reconnect(); } } ); _server.on("/api/mqtt", HTTP_GET, [this](AsyncWebServerRequest* request) { handleMQTTGet(request); }); // MQTT POST - use body handler for JSON _server.on("/api/mqtt", HTTP_POST, // Request handler [this](AsyncWebServerRequest* request) {}, // Upload handler nullptr, // Body handler [this](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { if (index == 0 && len == total) { JsonDocument doc; DeserializationError error = deserializeJson(doc, data, len); if (error) { sendErrorResponse(request, "Invalid JSON"); return; } if (doc["enabled"].is()) { _mqtt_config->enabled = doc["enabled"].as() ? 1 : 0; } if (doc["broker"].is()) { strncpy(_mqtt_config->broker, doc["broker"].as(), sizeof(_mqtt_config->broker) - 1); } if (doc["port"].is()) { _mqtt_config->port = doc["port"].as(); } if (doc["user"].is()) { strncpy(_mqtt_config->user, doc["user"].as(), sizeof(_mqtt_config->user) - 1); } if (doc["password"].is()) { strncpy(_mqtt_config->password, doc["password"].as(), sizeof(_mqtt_config->password) - 1); } if (doc["topic_prefix"].is()) { strncpy(_mqtt_config->topic_prefix, doc["topic_prefix"].as(), sizeof(_mqtt_config->topic_prefix) - 1); } if (_save_callback) _save_callback(); // Update MQTT bridge config _mqtt.updateConfig(*_mqtt_config); JsonDocument resp; resp["success"] = true; resp["message"] = "MQTT config saved"; sendJsonResponse(request, resp); } } ); _server.on("/api/reboot", HTTP_POST, [this](AsyncWebServerRequest* request) { handleReboot(request); }); // Handle 404 _server.onNotFound([this](AsyncWebServerRequest* request) { handleNotFound(request); }); } void WebConfig::handleRoot(AsyncWebServerRequest* request) { request->send_P(200, "text/html", WEB_UI_HTML); } void WebConfig::handleStatus(AsyncWebServerRequest* request) { JsonDocument doc; // Node info doc["node_id"] = _node_id; doc["node_name"] = _node_name ? _node_name : "MeshCore"; doc["firmware"] = _firmware_version ? _firmware_version : "unknown"; // WiFi status JsonObject wifi = doc["wifi"].to(); wifi["connected"] = _wifi.isConnected(); wifi["ap_mode"] = _wifi.isAPMode(); wifi["ip"] = _wifi.getLocalIP().toString(); wifi["rssi"] = _wifi.getRSSI(); wifi["ssid"] = _wifi.getSSID(); wifi["mac"] = _wifi.getMacAddress(); // MQTT status JsonObject mqtt = doc["mqtt"].to(); mqtt["connected"] = _mqtt.isConnected(); mqtt["enabled"] = _mqtt.isEnabled(); mqtt["broker"] = _mqtt.getBroker(); mqtt["port"] = _mqtt.getPort(); mqtt["msgs_sent"] = _mqtt.getMessagesSent(); mqtt["msgs_recv"] = _mqtt.getMessagesReceived(); // Mesh stats JsonObject mesh = doc["mesh"].to(); mesh["uptime_secs"] = _stats.uptime_secs; mesh["packets_rx"] = _stats.packets_rx; mesh["packets_tx"] = _stats.packets_tx; mesh["air_time_secs"] = _stats.air_time_secs; mesh["noise_floor"] = _stats.noise_floor; mesh["last_rssi"] = _stats.last_rssi; mesh["last_snr"] = _stats.last_snr; mesh["tx_queue_len"] = _stats.tx_queue_len; mesh["batt_mv"] = _stats.batt_mv; // System info doc["free_heap"] = ESP.getFreeHeap(); doc["uptime_ms"] = millis(); sendJsonResponse(request, doc); } void WebConfig::handleWiFiGet(AsyncWebServerRequest* request) { if (!_wifi_config) { sendErrorResponse(request, "Config not available"); return; } JsonDocument doc; doc["ssid"] = _wifi_config->ssid; // Don't send password for security doc["password_set"] = strlen(_wifi_config->password) > 0; doc["ap_ssid"] = _wifi_config->ap_ssid; sendJsonResponse(request, doc); } void WebConfig::handleMQTTGet(AsyncWebServerRequest* request) { if (!_mqtt_config) { sendErrorResponse(request, "Config not available"); return; } JsonDocument doc; doc["enabled"] = _mqtt_config->enabled != 0; doc["broker"] = _mqtt_config->broker; doc["port"] = _mqtt_config->port; doc["user"] = _mqtt_config->user; // Don't send password for security doc["password_set"] = strlen(_mqtt_config->password) > 0; doc["topic_prefix"] = _mqtt_config->topic_prefix; sendJsonResponse(request, doc); } void WebConfig::handleReboot(AsyncWebServerRequest* request) { JsonDocument doc; doc["success"] = true; doc["message"] = "Rebooting..."; sendJsonResponse(request, doc); // Schedule reboot after response is sent delay(100); if (_reboot_callback) { _reboot_callback(); } else { ESP.restart(); } } void WebConfig::handleNotFound(AsyncWebServerRequest* request) { request->send(404, "text/plain", "Not found"); } void WebConfig::sendJsonResponse(AsyncWebServerRequest* request, JsonDocument& doc, int code) { String response; serializeJson(doc, response); request->send(code, "application/json", response); } void WebConfig::sendErrorResponse(AsyncWebServerRequest* request, const char* message, int code) { JsonDocument doc; doc["success"] = false; doc["error"] = message; sendJsonResponse(request, doc, code); } #endif // WITH_MQTT