#include "ApiServer.h" #include ApiServer::ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port) : server(port), ctx(ctx), taskManager(taskMgr) {} void ApiServer::addEndpoint(const String& uri, int method, std::function requestHandler) { serviceRegistry.push_back(std::make_tuple(uri, method)); // Store in NodeInfo for local node if (ctx.memberList && !ctx.memberList->empty()) { auto it = ctx.memberList->find(ctx.hostname); if (it != ctx.memberList->end()) { it->second.apiEndpoints.push_back(std::make_tuple(uri, method)); } } server.on(uri.c_str(), method, requestHandler); } void ApiServer::addEndpoint(const String& uri, int method, std::function requestHandler, std::function uploadHandler) { serviceRegistry.push_back(std::make_tuple(uri, method)); if (ctx.memberList && !ctx.memberList->empty()) { auto it = ctx.memberList->find(ctx.hostname); if (it != ctx.memberList->end()) { it->second.apiEndpoints.push_back(std::make_tuple(uri, method)); } } server.on(uri.c_str(), method, requestHandler, uploadHandler); } void ApiServer::begin() { addEndpoint("/api/node/status", HTTP_GET, std::bind(&ApiServer::onSystemStatusRequest, this, std::placeholders::_1)); addEndpoint("/api/cluster/members", HTTP_GET, std::bind(&ApiServer::onClusterMembersRequest, this, std::placeholders::_1)); addEndpoint("/api/node/update", HTTP_POST, std::bind(&ApiServer::onFirmwareUpdateRequest, this, std::placeholders::_1), std::bind(&ApiServer::onFirmwareUpload, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6) ); addEndpoint("/api/node/restart", HTTP_POST, std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1)); // Task management endpoints addEndpoint("/api/tasks/status", HTTP_GET, std::bind(&ApiServer::onTaskStatusRequest, this, std::placeholders::_1)); addEndpoint("/api/tasks/control", HTTP_POST, std::bind(&ApiServer::onTaskControlRequest, this, std::placeholders::_1)); server.begin(); } void ApiServer::onSystemStatusRequest(AsyncWebServerRequest *request) { JsonDocument doc; doc["freeHeap"] = ESP.getFreeHeap(); doc["chipId"] = ESP.getChipId(); doc["sdkVersion"] = ESP.getSdkVersion(); doc["cpuFreqMHz"] = ESP.getCpuFreqMHz(); doc["flashChipSize"] = ESP.getFlashChipSize(); JsonArray apiArr = doc["api"].to(); for (const auto& entry : serviceRegistry) { JsonObject apiObj = apiArr.add(); apiObj["uri"] = std::get<0>(entry); apiObj["method"] = std::get<1>(entry); } String json; serializeJson(doc, json); request->send(200, "application/json", json); } void ApiServer::onClusterMembersRequest(AsyncWebServerRequest *request) { JsonDocument doc; JsonArray arr = doc["members"].to(); for (const auto& pair : *ctx.memberList) { const NodeInfo& node = pair.second; JsonObject obj = arr.add(); obj["hostname"] = node.hostname; obj["ip"] = node.ip.toString(); obj["lastSeen"] = node.lastSeen; obj["latency"] = node.latency; obj["status"] = statusToStr(node.status); obj["resources"]["freeHeap"] = node.resources.freeHeap; obj["resources"]["chipId"] = node.resources.chipId; obj["resources"]["sdkVersion"] = node.resources.sdkVersion; obj["resources"]["cpuFreqMHz"] = node.resources.cpuFreqMHz; obj["resources"]["flashChipSize"] = node.resources.flashChipSize; JsonArray apiArr = obj["api"].to(); for (const auto& endpoint : node.apiEndpoints) { JsonObject apiObj = apiArr.add(); apiObj["uri"] = std::get<0>(endpoint); methodToStr(endpoint, apiObj); } } String json; serializeJson(doc, json); request->send(200, "application/json", json); } void ApiServer::methodToStr(const std::tuple &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj) { int method = std::get<1>(endpoint); const char *methodStr = nullptr; switch (method) { case HTTP_GET: methodStr = "GET"; break; case HTTP_POST: methodStr = "POST"; break; case HTTP_PUT: methodStr = "PUT"; break; case HTTP_DELETE: methodStr = "DELETE"; break; case HTTP_PATCH: methodStr = "PATCH"; break; default: methodStr = "UNKNOWN"; break; } apiObj["method"] = methodStr; } void ApiServer::onFirmwareUpdateRequest(AsyncWebServerRequest *request) { bool hasError = !Update.hasError(); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", hasError ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}"); response->addHeader("Connection", "close"); request->send(response); request->onDisconnect([]() { Serial.println("[API] Restart device"); delay(10); ESP.restart(); }); } void ApiServer::onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { Serial.print("[OTA] Update Start "); Serial.println(filename); Update.runAsync(true); if(!Update.begin(request->contentLength(), U_FLASH)) { Serial.println("[OTA] Update failed: not enough space"); Update.printError(Serial); AsyncWebServerResponse *response = request->beginResponse(500, "application/json", "{\"status\": \"FAIL\"}"); response->addHeader("Connection", "close"); request->send(response); return; } else { Update.printError(Serial); } } if (!Update.hasError()){ if (Update.write(data, len) != len) { Update.printError(Serial); } } if (final) { if (Update.end(true)) { if(Update.isFinished()) { Serial.print("[OTA] Update Success with "); Serial.print(index + len); Serial.println("B"); } else { Serial.println("[OTA] Update not finished"); } } else { Serial.print("[OTA] Update failed: "); Update.printError(Serial); } } return; } void ApiServer::onRestartRequest(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"status\": \"restarting\"}"); response->addHeader("Connection", "close"); request->send(response); request->onDisconnect([]() { Serial.println("[API] Restart device"); delay(10); ESP.restart(); }); } void ApiServer::onTaskStatusRequest(AsyncWebServerRequest *request) { JsonDocument doc; // Get comprehensive task status from TaskManager auto taskStatuses = taskManager.getAllTaskStatuses(doc); // Add summary information JsonObject summaryObj = doc["summary"].to(); summaryObj["totalTasks"] = taskStatuses.size(); summaryObj["activeTasks"] = std::count_if(taskStatuses.begin(), taskStatuses.end(), [](const auto& pair) { return pair.second["enabled"]; }); // Add detailed task information JsonArray tasksArr = doc["tasks"].to(); for (const auto& taskPair : taskStatuses) { JsonObject taskObj = tasksArr.add(); taskObj["name"] = taskPair.first; taskObj["interval"] = taskPair.second["interval"]; taskObj["enabled"] = taskPair.second["enabled"]; taskObj["running"] = taskPair.second["running"]; taskObj["autoStart"] = taskPair.second["autoStart"]; } // Add system information JsonObject systemObj = doc["system"].to(); systemObj["freeHeap"] = ESP.getFreeHeap(); systemObj["uptime"] = millis(); String json; serializeJson(doc, json); request->send(200, "application/json", json); } void ApiServer::onTaskControlRequest(AsyncWebServerRequest *request) { // Parse the request body for task control commands if (request->hasParam("task", true) && request->hasParam("action", true)) { String taskName = request->getParam("task", true)->value(); String action = request->getParam("action", true)->value(); bool success = false; String message = ""; if (action == "enable") { taskManager.enableTask(taskName.c_str()); success = true; message = "Task enabled"; } else if (action == "disable") { taskManager.disableTask(taskName.c_str()); success = true; message = "Task disabled"; } else if (action == "start") { taskManager.startTask(taskName.c_str()); success = true; message = "Task started"; } else if (action == "stop") { taskManager.stopTask(taskName.c_str()); success = true; message = "Task stopped"; } else if (action == "status") { // Get detailed status for a specific task success = true; message = "Task status retrieved"; // Create JsonDocument for status response JsonDocument statusDoc; statusDoc["success"] = success; statusDoc["message"] = message; statusDoc["task"] = taskName; statusDoc["action"] = action; // Add task details to response statusDoc["taskDetails"] = JsonObject(); JsonObject taskDetails = statusDoc["taskDetails"]; taskDetails["name"] = taskName; taskDetails["enabled"] = taskManager.isTaskEnabled(taskName.c_str()); taskDetails["running"] = taskManager.isTaskRunning(taskName.c_str()); taskDetails["interval"] = taskManager.getTaskInterval(taskName.c_str()); // Add system context taskDetails["system"] = JsonObject(); JsonObject systemInfo = taskDetails["system"]; systemInfo["freeHeap"] = ESP.getFreeHeap(); systemInfo["uptime"] = millis(); String statusJson; serializeJson(statusDoc, statusJson); request->send(200, "application/json", statusJson); return; // Early return since we've already sent the response } else { success = false; message = "Invalid action. Use: enable, disable, start, stop, or status"; } JsonDocument doc; doc["success"] = success; doc["message"] = message; doc["task"] = taskName; doc["action"] = action; String json; serializeJson(doc, json); request->send(success ? 200 : 400, "application/json", json); } else { // Missing parameters JsonDocument doc; doc["success"] = false; doc["message"] = "Missing parameters. Required: task, action"; doc["example"] = "{\"task\": \"discovery_send\", \"action\": \"status\"}"; String json; serializeJson(doc, json); request->send(400, "application/json", json); } }