158 lines
5.7 KiB
C++
158 lines
5.7 KiB
C++
#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<ParamSpec>{});
|
|
|
|
// 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>{
|
|
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<ParamSpec>{});
|
|
|
|
// Capabilities endpoint
|
|
api.addEndpoint("/api/node/capabilities", HTTP_GET,
|
|
[this](AsyncWebServerRequest* request) { handleCapabilitiesRequest(request); },
|
|
std::vector<ParamSpec>{});
|
|
}
|
|
|
|
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<JsonObject>();
|
|
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<JsonObject>();
|
|
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<JsonArray>();
|
|
|
|
// Add all registered capabilities
|
|
for (const auto& cap : ctx.capabilities) {
|
|
JsonObject obj = endpointsArr.add<JsonObject>();
|
|
obj["uri"] = cap.uri;
|
|
obj["method"] = ApiServer::methodToStr(cap.method);
|
|
if (!cap.params.empty()) {
|
|
JsonArray paramsArr = obj["params"].to<JsonArray>();
|
|
for (const auto& ps : cap.params) {
|
|
JsonObject p = paramsArr.add<JsonObject>();
|
|
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<JsonArray>();
|
|
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);
|
|
}
|