#include "ApiServer.h" ApiServer::ApiServer(NodeContext& ctx, uint16_t port) : server(port), ctx(ctx) {} 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()) { (*ctx.memberList)[0].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()) { (*ctx.memberList)[0].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)); 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& node : *ctx.memberList) { 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(); }); }