refactor: harmonize method names

This commit is contained in:
2025-10-14 17:09:54 +02:00
parent 52f9098c1b
commit a45871625e
32 changed files with 274 additions and 79 deletions

View File

@@ -93,12 +93,12 @@ void setup() {
spore.setup(); spore.setup();
// Create and register custom services // Create and register custom services
RelayService* relayService = new RelayService(spore.getTaskManager(), 2); RelayService* relayService = new RelayService(spore.getContext(), spore.getTaskManager(), 2);
spore.addService(relayService); spore.registerService(relayService);
// Or using smart pointers // Or using smart pointers
auto sensorService = std::make_shared<SensorService>(); auto sensorService = std::make_shared<SensorService>(spore.getContext(), spore.getTaskManager());
spore.addService(sensorService); spore.registerService(sensorService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();

View File

@@ -21,11 +21,13 @@ The system architecture consists of several key components working together:
### API Server ### API Server
- **HTTP API Server**: RESTful API for cluster management - **HTTP API Server**: RESTful API for cluster management
- **Dynamic Endpoint Registration**: Automatic API endpoint discovery - **Dynamic Endpoint Registration**: Services register endpoints via `registerEndpoints(ApiServer&)`
- **Service Registry**: Track available services across the cluster - **Service Registry**: Track available services across the cluster
- **Service Lifecycle**: Services register both endpoints and tasks through unified interface
### Task Scheduler ### Task Scheduler
- **Cooperative Multitasking**: Background task management system (`TaskManager`) - **Cooperative Multitasking**: Background task management system (`TaskManager`)
- **Service Task Registration**: Services register tasks via `registerTasks(TaskManager&)`
- **Task Lifecycle Management**: Enable/disable tasks and set intervals at runtime - **Task Lifecycle Management**: Enable/disable tasks and set intervals at runtime
- **Execution Model**: Tasks run in `Spore::loop()` when their interval elapses - **Execution Model**: Tasks run in `Spore::loop()` when their interval elapses

View File

@@ -29,13 +29,13 @@ spore/
│ │ ├── NetworkManager.cpp # WiFi and network handling │ │ ├── NetworkManager.cpp # WiFi and network handling
│ │ ├── TaskManager.cpp # Background task management │ │ ├── TaskManager.cpp # Background task management
│ │ └── NodeContext.cpp # Central context and events │ │ └── NodeContext.cpp # Central context and events
│ ├── services/ # Built-in services │ ├── services/ # Built-in services (implement Service interface)
│ │ ├── NodeService.cpp │ │ ├── NodeService.cpp # registerEndpoints() + registerTasks()
│ │ ├── NetworkService.cpp │ │ ├── NetworkService.cpp # registerEndpoints() + registerTasks()
│ │ ├── ClusterService.cpp │ │ ├── ClusterService.cpp # registerEndpoints() + registerTasks()
│ │ ├── TaskService.cpp │ │ ├── TaskService.cpp # registerEndpoints() + registerTasks()
│ │ ├── StaticFileService.cpp │ │ ├── StaticFileService.cpp # registerEndpoints() + registerTasks()
│ │ └── MonitoringService.cpp │ │ └── MonitoringService.cpp # registerEndpoints() + registerTasks()
│ └── types/ # Shared types │ └── types/ # Shared types
├── include/ # Header files ├── include/ # Header files
├── examples/ # Example apps per env (base, relay, neopattern) ├── examples/ # Example apps per env (base, relay, neopattern)

View File

@@ -11,6 +11,31 @@ The TaskManager system provides:
- **Status Monitoring**: View task status and configuration - **Status Monitoring**: View task status and configuration
- **Automatic Lifecycle**: Tasks are automatically managed and executed - **Automatic Lifecycle**: Tasks are automatically managed and executed
## Service Interface Integration
Services now implement a unified interface for both endpoint and task registration:
```cpp
class MyService : public Service {
public:
void registerEndpoints(ApiServer& api) override {
// Register HTTP endpoints
api.registerEndpoint("/api/my/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
// Register background tasks
taskManager.registerTask("my_heartbeat", 2000,
[this]() { sendHeartbeat(); });
taskManager.registerTask("my_maintenance", 30000,
[this]() { performMaintenance(); });
}
const char* getName() const override { return "MyService"; }
};
```
## Basic Usage ## Basic Usage
```cpp ```cpp
@@ -27,6 +52,46 @@ taskManager.registerTask("maintenance", 30000, maintenanceFunction);
taskManager.initialize(); taskManager.initialize();
``` ```
## Service Lifecycle
The Spore framework automatically manages service registration and task lifecycle:
### Service Registration Process
1. **Service Creation**: Services are created with required dependencies (NodeContext, TaskManager, etc.)
2. **Service Registration**: Services are registered with the Spore framework via `spore.registerService()`
3. **Endpoint Registration**: When `spore.begin()` is called, `registerEndpoints()` is called for each service
4. **Task Registration**: Simultaneously, `registerTasks()` is called for each service
5. **Task Initialization**: The TaskManager initializes all registered tasks
6. **Execution**: Tasks run in the main loop when their intervals elapse
### Framework Integration
```cpp
void setup() {
spore.setup();
// Create service with dependencies
MyService* service = new MyService(spore.getContext(), spore.getTaskManager());
// Register service (endpoints and tasks will be registered when begin() is called)
spore.registerService(service);
// This triggers registerEndpoints() and registerTasks() for all services
spore.begin();
}
```
### Dynamic Service Addition
Services can be added after the framework has started:
```cpp
// Add service to running framework
MyService* newService = new MyService(spore.getContext(), spore.getTaskManager());
spore.registerService(newService); // Immediately registers endpoints and tasks
```
## Task Registration Methods ## Task Registration Methods
### Using std::bind with Member Functions (Recommended) ### Using std::bind with Member Functions (Recommended)
@@ -153,7 +218,62 @@ taskManager.registerTask("lambda_task", 2000,
## Adding Custom Tasks ## Adding Custom Tasks
### Method 1: Using std::bind (Recommended) ### Method 1: Service Interface (Recommended)
1. **Create your service class implementing the Service interface**:
```cpp
class SensorService : public Service {
public:
SensorService(NodeContext& ctx, TaskManager& taskManager)
: ctx(ctx), taskManager(taskManager) {}
void registerEndpoints(ApiServer& api) override {
api.registerEndpoint("/api/sensor/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
taskManager.registerTask("temp_read", 1000,
[this]() { readTemperature(); });
taskManager.registerTask("calibrate", 60000,
[this]() { calibrateSensors(); });
}
const char* getName() const override { return "SensorService"; }
private:
NodeContext& ctx;
TaskManager& taskManager;
void readTemperature() {
// Read sensor logic
Serial.println("Reading temperature");
}
void calibrateSensors() {
// Calibration logic
Serial.println("Calibrating sensors");
}
void handleStatus(AsyncWebServerRequest* request) {
// Handle status request
}
};
```
2. **Register with Spore framework**:
```cpp
void setup() {
spore.setup();
SensorService* sensorService = new SensorService(spore.getContext(), spore.getTaskManager());
spore.registerService(sensorService);
spore.begin(); // This will call registerTasks() automatically
}
```
### Method 2: Direct TaskManager Registration
1. **Create your service class**: 1. **Create your service class**:
```cpp ```cpp
@@ -181,7 +301,7 @@ taskManager.registerTask("lambda_task", 2000,
std::bind(&SensorService::calibrateSensors, &sensors)); std::bind(&SensorService::calibrateSensors, &sensors));
``` ```
### Method 2: Traditional Functions ### Method 3: Traditional Functions
1. **Define your task function**: 1. **Define your task function**:
```cpp ```cpp
@@ -308,13 +428,55 @@ curl -X POST http://192.168.1.100/api/tasks/control \
## Best Practices ## Best Practices
1. **Use std::bind for member functions**: Cleaner than wrapper functions 1. **Use Service Interface**: Implement the Service interface for clean integration with the framework
2. **Group related tasks**: Register multiple related operations in a single task 2. **Group related tasks**: Register multiple related operations in a single service
3. **Monitor task health**: Use the status API to monitor task performance 3. **Monitor task health**: Use the status API to monitor task performance
4. **Plan intervals carefully**: Balance responsiveness with system resources 4. **Plan intervals carefully**: Balance responsiveness with system resources
5. **Use descriptive names**: Make task names clear and meaningful 5. **Use descriptive names**: Make task names clear and meaningful
6. **Separate concerns**: Use registerEndpoints() for HTTP API and registerTasks() for background work
7. **Dependency injection**: Pass required dependencies (NodeContext, TaskManager) to service constructors
## Migration from Wrapper Functions ## Migration to Service Interface
### Before (manual task registration in constructor):
```cpp
class MyService : public Service {
public:
MyService(TaskManager& taskManager) : taskManager(taskManager) {
// Tasks registered in constructor
taskManager.registerTask("heartbeat", 2000, [this]() { sendHeartbeat(); });
}
void registerEndpoints(ApiServer& api) override {
// Only endpoints registered here
}
};
```
### After (using Service interface):
```cpp
class MyService : public Service {
public:
MyService(TaskManager& taskManager) : taskManager(taskManager) {
// No task registration in constructor
}
void registerEndpoints(ApiServer& api) override {
// Register HTTP endpoints
api.registerEndpoint("/api/my/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
// Register background tasks
taskManager.registerTask("heartbeat", 2000, [this]() { sendHeartbeat(); });
}
const char* getName() const override { return "MyService"; }
};
```
### Migration from Wrapper Functions
### Before (with wrapper functions): ### Before (with wrapper functions):
```cpp ```cpp
@@ -335,11 +497,11 @@ taskManager.registerTask("cluster_listen", interval,
## Compatibility ## Compatibility
- The new `std::bind` support is fully backward compatible - The new Service interface is fully backward compatible
- Existing code using function pointers will continue to work - Existing code using direct TaskManager registration will continue to work
- You can mix both approaches in the same project - You can mix Service interface and direct registration in the same project
- All existing TaskManager methods remain unchanged - All existing TaskManager methods remain unchanged
- New status monitoring methods are additive and don't break existing functionality - The Service interface provides a cleaner, more organized approach for framework integration
## Related Documentation ## Related Documentation

View File

@@ -35,15 +35,14 @@ MultiMatrixService::MultiMatrixService(NodeContext& ctx, TaskManager& taskManage
} else { } else {
LOG_ERROR("MultiMatrixService", "Failed to initialize DFPlayer"); LOG_ERROR("MultiMatrixService", "Failed to initialize DFPlayer");
} }
registerTasks();
} }
void MultiMatrixService::registerEndpoints(ApiServer& api) { void MultiMatrixService::registerEndpoints(ApiServer& api) {
api.addEndpoint(API_STATUS_ENDPOINT, HTTP_GET, api.registerEndpoint(API_STATUS_ENDPOINT, HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint(API_CONTROL_ENDPOINT, HTTP_POST, api.registerEndpoint(API_CONTROL_ENDPOINT, HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("action"), true, String("body"), String("string"), ParamSpec{String("action"), true, String("body"), String("string"),
@@ -144,8 +143,8 @@ void MultiMatrixService::setLoop(bool enabled) {
LOG_INFO("MultiMatrixService", String("Loop ") + (enabled ? "enabled" : "disabled")); LOG_INFO("MultiMatrixService", String("Loop ") + (enabled ? "enabled" : "disabled"));
} }
void MultiMatrixService::registerTasks() { void MultiMatrixService::registerTasks(TaskManager& taskManager) {
m_taskManager.registerTask("multimatrix_potentiometer", POTENTIOMETER_SAMPLE_INTERVAL_MS, taskManager.registerTask("multimatrix_potentiometer", POTENTIOMETER_SAMPLE_INTERVAL_MS,
[this]() { pollPotentiometer(); }); [this]() { pollPotentiometer(); });
} }

View File

@@ -14,6 +14,7 @@ class MultiMatrixService : public Service {
public: public:
MultiMatrixService(NodeContext& ctx, TaskManager& taskManager, uint8_t rxPin, uint8_t txPin, uint8_t potentiometerPin); MultiMatrixService(NodeContext& ctx, TaskManager& taskManager, uint8_t rxPin, uint8_t txPin, uint8_t potentiometerPin);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "MultiMatrixAudio"; } const char* getName() const override { return "MultiMatrixAudio"; }
bool isReady() const; bool isReady() const;
@@ -34,7 +35,6 @@ private:
static constexpr uint8_t MAX_VOLUME = 30; static constexpr uint8_t MAX_VOLUME = 30;
static constexpr uint8_t POT_VOLUME_EPSILON = 2; static constexpr uint8_t POT_VOLUME_EPSILON = 2;
void registerTasks();
void pollPotentiometer(); void pollPotentiometer();
void handleStatusRequest(AsyncWebServerRequest* request); void handleStatusRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request); void handleControlRequest(AsyncWebServerRequest* request);

View File

@@ -42,7 +42,7 @@ void setup() {
pixelController->begin(); pixelController->begin();
audioService = std::make_shared<MultiMatrixService>(spore.getContext(), spore.getTaskManager(), MP3PLAYER_PIN_RX, MP3PLAYER_PIN_TX, POTENTIOMETER_PIN); audioService = std::make_shared<MultiMatrixService>(spore.getContext(), spore.getTaskManager(), MP3PLAYER_PIN_RX, MP3PLAYER_PIN_TX, POTENTIOMETER_PIN);
spore.addService(audioService); spore.registerService(audioService);
spore.begin(); spore.begin();

View File

@@ -34,7 +34,6 @@ NeoPatternService::NeoPatternService(NodeContext& ctx, TaskManager& taskMgr, con
neoPattern->Direction = static_cast<::direction>(direction); neoPattern->Direction = static_cast<::direction>(direction);
registerPatterns(); registerPatterns();
registerTasks();
registerEventHandlers(); registerEventHandlers();
initialized = true; initialized = true;
@@ -49,17 +48,17 @@ NeoPatternService::~NeoPatternService() {
void NeoPatternService::registerEndpoints(ApiServer& api) { void NeoPatternService::registerEndpoints(ApiServer& api) {
// Status endpoint // Status endpoint
api.addEndpoint("/api/neopattern/status", HTTP_GET, api.registerEndpoint("/api/neopattern/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Patterns list endpoint // Patterns list endpoint
api.addEndpoint("/api/neopattern/patterns", HTTP_GET, api.registerEndpoint("/api/neopattern/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Control endpoint // Control endpoint
api.addEndpoint("/api/neopattern", HTTP_POST, api.registerEndpoint("/api/neopattern", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()},
@@ -73,7 +72,7 @@ void NeoPatternService::registerEndpoints(ApiServer& api) {
}); });
// State endpoint for complex state updates // State endpoint for complex state updates
api.addEndpoint("/api/neopattern/state", HTTP_POST, api.registerEndpoint("/api/neopattern/state", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleStateRequest(request); }, [this](AsyncWebServerRequest* request) { handleStateRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
} }
@@ -403,7 +402,7 @@ NeoPatternState NeoPatternService::getState() const {
return currentState; return currentState;
} }
void NeoPatternService::registerTasks() { void NeoPatternService::registerTasks(TaskManager& taskManager) {
taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); }); taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); });
taskManager.registerTask("neopattern_status_print", 10000, [this]() { taskManager.registerTask("neopattern_status_print", 10000, [this]() {
LOG_INFO("NeoPattern", "Status update"); LOG_INFO("NeoPattern", "Status update");

View File

@@ -30,6 +30,7 @@ public:
~NeoPatternService(); ~NeoPatternService();
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "NeoPattern"; } const char* getName() const override { return "NeoPattern"; }
// Pattern control methods // Pattern control methods
@@ -47,7 +48,6 @@ public:
NeoPatternState getState() const; NeoPatternState getState() const;
private: private:
void registerTasks();
void registerPatterns(); void registerPatterns();
void update(); void update();
void registerEventHandlers(); void registerEventHandlers();

View File

@@ -46,7 +46,7 @@ void setup() {
// Create and add custom service // Create and add custom service
neoPatternService = new NeoPatternService(spore.getContext(), spore.getTaskManager(), config); neoPatternService = new NeoPatternService(spore.getContext(), spore.getTaskManager(), config);
spore.addService(neoPatternService); spore.registerService(neoPatternService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();

View File

@@ -33,7 +33,7 @@ void setup() {
spore.setup(); spore.setup();
relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN); relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService); spore.registerService(relayService);
spore.begin(); spore.begin();
} }

View File

@@ -7,15 +7,14 @@ RelayService::RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin)
pinMode(relayPin, OUTPUT); pinMode(relayPin, OUTPUT);
// Many relay modules are active LOW. Start in OFF state (relay de-energized). // Many relay modules are active LOW. Start in OFF state (relay de-energized).
digitalWrite(relayPin, HIGH); digitalWrite(relayPin, HIGH);
registerTasks();
} }
void RelayService::registerEndpoints(ApiServer& api) { void RelayService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/relay/status", HTTP_GET, api.registerEndpoint("/api/relay/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/relay", HTTP_POST, api.registerEndpoint("/api/relay", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("state"), true, String("body"), String("string"), ParamSpec{String("state"), true, String("body"), String("string"),
@@ -82,7 +81,7 @@ void RelayService::toggle() {
} }
} }
void RelayService::registerTasks() { void RelayService::registerTasks(TaskManager& taskManager) {
taskManager.registerTask("relay_status_print", 5000, [this]() { taskManager.registerTask("relay_status_print", 5000, [this]() {
LOG_INFO("RelayService", "Status - pin: " + String(relayPin) + ", state: " + (relayOn ? "ON" : "OFF")); LOG_INFO("RelayService", "Status - pin: " + String(relayPin) + ", state: " + (relayOn ? "ON" : "OFF"));
}); });

View File

@@ -8,6 +8,7 @@ class RelayService : public Service {
public: public:
RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin); RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Relay"; } const char* getName() const override { return "Relay"; }
void turnOn(); void turnOn();
@@ -15,8 +16,6 @@ public:
void toggle(); void toggle();
private: private:
void registerTasks();
NodeContext& ctx; NodeContext& ctx;
TaskManager& taskManager; TaskManager& taskManager;
int relayPin; int relayPin;

View File

@@ -23,7 +23,7 @@ void setup() {
// Create and add custom service // Create and add custom service
relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN); relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService); spore.registerService(relayService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();

View File

@@ -1,9 +1,12 @@
#pragma once #pragma once
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
class TaskManager;
class Service { class Service {
public: public:
virtual ~Service() = default; virtual ~Service() = default;
virtual void registerEndpoints(ApiServer& api) = 0; virtual void registerEndpoints(ApiServer& api) = 0;
virtual void registerTasks(TaskManager& taskManager) = 0;
virtual const char* getName() const = 0; virtual const char* getName() const = 0;
}; };

View File

@@ -25,8 +25,8 @@ public:
void loop(); void loop();
// Service management // Service management
void addService(std::shared_ptr<Service> service); void registerService(std::shared_ptr<Service> service);
void addService(Service* service); void registerService(Service* service);
// Access to core components // Access to core components
NodeContext& getContext() { return ctx; } NodeContext& getContext() { return ctx; }

View File

@@ -19,14 +19,14 @@ 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 registerService(Service& service);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler); void registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void registerEndpoint(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);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void registerEndpoint(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 registerEndpoint(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);

View File

@@ -7,6 +7,7 @@ class ClusterService : public Service {
public: public:
ClusterService(NodeContext& ctx); ClusterService(NodeContext& ctx);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Cluster"; } const char* getName() const override { return "Cluster"; }
private: private:

View File

@@ -7,6 +7,7 @@ class MonitoringService : public Service {
public: public:
MonitoringService(CpuUsage& cpuUsage); MonitoringService(CpuUsage& cpuUsage);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Monitoring"; } const char* getName() const override { return "Monitoring"; }
// System resource information // System resource information

View File

@@ -7,6 +7,7 @@ class NetworkService : public Service {
public: public:
NetworkService(NetworkManager& networkManager); NetworkService(NetworkManager& networkManager);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Network"; } const char* getName() const override { return "Network"; }
private: private:

View File

@@ -8,6 +8,7 @@ class NodeService : public Service {
public: public:
NodeService(NodeContext& ctx, ApiServer& apiServer); NodeService(NodeContext& ctx, ApiServer& apiServer);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Node"; } const char* getName() const override { return "Node"; }
private: private:

View File

@@ -9,6 +9,7 @@ class StaticFileService : public Service {
public: public:
StaticFileService(NodeContext& ctx, ApiServer& apiServer); StaticFileService(NodeContext& ctx, ApiServer& apiServer);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return name.c_str(); } const char* getName() const override { return name.c_str(); }
private: private:

View File

@@ -7,6 +7,7 @@ class TaskService : public Service {
public: public:
TaskService(TaskManager& taskManager); TaskService(TaskManager& taskManager);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Task"; } const char* getName() const override { return "Task"; }
private: private:

View File

@@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
default_envs = base ;default_envs = base
src_dir = . src_dir = .
data_dir = ${PROJECT_DIR}/examples/${PIOENV}/data data_dir = ${PROJECT_DIR}/examples/${PIOENV}/data

View File

@@ -85,7 +85,7 @@ void Spore::loop() {
yield(); yield();
} }
void Spore::addService(std::shared_ptr<Service> service) { void Spore::registerService(std::shared_ptr<Service> service) {
if (!service) { if (!service) {
LOG_WARN("Spore", "Attempted to add null service"); LOG_WARN("Spore", "Attempted to add null service");
return; return;
@@ -95,21 +95,22 @@ void Spore::addService(std::shared_ptr<Service> service) {
if (apiServerStarted) { if (apiServerStarted) {
// If API server is already started, register the service immediately // If API server is already started, register the service immediately
apiServer.addService(*service); apiServer.registerService(*service);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to running API server"); service->registerTasks(taskManager);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to running API server and task manager");
} else { } else {
LOG_INFO("Spore", "Registered service '" + String(service->getName()) + "' (will be added to API server when begin() is called)"); LOG_INFO("Spore", "Registered service '" + String(service->getName()) + "' (will be added to API server when begin() is called)");
} }
} }
void Spore::addService(Service* service) { void Spore::registerService(Service* service) {
if (!service) { if (!service) {
LOG_WARN("Spore", "Attempted to add null service"); LOG_WARN("Spore", "Attempted to add null service");
return; return;
} }
// Wrap raw pointer in shared_ptr with no-op deleter to avoid double-delete // Wrap raw pointer in shared_ptr with no-op deleter to avoid double-delete
addService(std::shared_ptr<Service>(service, [](Service*){})); registerService(std::shared_ptr<Service>(service, [](Service*){}));
} }
@@ -155,11 +156,12 @@ void Spore::startApiServer() {
LOG_INFO("Spore", "Starting API server..."); LOG_INFO("Spore", "Starting API server...");
// Register all services with API server // Register all services with API server and task manager
for (auto& service : services) { for (auto& service : services) {
if (service) { if (service) {
apiServer.addService(*service); apiServer.registerService(*service);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to API server"); service->registerTasks(taskManager);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to API server and task manager");
} }
} }

View File

@@ -31,7 +31,7 @@ void ApiServer::registerEndpoint(const String& uri, int method,
} }
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) { void ApiServer::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
if (!services.empty()) { if (!services.empty()) {
@@ -41,7 +41,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler); server.on(uri.c_str(), method, requestHandler);
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void ApiServer::registerEndpoint(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) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
@@ -53,7 +53,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::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params) { const std::vector<ParamSpec>& params) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
@@ -64,7 +64,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler); server.on(uri.c_str(), method, requestHandler);
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void ApiServer::registerEndpoint(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) {
// Get current service name if available // Get current service name if available
@@ -76,7 +76,7 @@ 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);
} }
void ApiServer::addService(Service& service) { void ApiServer::registerService(Service& service) {
services.push_back(service); services.push_back(service);
LOG_INFO("API", "Added service: " + String(service.getName())); LOG_INFO("API", "Added service: " + String(service.getName()));
} }

View File

@@ -4,12 +4,12 @@
ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {} ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {}
void ClusterService::registerEndpoints(ApiServer& api) { void ClusterService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/cluster/members", HTTP_GET, api.registerEndpoint("/api/cluster/members", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleMembersRequest(request); }, [this](AsyncWebServerRequest* request) { handleMembersRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Generic cluster broadcast endpoint // Generic cluster broadcast endpoint
api.addEndpoint("/api/cluster/event", HTTP_POST, api.registerEndpoint("/api/cluster/event", HTTP_POST,
[this](AsyncWebServerRequest* request) { [this](AsyncWebServerRequest* request) {
if (!request->hasParam("event", true) || !request->hasParam("payload", true)) { if (!request->hasParam("event", true) || !request->hasParam("payload", true)) {
request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}"); request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}");
@@ -32,6 +32,10 @@ void ClusterService::registerEndpoints(ApiServer& api) {
}); });
} }
void ClusterService::registerTasks(TaskManager& taskManager) {
// ClusterService doesn't register any tasks itself
}
void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) { void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc["members"].to<JsonArray>(); JsonArray arr = doc["members"].to<JsonArray>();

View File

@@ -10,11 +10,15 @@ MonitoringService::MonitoringService(CpuUsage& cpuUsage)
} }
void MonitoringService::registerEndpoints(ApiServer& api) { void MonitoringService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/monitoring/resources", HTTP_GET, api.registerEndpoint("/api/monitoring/resources", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleResourcesRequest(request); }, [this](AsyncWebServerRequest* request) { handleResourcesRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
} }
void MonitoringService::registerTasks(TaskManager& taskManager) {
// MonitoringService doesn't register any tasks itself
}
MonitoringService::SystemResources MonitoringService::getSystemResources() const { MonitoringService::SystemResources MonitoringService::getSystemResources() const {
SystemResources resources; SystemResources resources;

View File

@@ -6,20 +6,20 @@ NetworkService::NetworkService(NetworkManager& networkManager)
void NetworkService::registerEndpoints(ApiServer& api) { void NetworkService::registerEndpoints(ApiServer& api) {
// WiFi scanning endpoints // WiFi scanning endpoints
api.addEndpoint("/api/network/wifi/scan", HTTP_POST, api.registerEndpoint("/api/network/wifi/scan", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleWifiScanRequest(request); }, [this](AsyncWebServerRequest* request) { handleWifiScanRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/network/wifi/scan", HTTP_GET, api.registerEndpoint("/api/network/wifi/scan", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleGetWifiNetworks(request); }, [this](AsyncWebServerRequest* request) { handleGetWifiNetworks(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Network status and configuration endpoints // Network status and configuration endpoints
api.addEndpoint("/api/network/status", HTTP_GET, api.registerEndpoint("/api/network/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleNetworkStatus(request); }, [this](AsyncWebServerRequest* request) { handleNetworkStatus(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/network/wifi/config", HTTP_POST, api.registerEndpoint("/api/network/wifi/config", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleSetWifiConfig(request); }, [this](AsyncWebServerRequest* request) { handleSetWifiConfig(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("ssid"), true, String("body"), String("string"), {}, String("")}, ParamSpec{String("ssid"), true, String("body"), String("string"), {}, String("")},
@@ -29,6 +29,10 @@ void NetworkService::registerEndpoints(ApiServer& api) {
}); });
} }
void NetworkService::registerTasks(TaskManager& taskManager) {
// NetworkService doesn't register any tasks itself
}
void NetworkService::handleWifiScanRequest(AsyncWebServerRequest* request) { void NetworkService::handleWifiScanRequest(AsyncWebServerRequest* request) {
networkManager.scanWifi(); networkManager.scanWifi();

View File

@@ -6,12 +6,12 @@ NodeService::NodeService(NodeContext& ctx, ApiServer& apiServer) : ctx(ctx), api
void NodeService::registerEndpoints(ApiServer& api) { void NodeService::registerEndpoints(ApiServer& api) {
// Status endpoint // Status endpoint
api.addEndpoint("/api/node/status", HTTP_GET, api.registerEndpoint("/api/node/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Update endpoint with file upload // Update endpoint with file upload
api.addEndpoint("/api/node/update", HTTP_POST, api.registerEndpoint("/api/node/update", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleUpdateRequest(request); }, [this](AsyncWebServerRequest* request) { handleUpdateRequest(request); },
[this](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) { [this](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) {
handleUpdateUpload(request, filename, index, data, len, final); handleUpdateUpload(request, filename, index, data, len, final);
@@ -21,17 +21,17 @@ void NodeService::registerEndpoints(ApiServer& api) {
}); });
// Restart endpoint // Restart endpoint
api.addEndpoint("/api/node/restart", HTTP_POST, api.registerEndpoint("/api/node/restart", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleRestartRequest(request); }, [this](AsyncWebServerRequest* request) { handleRestartRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Endpoints endpoint // Endpoints endpoint
api.addEndpoint("/api/node/endpoints", HTTP_GET, api.registerEndpoint("/api/node/endpoints", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleEndpointsRequest(request); }, [this](AsyncWebServerRequest* request) { handleEndpointsRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Generic local event endpoint // Generic local event endpoint
api.addEndpoint("/api/node/event", HTTP_POST, api.registerEndpoint("/api/node/event", HTTP_POST,
[this](AsyncWebServerRequest* request) { [this](AsyncWebServerRequest* request) {
if (!request->hasParam("event", true) || !request->hasParam("payload", true)) { if (!request->hasParam("event", true) || !request->hasParam("payload", true)) {
request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}"); request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}");
@@ -49,6 +49,10 @@ void NodeService::registerEndpoints(ApiServer& api) {
}); });
} }
void NodeService::registerTasks(TaskManager& taskManager) {
// NodeService doesn't register any tasks itself
}
void NodeService::handleStatusRequest(AsyncWebServerRequest* request) { void NodeService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
doc["freeHeap"] = ESP.getFreeHeap(); doc["freeHeap"] = ESP.getFreeHeap();

View File

@@ -20,3 +20,7 @@ void StaticFileService::registerEndpoints(ApiServer& api) {
api.serveStatic("/", LittleFS, "/public", "max-age=3600"); api.serveStatic("/", LittleFS, "/public", "max-age=3600");
} }
void StaticFileService::registerTasks(TaskManager& taskManager) {
// StaticFileService doesn't register any tasks itself
}

View File

@@ -5,11 +5,11 @@
TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {} TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {}
void TaskService::registerEndpoints(ApiServer& api) { void TaskService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/tasks/status", HTTP_GET, api.registerEndpoint("/api/tasks/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/tasks/control", HTTP_POST, api.registerEndpoint("/api/tasks/control", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{ ParamSpec{
@@ -31,6 +31,10 @@ void TaskService::registerEndpoints(ApiServer& api) {
}); });
} }
void TaskService::registerTasks(TaskManager& taskManager) {
// TaskService doesn't register any tasks itself - it manages other tasks
}
void TaskService::handleStatusRequest(AsyncWebServerRequest* request) { void TaskService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument scratch; JsonDocument scratch;
auto taskStatuses = taskManager.getAllTaskStatuses(scratch); auto taskStatuses = taskManager.getAllTaskStatuses(scratch);