feature/capabilities #1

Merged
master merged 2 commits from feature/capabilities into main 2025-08-28 11:17:24 +02:00
3 changed files with 127 additions and 3 deletions

View File

@@ -59,7 +59,7 @@ public:
String json; String json;
serializeJson(resp, json); serializeJson(resp, json);
request->send(ok ? 200 : 400, "application/json", json); request->send(ok ? 200 : 400, "application/json", json);
}); }, std::vector<ApiServer::ParamSpec>{ ApiServer::ParamSpec{ String("state"), true, String("body"), String("string"), { String("on"), String("off"), String("toggle") } } });
} }
void turnOn() { void turnOn() {

View File

@@ -21,19 +21,42 @@ public:
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler); void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void 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); std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler);
// Minimal capability spec types and registration overloads
struct ParamSpec {
String name;
bool required;
String location; // "query" | "body" | "path" | "header"
String type; // e.g. "string", "number", "boolean"
std::vector<String> values; // optional allowed values
};
struct EndpointCapability {
String uri;
int method;
std::vector<ParamSpec> params;
};
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params);
void 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,
const std::vector<ParamSpec>& params);
private: private:
AsyncWebServer server; AsyncWebServer server;
NodeContext& ctx; NodeContext& ctx;
TaskManager& taskManager; TaskManager& taskManager;
std::vector<std::tuple<String, int>> serviceRegistry; std::vector<std::tuple<String, int>> serviceRegistry;
std::vector<EndpointCapability> capabilityRegistry;
void onClusterMembersRequest(AsyncWebServerRequest *request); void onClusterMembersRequest(AsyncWebServerRequest *request);
void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj); void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj);
void onSystemStatusRequest(AsyncWebServerRequest *request); void onSystemStatusRequest(AsyncWebServerRequest *request);
void onFirmwareUpdateRequest(AsyncWebServerRequest *request); void onFirmwareUpdateRequest(AsyncWebServerRequest *request);
void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void onRestartRequest(AsyncWebServerRequest *request); void onRestartRequest(AsyncWebServerRequest *request);
// Task management endpoints // Task management endpoints
void onTaskStatusRequest(AsyncWebServerRequest *request); void onTaskStatusRequest(AsyncWebServerRequest *request);
void onTaskControlRequest(AsyncWebServerRequest *request); void onTaskControlRequest(AsyncWebServerRequest *request);
// Capabilities endpoint
void onCapabilitiesRequest(AsyncWebServerRequest *request);
}; };

View File

@@ -27,6 +27,34 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler, uploadHandler); server.on(uri.c_str(), method, requestHandler, uploadHandler);
} }
// Overloads that also record minimal capability specs
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params) {
capabilityRegistry.push_back(EndpointCapability{uri, method, params});
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);
}
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,
const std::vector<ParamSpec>& params) {
capabilityRegistry.push_back(EndpointCapability{uri, method, params});
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() { void ApiServer::begin() {
addEndpoint("/api/node/status", HTTP_GET, addEndpoint("/api/node/status", HTTP_GET,
std::bind(&ApiServer::onSystemStatusRequest, this, std::placeholders::_1)); std::bind(&ApiServer::onSystemStatusRequest, this, std::placeholders::_1));
@@ -38,12 +66,21 @@ void ApiServer::begin() {
); );
addEndpoint("/api/node/restart", HTTP_POST, addEndpoint("/api/node/restart", HTTP_POST,
std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1)); std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1));
// New: Capabilities endpoint
addEndpoint("/api/capabilities", HTTP_GET,
std::bind(&ApiServer::onCapabilitiesRequest, this, std::placeholders::_1));
// Task management endpoints // Task management endpoints
addEndpoint("/api/tasks/status", HTTP_GET, addEndpoint("/api/tasks/status", HTTP_GET,
std::bind(&ApiServer::onTaskStatusRequest, this, std::placeholders::_1)); std::bind(&ApiServer::onTaskStatusRequest, this, std::placeholders::_1));
addEndpoint("/api/tasks/control", HTTP_POST, addEndpoint("/api/tasks/control", HTTP_POST,
std::bind(&ApiServer::onTaskControlRequest, this, std::placeholders::_1)); std::bind(&ApiServer::onTaskControlRequest, this, std::placeholders::_1),
std::vector<ParamSpec>{
ParamSpec{String("task"), true, String("body"), String("string"), {}},
ParamSpec{String("action"), true, String("body"), String("string"), {String("enable"), String("disable"), String("start"), String("stop"), String("status")}}
}
);
server.begin(); server.begin();
} }
@@ -296,4 +333,68 @@ void ApiServer::onTaskControlRequest(AsyncWebServerRequest *request) {
serializeJson(doc, json); serializeJson(doc, json);
request->send(400, "application/json", json); request->send(400, "application/json", json);
} }
}
void ApiServer::onCapabilitiesRequest(AsyncWebServerRequest *request) {
JsonDocument doc;
JsonArray endpointsArr = doc["endpoints"].to<JsonArray>();
// Track seen (uri|method) to avoid duplicates
std::vector<String> seen;
auto makeKey = [](const String& uri, int method) {
String k = uri; k += "|"; k += method; return k;
};
auto methodStrFromInt = [](int method) -> const char* {
switch (method) {
case HTTP_GET: return "GET";
case HTTP_POST: return "POST";
case HTTP_PUT: return "PUT";
case HTTP_DELETE: return "DELETE";
case HTTP_PATCH: return "PATCH";
default: return "UNKNOWN";
}
};
// Rich entries first
for (const auto& cap : capabilityRegistry) {
String key = makeKey(cap.uri, cap.method);
seen.push_back(key);
JsonObject obj = endpointsArr.add<JsonObject>();
obj["uri"] = cap.uri;
obj["method"] = methodStrFromInt(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);
}
}
}
}
}
// Then any endpoints without explicit param specs
for (const auto& entry : serviceRegistry) {
const String& uri = std::get<0>(entry);
int method = std::get<1>(entry);
String key = makeKey(uri, method);
bool exists = false;
for (const auto& s : seen) { if (s == key) { exists = true; break; } }
if (!exists) {
JsonObject obj = endpointsArr.add<JsonObject>();
obj["uri"] = uri;
obj["method"] = methodStrFromInt(method);
}
}
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
} }