#include "services/NodeService.h" #include "ApiServer.h" NodeService::NodeService(NodeContext& ctx) : ctx(ctx) {} void NodeService::registerEndpoints(ApiServer& api) { // Status endpoint api.addEndpoint("/api/node/status", HTTP_GET, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, std::vector{}); // Update endpoint with file upload api.addEndpoint("/api/node/update", HTTP_POST, [this](AsyncWebServerRequest* request) { handleUpdateRequest(request); }, [this](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) { handleUpdateUpload(request, filename, index, data, len, final); }, std::vector{ ParamSpec{String("firmware"), true, String("body"), String("file"), {}, String("")} }); // Restart endpoint api.addEndpoint("/api/node/restart", HTTP_POST, [this](AsyncWebServerRequest* request) { handleRestartRequest(request); }, std::vector{}); // Capabilities endpoint api.addEndpoint("/api/node/capabilities", HTTP_GET, [this](AsyncWebServerRequest* request) { handleCapabilitiesRequest(request); }, std::vector{}); } void NodeService::handleStatusRequest(AsyncWebServerRequest* request) { JsonDocument doc; doc["freeHeap"] = ESP.getFreeHeap(); doc["chipId"] = ESP.getChipId(); doc["sdkVersion"] = ESP.getSdkVersion(); doc["cpuFreqMHz"] = ESP.getCpuFreqMHz(); doc["flashChipSize"] = ESP.getFlashChipSize(); // Include local node labels if present if (ctx.memberList) { auto it = ctx.memberList->find(ctx.hostname); if (it != ctx.memberList->end()) { JsonObject labelsObj = doc["labels"].to(); for (const auto& kv : it->second.labels) { labelsObj[kv.first.c_str()] = kv.second; } } else if (!ctx.self.labels.empty()) { JsonObject labelsObj = doc["labels"].to(); for (const auto& kv : ctx.self.labels) { labelsObj[kv.first.c_str()] = kv.second; } } } String json; serializeJson(doc, json); request->send(200, "application/json", json); } void NodeService::handleUpdateRequest(AsyncWebServerRequest* request) { bool success = !Update.hasError(); AsyncWebServerResponse* response = request->beginResponse(200, "application/json", success ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}"); response->addHeader("Connection", "close"); request->send(response); request->onDisconnect([]() { Serial.println("[API] Restart device"); delay(10); ESP.restart(); }); } void NodeService::handleUpdateUpload(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; } } 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); } } } void NodeService::handleRestartRequest(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 NodeService::handleCapabilitiesRequest(AsyncWebServerRequest* request) { JsonDocument doc; JsonArray endpointsArr = doc["endpoints"].to(); // Add all registered capabilities for (const auto& cap : ctx.capabilities) { JsonObject obj = endpointsArr.add(); obj["uri"] = cap.uri; obj["method"] = ApiServer::methodToStr(cap.method); if (!cap.params.empty()) { JsonArray paramsArr = obj["params"].to(); for (const auto& ps : cap.params) { JsonObject p = paramsArr.add(); p["name"] = ps.name; p["location"] = ps.location; p["required"] = ps.required; p["type"] = ps.type; if (!ps.values.empty()) { JsonArray allowed = p["values"].to(); for (const auto& v : ps.values) { allowed.add(v); } } if (ps.defaultValue.length() > 0) { p["default"] = ps.defaultValue; } } } } String json; serializeJson(doc, json); request->send(200, "application/json", json); }