feat: services
This commit is contained in:
55
examples/wifiscan/main.cpp
Normal file
55
examples/wifiscan/main.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "NodeContext.h"
|
||||||
|
#include "NetworkManager.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
#include "TaskManager.h"
|
||||||
|
#include "ClusterManager.h"
|
||||||
|
|
||||||
|
// Services
|
||||||
|
#include "NodeService.h"
|
||||||
|
#include "ClusterService.h"
|
||||||
|
#include "TaskService.h"
|
||||||
|
#include "WifiScanService.h"
|
||||||
|
|
||||||
|
NodeContext ctx;
|
||||||
|
TaskManager taskManager(ctx);
|
||||||
|
NetworkManager networkManager(ctx);
|
||||||
|
ApiServer apiServer(ctx, taskManager);
|
||||||
|
ClusterManager cluster(ctx, taskManager);
|
||||||
|
|
||||||
|
// Create services
|
||||||
|
NodeService nodeService(ctx);
|
||||||
|
ClusterService clusterService(ctx);
|
||||||
|
TaskService taskService(taskManager);
|
||||||
|
WifiScanService wifiScanService(networkManager);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("\n[WiFiScan] Starting...");
|
||||||
|
|
||||||
|
// Initialize networking
|
||||||
|
networkManager.setupWiFi();
|
||||||
|
|
||||||
|
taskManager.initialize();
|
||||||
|
|
||||||
|
// Add all services to API server
|
||||||
|
apiServer.addService(nodeService);
|
||||||
|
apiServer.addService(clusterService);
|
||||||
|
apiServer.addService(taskService);
|
||||||
|
apiServer.addService(wifiScanService);
|
||||||
|
|
||||||
|
// Start API server
|
||||||
|
apiServer.begin();
|
||||||
|
|
||||||
|
taskManager.printTaskStatus();
|
||||||
|
|
||||||
|
Serial.println("[WiFiScan] Ready!");
|
||||||
|
Serial.println("[WiFiScan] Available endpoints:");
|
||||||
|
Serial.println(" POST /api/wifi/scan - Start WiFi scan");
|
||||||
|
Serial.println(" GET /api/wifi - Get scan results");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
taskManager.execute();
|
||||||
|
yield();
|
||||||
|
}
|
||||||
@@ -10,53 +10,33 @@
|
|||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include "NodeInfo.h"
|
#include "NodeInfo.h"
|
||||||
#include "TaskManager.h"
|
#include "TaskManager.h"
|
||||||
|
#include "ApiTypes.h"
|
||||||
|
|
||||||
|
class Service; // Forward declaration
|
||||||
|
|
||||||
class ApiServer {
|
class ApiServer {
|
||||||
public:
|
public:
|
||||||
ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80);
|
ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80);
|
||||||
void begin();
|
void begin();
|
||||||
|
void addService(Service& service);
|
||||||
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
|
|
||||||
String defaultValue; // optional default value (stringified)
|
|
||||||
};
|
|
||||||
struct EndpointCapability {
|
|
||||||
String uri;
|
|
||||||
int method;
|
|
||||||
std::vector<ParamSpec> params;
|
|
||||||
};
|
|
||||||
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
||||||
const std::vector<ParamSpec>& params);
|
const std::vector<ParamSpec>& params);
|
||||||
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,
|
||||||
const std::vector<ParamSpec>& params);
|
const std::vector<ParamSpec>& params);
|
||||||
|
|
||||||
|
static const char* methodToStr(int method);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebServer server;
|
AsyncWebServer server;
|
||||||
NodeContext& ctx;
|
NodeContext& ctx;
|
||||||
TaskManager& taskManager;
|
TaskManager& taskManager;
|
||||||
|
std::vector<std::reference_wrapper<Service>> services;
|
||||||
std::vector<std::tuple<String, int>> serviceRegistry;
|
std::vector<std::tuple<String, int>> serviceRegistry;
|
||||||
std::vector<EndpointCapability> capabilityRegistry;
|
|
||||||
void onClusterMembersRequest(AsyncWebServerRequest *request);
|
|
||||||
void methodToStr(const std::tuple<String, int> &endpoint, JsonObject &apiObj);
|
|
||||||
void onSystemStatusRequest(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 onRestartRequest(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
// Task management endpoints
|
|
||||||
void onTaskStatusRequest(AsyncWebServerRequest *request);
|
|
||||||
void onTaskControlRequest(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
// Capabilities endpoint
|
|
||||||
void onCapabilitiesRequest(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
// Internal helpers
|
// Internal helpers
|
||||||
void registerServiceForLocalNode(const String& uri, int method);
|
void registerServiceForLocalNode(const String& uri, int method);
|
||||||
|
|||||||
18
include/ApiTypes.h
Normal file
18
include/ApiTypes.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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
|
||||||
|
String defaultValue; // optional default value (stringified)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EndpointCapability {
|
||||||
|
String uri;
|
||||||
|
int method;
|
||||||
|
std::vector<ParamSpec> params;
|
||||||
|
};
|
||||||
16
include/ClusterService.h
Normal file
16
include/ClusterService.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "NodeContext.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
class ClusterService : public Service {
|
||||||
|
public:
|
||||||
|
ClusterService(NodeContext& ctx);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "Cluster"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NodeContext& ctx;
|
||||||
|
|
||||||
|
void handleMembersRequest(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
@@ -1,12 +1,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "NodeContext.h"
|
#include "NodeContext.h"
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct AccessPoint {
|
||||||
|
String ssid;
|
||||||
|
int32_t rssi;
|
||||||
|
uint8_t encryptionType;
|
||||||
|
uint8_t* bssid;
|
||||||
|
int32_t channel;
|
||||||
|
bool isHidden;
|
||||||
|
};
|
||||||
|
|
||||||
class NetworkManager {
|
class NetworkManager {
|
||||||
public:
|
public:
|
||||||
NetworkManager(NodeContext& ctx);
|
NetworkManager(NodeContext& ctx);
|
||||||
void setupWiFi();
|
void setupWiFi();
|
||||||
void setHostnameFromMac();
|
void setHostnameFromMac();
|
||||||
|
|
||||||
|
// WiFi scanning methods
|
||||||
|
void scanWifi();
|
||||||
|
void processAccessPoints();
|
||||||
|
std::vector<AccessPoint> getAccessPoints() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NodeContext& ctx;
|
NodeContext& ctx;
|
||||||
|
std::vector<AccessPoint> accessPoints;
|
||||||
|
bool isScanning = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "ApiTypes.h"
|
||||||
|
|
||||||
class NodeContext {
|
class NodeContext {
|
||||||
public:
|
public:
|
||||||
@@ -19,6 +20,7 @@ public:
|
|||||||
NodeInfo self;
|
NodeInfo self;
|
||||||
std::map<String, NodeInfo>* memberList;
|
std::map<String, NodeInfo>* memberList;
|
||||||
Config config;
|
Config config;
|
||||||
|
std::vector<EndpointCapability> capabilities;
|
||||||
|
|
||||||
using EventCallback = std::function<void(void*)>;
|
using EventCallback = std::function<void(void*)>;
|
||||||
std::map<std::string, std::vector<EventCallback>> eventRegistry;
|
std::map<std::string, std::vector<EventCallback>> eventRegistry;
|
||||||
|
|||||||
21
include/NodeService.h
Normal file
21
include/NodeService.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "NodeContext.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <Updater.h>
|
||||||
|
|
||||||
|
class NodeService : public Service {
|
||||||
|
public:
|
||||||
|
NodeService(NodeContext& ctx);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "Node"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NodeContext& ctx;
|
||||||
|
|
||||||
|
void handleStatusRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleUpdateRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleUpdateUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final);
|
||||||
|
void handleRestartRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleCapabilitiesRequest(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
9
include/Service.h
Normal file
9
include/Service.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ApiServer.h"
|
||||||
|
|
||||||
|
class Service {
|
||||||
|
public:
|
||||||
|
virtual ~Service() = default;
|
||||||
|
virtual void registerEndpoints(ApiServer& api) = 0;
|
||||||
|
virtual const char* getName() const = 0;
|
||||||
|
};
|
||||||
17
include/TaskService.h
Normal file
17
include/TaskService.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "TaskManager.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
class TaskService : public Service {
|
||||||
|
public:
|
||||||
|
TaskService(TaskManager& taskManager);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "Task"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TaskManager& taskManager;
|
||||||
|
|
||||||
|
void handleStatusRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleControlRequest(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
17
include/WifiScanService.h
Normal file
17
include/WifiScanService.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Service.h"
|
||||||
|
#include "NetworkManager.h"
|
||||||
|
#include "NodeContext.h"
|
||||||
|
|
||||||
|
class WifiScanService : public Service {
|
||||||
|
public:
|
||||||
|
WifiScanService(NetworkManager& networkManager);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return "WifiScan"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkManager& networkManager;
|
||||||
|
|
||||||
|
void handleScanRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleGetAccessPoints(AsyncWebServerRequest* request);
|
||||||
|
};
|
||||||
@@ -110,6 +110,35 @@ build_src_filter =
|
|||||||
+<src/*.c>
|
+<src/*.c>
|
||||||
+<src/*.cpp>
|
+<src/*.cpp>
|
||||||
|
|
||||||
|
[env:esp01_1m_wifiscan]
|
||||||
|
platform = platformio/espressif8266@^4.2.1
|
||||||
|
board = esp01_1m
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 115200
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.partitions = partitions_ota_1M.csv
|
||||||
|
board_build.flash_mode = dout
|
||||||
|
board_build.flash_size = 1M
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
build_src_filter =
|
||||||
|
+<examples/wifiscan/main.cpp>
|
||||||
|
+<src/*.c>
|
||||||
|
+<src/*.cpp>
|
||||||
|
|
||||||
|
[env:d1_mini_wifiscan]
|
||||||
|
platform = platformio/espressif8266@^4.2.1
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 115200
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
board_build.flash_size = 4M
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
build_src_filter =
|
||||||
|
+<examples/wifiscan/main.cpp>
|
||||||
|
+<src/*.c>
|
||||||
|
+<src/*.cpp>
|
||||||
|
|
||||||
[env:d1_mini_neopattern]
|
[env:d1_mini_neopattern]
|
||||||
platform = platformio/espressif8266@^4.2.1
|
platform = platformio/espressif8266@^4.2.1
|
||||||
board = d1_mini
|
board = d1_mini
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "ApiServer.h"
|
#include "ApiServer.h"
|
||||||
|
#include "Service.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
// Shared helper for HTTP method to string
|
const char* ApiServer::methodToStr(int method) {
|
||||||
static const char* methodStrFromInt(int method) {
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case HTTP_GET: return "GET";
|
case HTTP_GET: return "GET";
|
||||||
case HTTP_POST: return "POST";
|
case HTTP_POST: return "POST";
|
||||||
@@ -39,7 +39,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
|
|||||||
// Overloads that also record minimal capability specs
|
// Overloads that also record minimal capability specs
|
||||||
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
|
||||||
const std::vector<ParamSpec>& params) {
|
const std::vector<ParamSpec>& params) {
|
||||||
capabilityRegistry.push_back(EndpointCapability{uri, method, params});
|
ctx.capabilities.push_back(EndpointCapability{uri, method, params});
|
||||||
registerServiceForLocalNode(uri, method);
|
registerServiceForLocalNode(uri, method);
|
||||||
server.on(uri.c_str(), method, requestHandler);
|
server.on(uri.c_str(), method, requestHandler);
|
||||||
}
|
}
|
||||||
@@ -47,346 +47,22 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
|
|||||||
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> 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,
|
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler,
|
||||||
const std::vector<ParamSpec>& params) {
|
const std::vector<ParamSpec>& params) {
|
||||||
capabilityRegistry.push_back(EndpointCapability{uri, method, params});
|
ctx.capabilities.push_back(EndpointCapability{uri, method, params});
|
||||||
registerServiceForLocalNode(uri, method);
|
registerServiceForLocalNode(uri, method);
|
||||||
server.on(uri.c_str(), method, requestHandler, uploadHandler);
|
server.on(uri.c_str(), method, requestHandler, uploadHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiServer::begin() {
|
void ApiServer::addService(Service& service) {
|
||||||
addEndpoint("/api/node/status", HTTP_GET,
|
services.push_back(service);
|
||||||
std::bind(&ApiServer::onSystemStatusRequest, this, std::placeholders::_1));
|
Serial.printf("[API] Added service: %s\n", service.getName());
|
||||||
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));
|
|
||||||
|
|
||||||
// New: Capabilities endpoint
|
void ApiServer::begin() {
|
||||||
addEndpoint("/api/capabilities", HTTP_GET,
|
// Register all service endpoints
|
||||||
std::bind(&ApiServer::onCapabilitiesRequest, this, std::placeholders::_1));
|
for (auto& service : services) {
|
||||||
|
service.get().registerEndpoints(*this);
|
||||||
// Task management endpoints
|
Serial.printf("[API] Registered endpoints for service: %s\n", service.get().getName());
|
||||||
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),
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
// Add labels if present
|
|
||||||
if (!node.labels.empty()) {
|
|
||||||
JsonObject labelsObj = obj["labels"].to<JsonObject>();
|
|
||||||
for (const auto& kv : node.labels) {
|
|
||||||
labelsObj[kv.first.c_str()] = kv.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String json;
|
|
||||||
serializeJson(doc, json);
|
|
||||||
request->send(200, "application/json", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiServer::methodToStr(const std::tuple<String, int> &endpoint, JsonObject &apiObj)
|
|
||||||
{
|
|
||||||
int method = std::get<1>(endpoint);
|
|
||||||
apiObj["method"] = methodStrFromInt(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiServer::onFirmwareUpdateRequest(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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
// Use a separate document as scratch space for task statuses to avoid interfering with the response root
|
|
||||||
JsonDocument scratch;
|
|
||||||
|
|
||||||
// Get comprehensive task status from TaskManager
|
|
||||||
auto taskStatuses = taskManager.getAllTaskStatuses(scratch);
|
|
||||||
|
|
||||||
// Build response document
|
|
||||||
JsonDocument 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ps.defaultValue.length() > 0) {
|
|
||||||
p["default"] = ps.defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
42
src/ClusterService.cpp
Normal file
42
src/ClusterService.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "ClusterService.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
|
||||||
|
ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {}
|
||||||
|
|
||||||
|
void ClusterService::registerEndpoints(ApiServer& api) {
|
||||||
|
api.addEndpoint("/api/cluster/members", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleMembersRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClusterService::handleMembersRequest(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;
|
||||||
|
|
||||||
|
// Add labels if present
|
||||||
|
if (!node.labels.empty()) {
|
||||||
|
JsonObject labelsObj = obj["labels"].to<JsonObject>();
|
||||||
|
for (const auto& kv : node.labels) {
|
||||||
|
labelsObj[kv.first.c_str()] = kv.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
request->send(200, "application/json", json);
|
||||||
|
}
|
||||||
@@ -2,6 +2,59 @@
|
|||||||
|
|
||||||
// SSID and password are now configured via Config class
|
// SSID and password are now configured via Config class
|
||||||
|
|
||||||
|
void NetworkManager::scanWifi() {
|
||||||
|
if (!isScanning) {
|
||||||
|
isScanning = true;
|
||||||
|
Serial.println("[WiFi] Starting WiFi scan...");
|
||||||
|
// Start async WiFi scan
|
||||||
|
WiFi.scanNetworksAsync([this](int networksFound) {
|
||||||
|
Serial.printf("[WiFi] Scan completed, found %d networks\n", networksFound);
|
||||||
|
this->processAccessPoints();
|
||||||
|
this->isScanning = false;
|
||||||
|
}, true);
|
||||||
|
} else {
|
||||||
|
Serial.println("[WiFi] Scan already in progress...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkManager::processAccessPoints() {
|
||||||
|
int numNetworks = WiFi.scanComplete();
|
||||||
|
if (numNetworks <= 0) {
|
||||||
|
Serial.println("[WiFi] No networks found or scan not complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing access points
|
||||||
|
accessPoints.clear();
|
||||||
|
|
||||||
|
// Process each network found
|
||||||
|
for (int i = 0; i < numNetworks; i++) {
|
||||||
|
AccessPoint ap;
|
||||||
|
ap.ssid = WiFi.SSID(i);
|
||||||
|
ap.rssi = WiFi.RSSI(i);
|
||||||
|
ap.encryptionType = WiFi.encryptionType(i);
|
||||||
|
ap.channel = WiFi.channel(i);
|
||||||
|
ap.isHidden = ap.ssid.length() == 0;
|
||||||
|
|
||||||
|
// Copy BSSID
|
||||||
|
uint8_t* newBssid = new uint8_t[6];
|
||||||
|
memcpy(newBssid, WiFi.BSSID(i), 6);
|
||||||
|
ap.bssid = newBssid;
|
||||||
|
|
||||||
|
accessPoints.push_back(ap);
|
||||||
|
|
||||||
|
Serial.printf("[WiFi] Found network %d: %s, Ch: %d, RSSI: %d\n",
|
||||||
|
i + 1, ap.ssid.c_str(), ap.channel, ap.rssi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the memory used by the scan
|
||||||
|
WiFi.scanDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AccessPoint> NetworkManager::getAccessPoints() const {
|
||||||
|
return accessPoints;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx) {}
|
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx) {}
|
||||||
|
|
||||||
void NetworkManager::setHostnameFromMac() {
|
void NetworkManager::setHostnameFromMac() {
|
||||||
|
|||||||
157
src/NodeService.cpp
Normal file
157
src/NodeService.cpp
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include "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);
|
||||||
|
}
|
||||||
123
src/TaskService.cpp
Normal file
123
src/TaskService.cpp
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#include "TaskService.h"
|
||||||
|
#include "ApiServer.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {}
|
||||||
|
|
||||||
|
void TaskService::registerEndpoints(ApiServer& api) {
|
||||||
|
api.addEndpoint("/api/tasks/status", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); });
|
||||||
|
|
||||||
|
api.addEndpoint("/api/tasks/control", HTTP_POST,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
|
||||||
|
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")}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskService::handleStatusRequest(AsyncWebServerRequest* request) {
|
||||||
|
JsonDocument scratch;
|
||||||
|
auto taskStatuses = taskManager.getAllTaskStatuses(scratch);
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
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"]; });
|
||||||
|
|
||||||
|
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"];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TaskService::handleControlRequest(AsyncWebServerRequest* request) {
|
||||||
|
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") {
|
||||||
|
success = true;
|
||||||
|
message = "Task status retrieved";
|
||||||
|
|
||||||
|
JsonDocument statusDoc;
|
||||||
|
statusDoc["success"] = success;
|
||||||
|
statusDoc["message"] = message;
|
||||||
|
statusDoc["task"] = taskName;
|
||||||
|
statusDoc["action"] = action;
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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;
|
||||||
|
} 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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/WifiScanService.cpp
Normal file
57
src/WifiScanService.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#include "WifiScanService.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
WifiScanService::WifiScanService(NetworkManager& networkManager)
|
||||||
|
: networkManager(networkManager) {}
|
||||||
|
|
||||||
|
void WifiScanService::registerEndpoints(ApiServer& api) {
|
||||||
|
// POST /api/wifi/scan - Start WiFi scan
|
||||||
|
api.addEndpoint("/api/wifi/scan", HTTP_POST,
|
||||||
|
[this](AsyncWebServerRequest* request) { this->handleScanRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
// GET /api/wifi - Get all access points
|
||||||
|
api.addEndpoint("/api/wifi", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { this->handleGetAccessPoints(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiScanService::handleScanRequest(AsyncWebServerRequest* request) {
|
||||||
|
networkManager.scanWifi();
|
||||||
|
|
||||||
|
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["status"] = "scanning";
|
||||||
|
doc["message"] = "WiFi scan started";
|
||||||
|
|
||||||
|
serializeJson(doc, *response);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiScanService::handleGetAccessPoints(AsyncWebServerRequest* request) {
|
||||||
|
auto accessPoints = networkManager.getAccessPoints();
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
|
||||||
|
doc["access_points"].to<JsonArray>();
|
||||||
|
|
||||||
|
for (const auto& ap : accessPoints) {
|
||||||
|
JsonObject apObj = doc["access_points"].add<JsonObject>();
|
||||||
|
apObj["ssid"] = ap.ssid;
|
||||||
|
apObj["rssi"] = ap.rssi;
|
||||||
|
apObj["channel"] = ap.channel;
|
||||||
|
apObj["encryption_type"] = ap.encryptionType;
|
||||||
|
apObj["hidden"] = ap.isHidden;
|
||||||
|
|
||||||
|
// Convert BSSID to string
|
||||||
|
char bssid[18];
|
||||||
|
sprintf(bssid, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
ap.bssid[0], ap.bssid[1], ap.bssid[2],
|
||||||
|
ap.bssid[3], ap.bssid[4], ap.bssid[5]);
|
||||||
|
apObj["bssid"] = bssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||||
|
serializeJson(doc, *response);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user