Files
spore/src/RestApiServer.cpp
2025-08-21 15:54:05 +02:00

169 lines
6.4 KiB
C++

#include "RestApiServer.h"
RestApiServer::RestApiServer(ClusterContext& ctx, uint16_t port) : server(port), ctx(ctx) {}
void RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> 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 RestApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> 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 RestApiServer::begin() {
addEndpoint("/api/node/status", HTTP_GET,
std::bind(&RestApiServer::getSystemStatusJson, this, std::placeholders::_1));
addEndpoint("/api/cluster/members", HTTP_GET,
std::bind(&RestApiServer::getClusterMembersJson, this, std::placeholders::_1));
addEndpoint("/api/node/update", HTTP_POST,
std::bind(&RestApiServer::onFirmwareUpdateRequest, this, std::placeholders::_1),
std::bind(&RestApiServer::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(&RestApiServer::onRestartRequest, this, std::placeholders::_1));
server.begin();
}
void RestApiServer::getSystemStatusJson(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<JsonArray>();
for (const auto& entry : serviceRegistry) {
JsonObject apiObj = apiArr.add<JsonObject>();
apiObj["uri"] = std::get<0>(entry);
apiObj["method"] = std::get<1>(entry);
}
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void RestApiServer::getClusterMembersJson(AsyncWebServerRequest *request) {
JsonDocument doc;
JsonArray arr = doc["members"].to<JsonArray>();
for (const auto& node : *ctx.memberList) {
JsonObject obj = arr.add<JsonObject>();
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<JsonArray>();
for (const auto& endpoint : node.apiEndpoints) {
JsonObject apiObj = apiArr.add<JsonObject>();
apiObj["uri"] = std::get<0>(endpoint);
methodToStr(endpoint, apiObj);
}
}
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void RestApiServer::methodToStr(const std::tuple<String, int> &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 RestApiServer::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 RestApiServer::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 RestApiServer::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();
});
}