feature/capabilities #1
@@ -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") } });
|
||||||
}
|
}
|
||||||
|
|
||||||
void turnOn() {
|
void turnOn() {
|
||||||
|
|||||||
@@ -21,19 +21,41 @@ 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"
|
||||||
|
};
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
@@ -296,4 +333,62 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user