14 KiB
Task Management System
The SPORE system includes a comprehensive TaskManager that provides a clean interface for managing system tasks. This makes it easy to add, configure, and control background tasks without cluttering the main application code.
Overview
The TaskManager system provides:
- Easy Task Registration: Simple API for adding new tasks with configurable intervals
- Dynamic Control: Enable/disable tasks at runtime
- Interval Management: Change task execution frequency on the fly
- Status Monitoring: View task status and configuration
- Automatic Lifecycle: Tasks are automatically managed and executed
Service Interface Integration
Services now implement a unified interface for both endpoint and task registration:
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
#include "TaskManager.h"
// Create task manager
TaskManager taskManager(ctx);
// Register tasks
taskManager.registerTask("heartbeat", 2000, heartbeatFunction);
taskManager.registerTask("maintenance", 30000, maintenanceFunction);
// Initialize and start all tasks
taskManager.initialize();
Service Lifecycle
The Spore framework automatically manages service registration and task lifecycle:
Service Registration Process
- Service Creation: Services are created with required dependencies (NodeContext, TaskManager, etc.)
- Service Registration: Services are registered with the Spore framework via
spore.registerService() - Endpoint Registration: When
spore.begin()is called,registerEndpoints()is called for each service - Task Registration: Simultaneously,
registerTasks()is called for each service - Task Initialization: The TaskManager initializes all registered tasks
- Execution: Tasks run in the main loop when their intervals elapse
Framework Integration
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:
// Add service to running framework
MyService* newService = new MyService(spore.getContext(), spore.getTaskManager());
spore.registerService(newService); // Immediately registers endpoints and tasks
Task Registration Methods
Using std::bind with Member Functions (Recommended)
#include <functional>
#include "TaskManager.h"
class MyService {
public:
void sendHeartbeat() {
Serial.println("Service heartbeat");
}
void performMaintenance() {
Serial.println("Running maintenance");
}
};
MyService service;
TaskManager taskManager(ctx);
// Register member functions using std::bind
taskManager.registerTask("heartbeat", 2000,
std::bind(&MyService::sendHeartbeat, &service));
taskManager.registerTask("maintenance", 30000,
std::bind(&MyService::performMaintenance, &service));
// Initialize and start all tasks
taskManager.initialize();
Using Lambda Functions
// Register lambda functions directly
taskManager.registerTask("counter", 1000, []() {
static int count = 0;
Serial.printf("Count: %d\n", ++count);
});
// Lambda with capture
int threshold = 100;
taskManager.registerTask("monitor", 5000, [&threshold]() {
if (ESP.getFreeHeap() < threshold) {
Serial.println("Low memory warning!");
}
});
Complex Task Registration
class NetworkManager {
public:
void checkConnection() { /* ... */ }
void sendData(String data) { /* ... */ }
};
NetworkManager network;
// Multiple operations in one task
taskManager.registerTask("network_ops", 3000,
std::bind([](NetworkManager* net) {
net->checkConnection();
net->sendData("status_update");
}, &network));
Task Control API
Basic Operations
// Enable/disable tasks
taskManager.enableTask("heartbeat");
taskManager.disableTask("maintenance");
// Change intervals
taskManager.setTaskInterval("heartbeat", 5000); // 5 seconds
// Check status
bool isRunning = taskManager.isTaskEnabled("heartbeat");
unsigned long interval = taskManager.getTaskInterval("heartbeat");
// Print all task statuses
taskManager.printTaskStatus();
Task Lifecycle Management
// Start/stop tasks
taskManager.startTask("heartbeat");
taskManager.stopTask("discovery");
// Bulk operations
taskManager.enableAllTasks();
taskManager.disableAllTasks();
Task Configuration Options
When registering tasks, you can specify:
- Name: Unique identifier for the task
- Interval: Execution frequency in milliseconds
- Callback: Function, bound method, or lambda to execute
- Enabled: Whether the task starts enabled (default: true)
- AutoStart: Whether to start automatically (default: true)
// Traditional function
taskManager.registerTask("delayed_task", 5000, taskFunction, true, false);
// Member function with std::bind
taskManager.registerTask("service_task", 3000,
std::bind(&Service::method, &instance), true, false);
// Lambda function
taskManager.registerTask("lambda_task", 2000,
[]() { Serial.println("Lambda!"); }, true, false);
Adding Custom Tasks
Method 1: Service Interface (Recommended)
-
Create your service class implementing the Service interface:
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 } }; -
Register with Spore framework:
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
-
Create your service class:
class SensorService { public: void readTemperature() { // Read sensor logic Serial.println("Reading temperature"); } void calibrateSensors() { // Calibration logic Serial.println("Calibrating sensors"); } }; -
Register with TaskManager:
SensorService sensors; taskManager.registerTask("temp_read", 1000, std::bind(&SensorService::readTemperature, &sensors)); taskManager.registerTask("calibrate", 60000, std::bind(&SensorService::calibrateSensors, &sensors));
Method 3: Traditional Functions
-
Define your task function:
void myCustomTask() { // Your task logic here Serial.println("Custom task executed"); } -
Register with TaskManager:
taskManager.registerTask("my_task", 10000, myCustomTask);
Enhanced TaskManager Capabilities
Task Status Monitoring
- Real-time Status: Check enabled/disabled state and running status
- Performance Metrics: Monitor execution intervals and timing
- System Integration: View task status alongside system resources
- Bulk Operations: Get status of all tasks at once
Task Control Features
- Runtime Control: Enable/disable tasks without restart
- Dynamic Intervals: Change task execution frequency on-the-fly
- Individual Status: Get detailed information about specific tasks
- Health Monitoring: Track task health and system resources
Remote Task Management
The TaskManager integrates with the API server to provide comprehensive remote task control and monitoring.
Task Status Overview
Get a complete overview of all tasks and system status:
# Get comprehensive task status
curl http://192.168.1.100/api/tasks/status
Response includes:
- Summary: Total task count and active task count
- Task Details: Individual status for each task (name, interval, enabled, running, auto-start)
- System Info: Free heap memory and uptime
Example Response:
{
"summary": {
"totalTasks": 6,
"activeTasks": 5
},
"tasks": [
{
"name": "discovery_send",
"interval": 1000,
"enabled": true,
"running": true,
"autoStart": true
},
{
"name": "heartbeat",
"interval": 2000,
"enabled": true,
"running": true,
"autoStart": true
}
],
"system": {
"freeHeap": 48748,
"uptime": 12345
}
}
Individual Task Control
Control individual tasks with various actions:
# Control tasks
curl -X POST http://192.168.1.100/api/tasks/control \
-d "task=heartbeat&action=disable"
# Get detailed status for a specific task
curl -X POST http://192.168.1.100/api/tasks/control \
-d "task=discovery_send&action=status"
Available Actions:
enable- Enable a taskdisable- Disable a taskstart- Start a taskstop- Stop a taskstatus- Get detailed status for a specific task
Task Status Response:
{
"success": true,
"message": "Task status retrieved",
"task": "discovery_send",
"action": "status",
"taskDetails": {
"name": "discovery_send",
"enabled": true,
"running": true,
"interval": 1000,
"system": {
"freeHeap": 48748,
"uptime": 12345
}
}
}
Performance Considerations
std::bindcreates a callable object that may have a small overhead compared to direct function pointers- For high-frequency tasks, consider the performance impact
- The overhead is typically negligible for most embedded applications
- The TaskManager stores bound functions efficiently in a registry
Best Practices
- Use Service Interface: Implement the Service interface for clean integration with the framework
- Group related tasks: Register multiple related operations in a single service
- Monitor task health: Use the status API to monitor task performance
- Plan intervals carefully: Balance responsiveness with system resources
- Use descriptive names: Make task names clear and meaningful
- Separate concerns: Use registerEndpoints() for HTTP API and registerTasks() for background work
- Dependency injection: Pass required dependencies (NodeContext, TaskManager) to service constructors
Migration to Service Interface
Before (manual task registration in constructor):
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):
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):
void discoverySendTask() { cluster.sendDiscovery(); }
void clusterListenTask() { cluster.listen(); }
taskManager.registerTask("discovery_send", interval, discoverySendTask);
taskManager.registerTask("cluster_listen", interval, clusterListenTask);
After (with std::bind):
taskManager.registerTask("discovery_send", interval,
std::bind(&ClusterManager::sendDiscovery, &cluster));
taskManager.registerTask("cluster_listen", interval,
std::bind(&ClusterManager::listen, &cluster));
Compatibility
- The new Service interface is fully backward compatible
- Existing code using direct TaskManager registration will continue to work
- You can mix Service interface and direct registration in the same project
- All existing TaskManager methods remain unchanged
- The Service interface provides a cleaner, more organized approach for framework integration
Related Documentation
- TaskManager API Reference - Detailed API documentation
- API Reference - REST API for remote task management
- OpenAPI Specification - Machine-readable API specification