299 lines
11 KiB
C++
299 lines
11 KiB
C++
#include "ApiServer.h"
|
|
#include <algorithm>
|
|
|
|
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<void(AsyncWebServerRequest*)> 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<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()) {
|
|
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<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 ApiServer::onClusterMembersRequest(AsyncWebServerRequest *request) {
|
|
JsonDocument doc;
|
|
JsonArray arr = doc["members"].to<JsonArray>();
|
|
for (const auto& pair : *ctx.memberList) {
|
|
const NodeInfo& node = pair.second;
|
|
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 ApiServer::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 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<JsonObject>();
|
|
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<JsonArray>();
|
|
for (const auto& taskPair : taskStatuses) {
|
|
JsonObject taskObj = tasksArr.add<JsonObject>();
|
|
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<JsonObject>();
|
|
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);
|
|
}
|
|
} |