feat: task manager

This commit is contained in:
2025-08-21 21:52:25 +02:00
parent 953768b681
commit f80b594d21
8 changed files with 495 additions and 35 deletions

View File

@@ -228,6 +228,100 @@ The system includes automatic WiFi fallback:
2. If connection fails, creates an access point 2. If connection fails, creates an access point
3. Hostname is automatically generated from MAC address 3. Hostname is automatically generated from MAC address
## Task Management
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.
### TaskManager Features
- **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
### Basic Usage
```cpp
#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();
```
### Task Control API
```cpp
// 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();
```
### Remote Task Management
The TaskManager integrates with the API server to provide remote task control:
```bash
# Get task status
curl http://192.168.1.100/api/tasks/status
# Control tasks
curl -X POST http://192.168.1.100/api/tasks/control \
-d "task=heartbeat&action=disable"
# Available actions: enable, disable, start, stop
```
### Adding Custom Tasks
To add custom tasks to your SPORE system:
1. **Define your task function**:
```cpp
void myCustomTask() {
// Your task logic here
Serial.println("Custom task executed");
}
```
2. **Register with TaskManager**:
```cpp
taskManager.registerTask("my_task", 10000, myCustomTask);
```
3. **The TaskManager handles the rest** - automatic execution, lifecycle management, and monitoring.
### Task Configuration Options
When registering tasks, you can specify:
- **Name**: Unique identifier for the task
- **Interval**: Execution frequency in milliseconds
- **Enabled**: Whether the task starts enabled (default: true)
- **AutoStart**: Whether to start automatically (default: true)
```cpp
taskManager.registerTask("delayed_task", 5000, taskFunction, true, false);
// Creates a task that's enabled but won't auto-start
```
## Current Limitations ## Current Limitations
- WiFi credentials are hardcoded in `Config.cpp` (should be configurable) - WiFi credentials are hardcoded in `Config.cpp` (should be configurable)

View File

@@ -0,0 +1,68 @@
/*
* Task Management Example
*
* This example demonstrates how to use the TaskManager to add custom tasks
* to the SPORE system. The TaskManager provides a clean interface for
* registering, controlling, and monitoring system tasks.
*/
#include <Arduino.h>
#include "TaskManager.h"
#include "NodeContext.h"
// Example custom task functions
void customTask1() {
Serial.println("[CustomTask1] Executing custom task 1");
// Add your custom logic here
}
void customTask2() {
Serial.println("[CustomTask2] Executing custom task 2");
// Add your custom logic here
}
void periodicMaintenance() {
Serial.println("[Maintenance] Running periodic maintenance");
// Add maintenance logic here
}
void setup() {
Serial.begin(115200);
Serial.println("Task Management Example");
// Create context and task manager
NodeContext ctx;
TaskManager taskManager(ctx);
// Register custom tasks with different intervals
taskManager.registerTask("custom_task_1", 5000, customTask1); // Every 5 seconds
taskManager.registerTask("custom_task_2", 10000, customTask2); // Every 10 seconds
taskManager.registerTask("maintenance", 30000, periodicMaintenance); // Every 30 seconds
// Initialize and start all tasks
taskManager.initialize();
// Print initial task status
taskManager.printTaskStatus();
// Example: Disable a task temporarily
taskManager.disableTask("custom_task_2");
Serial.println("Disabled custom_task_2");
// Example: Change task interval
taskManager.setTaskInterval("custom_task_1", 2000); // Change to 2 seconds
Serial.println("Changed custom_task_1 interval to 2 seconds");
// Re-enable the disabled task
taskManager.enableTask("custom_task_2");
Serial.println("Re-enabled custom_task_2");
// Print updated status
taskManager.printTaskStatus();
}
void loop() {
// The TaskManager handles all task execution
// No need to call individual task functions
yield();
}

63
include/TaskManager.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <vector>
#include <functional>
#include <string>
#include "NodeContext.h"
// Forward declarations to avoid multiple definition errors
class Task;
class Scheduler;
// Define the callback type that TaskScheduler expects
using TaskCallback = void (*)();
struct TaskDefinition {
std::string name;
unsigned long interval;
TaskCallback callback;
bool enabled;
bool autoStart;
TaskDefinition(const std::string& n, unsigned long intv, TaskCallback cb, bool en = true, bool autoS = true)
: name(n), interval(intv), callback(cb), enabled(en), autoStart(autoS) {}
};
class TaskManager {
public:
TaskManager(NodeContext& ctx);
~TaskManager();
// Task registration methods
void registerTask(const std::string& name, unsigned long interval, TaskCallback callback, bool enabled = true, bool autoStart = true);
void registerTask(const TaskDefinition& taskDef);
// Task control methods
void enableTask(const std::string& name);
void disableTask(const std::string& name);
void setTaskInterval(const std::string& name, unsigned long interval);
void startTask(const std::string& name);
void stopTask(const std::string& name);
// Task status methods
bool isTaskEnabled(const std::string& name) const;
bool isTaskRunning(const std::string& name) const;
unsigned long getTaskInterval(const std::string& name) const;
// Management methods
void initialize();
void enableAllTasks();
void disableAllTasks();
void printTaskStatus() const;
// Task execution
void execute();
private:
NodeContext& ctx;
std::vector<Task*> tasks;
std::vector<TaskDefinition> taskDefinitions;
Task* findTask(const std::string& name) const;
void createTask(const TaskDefinition& taskDef);
};

View File

@@ -1,6 +1,6 @@
#include "ApiServer.h" #include "ApiServer.h"
ApiServer::ApiServer(NodeContext& ctx, uint16_t port) : server(port), ctx(ctx) {} ApiServer::ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port) : server(port), ctx(ctx), taskManager(taskMgr) {}
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) {
serviceRegistry.push_back(std::make_tuple(uri, method)); serviceRegistry.push_back(std::make_tuple(uri, method));
@@ -37,6 +37,13 @@ void ApiServer::begin() {
); );
addEndpoint("/api/node/restart", HTTP_POST, addEndpoint("/api/node/restart", HTTP_POST,
std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1)); std::bind(&ApiServer::onRestartRequest, this, std::placeholders::_1));
// Task management endpoints
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));
server.begin(); server.begin();
} }
@@ -174,3 +181,70 @@ void ApiServer::onRestartRequest(AsyncWebServerRequest *request) {
ESP.restart(); ESP.restart();
}); });
} }
void ApiServer::onTaskStatusRequest(AsyncWebServerRequest *request) {
JsonDocument doc;
JsonArray tasksArr = doc["tasks"].to<JsonArray>();
// This would need to be implemented in TaskManager to expose task status
// For now, we'll return a basic response
JsonObject taskObj = tasksArr.add<JsonObject>();
taskObj["message"] = "Task status endpoint - implementation pending";
taskObj["note"] = "Task status will be available in future versions";
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 {
success = false;
message = "Invalid action. Use: enable, disable, start, or stop";
}
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\": \"disable\"}";
String json;
serializeJson(doc, json);
request->send(400, "application/json", json);
}
}

View File

@@ -9,13 +9,14 @@
#include "NodeContext.h" #include "NodeContext.h"
#include "NodeInfo.h" #include "NodeInfo.h"
#include "TaskManager.h"
using namespace std; using namespace std;
using namespace std::placeholders; using namespace std::placeholders;
class ApiServer { class ApiServer {
public: public:
ApiServer(NodeContext& ctx, uint16_t port = 80); ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80);
void begin(); void begin();
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,
@@ -23,6 +24,7 @@ public:
private: private:
AsyncWebServer server; AsyncWebServer server;
NodeContext& ctx; NodeContext& ctx;
TaskManager& taskManager;
std::vector<std::tuple<String, int>> serviceRegistry; std::vector<std::tuple<String, int>> serviceRegistry;
void onClusterMembersRequest(AsyncWebServerRequest *request); void onClusterMembersRequest(AsyncWebServerRequest *request);
void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj); void methodToStr(const std::tuple<String, int> &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj);
@@ -30,4 +32,8 @@ private:
void onFirmwareUpdateRequest(AsyncWebServerRequest *request); void onFirmwareUpdateRequest(AsyncWebServerRequest *request);
void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void onRestartRequest(AsyncWebServerRequest *request); void onRestartRequest(AsyncWebServerRequest *request);
// Task management endpoints
void onTaskStatusRequest(AsyncWebServerRequest *request);
void onTaskControlRequest(AsyncWebServerRequest *request);
}; };

162
src/TaskManager.cpp Normal file
View File

@@ -0,0 +1,162 @@
#include "TaskManager.h"
#include <Arduino.h>
#include <TaskScheduler.h>
TaskManager::TaskManager(NodeContext& ctx) : ctx(ctx) {}
TaskManager::~TaskManager() {
// Clean up tasks
for (auto task : tasks) {
delete task;
}
tasks.clear();
}
void TaskManager::registerTask(const std::string& name, unsigned long interval, TaskCallback callback, bool enabled, bool autoStart) {
TaskDefinition taskDef(name, interval, callback, enabled, autoStart);
registerTask(taskDef);
}
void TaskManager::registerTask(const TaskDefinition& taskDef) {
taskDefinitions.push_back(taskDef);
}
void TaskManager::initialize() {
// Initialize the scheduler
ctx.scheduler->init();
// Create all registered tasks
for (const auto& taskDef : taskDefinitions) {
createTask(taskDef);
}
// Enable tasks that should auto-start
for (const auto& taskDef : taskDefinitions) {
if (taskDef.autoStart && taskDef.enabled) {
enableTask(taskDef.name);
}
}
}
void TaskManager::createTask(const TaskDefinition& taskDef) {
// Create new task
Task* task = new Task(0, TASK_FOREVER, taskDef.callback);
task->setInterval(taskDef.interval);
// Add to scheduler
ctx.scheduler->addTask(*task);
// Store task pointer
tasks.push_back(task);
Serial.printf("[TaskManager] Created task: %s (interval: %lu ms)\n",
taskDef.name.c_str(), taskDef.interval);
}
void TaskManager::enableTask(const std::string& name) {
Task* task = findTask(name);
if (task) {
task->enable();
Serial.printf("[TaskManager] Enabled task: %s\n", name.c_str());
} else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str());
}
}
void TaskManager::disableTask(const std::string& name) {
Task* task = findTask(name);
if (task) {
task->disable();
Serial.printf("[TaskManager] Disabled task: %s\n", name.c_str());
} else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str());
}
}
void TaskManager::setTaskInterval(const std::string& name, unsigned long interval) {
Task* task = findTask(name);
if (task) {
task->setInterval(interval);
Serial.printf("[TaskManager] Set interval for task %s: %lu ms\n", name.c_str(), interval);
} else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str());
}
}
void TaskManager::startTask(const std::string& name) {
Task* task = findTask(name);
if (task) {
task->enable();
Serial.printf("[TaskManager] Started task: %s\n", name.c_str());
} else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str());
}
}
void TaskManager::stopTask(const std::string& name) {
Task* task = findTask(name);
if (task) {
task->disable();
Serial.printf("[TaskManager] Stopped task: %s\n", name.c_str());
} else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str());
}
}
bool TaskManager::isTaskEnabled(const std::string& name) const {
Task* task = findTask(name);
return task ? task->isEnabled() : false;
}
bool TaskManager::isTaskRunning(const std::string& name) const {
Task* task = findTask(name);
return task ? task->isEnabled() : false;
}
unsigned long TaskManager::getTaskInterval(const std::string& name) const {
Task* task = findTask(name);
return task ? task->getInterval() : 0;
}
void TaskManager::enableAllTasks() {
for (auto task : tasks) {
task->enable();
}
Serial.println("[TaskManager] Enabled all tasks");
}
void TaskManager::disableAllTasks() {
for (auto task : tasks) {
task->disable();
}
Serial.println("[TaskManager] Disabled all tasks");
}
void TaskManager::printTaskStatus() const {
Serial.println("\n[TaskManager] Task Status:");
Serial.println("==========================");
for (size_t i = 0; i < tasks.size() && i < taskDefinitions.size(); ++i) {
const auto& taskDef = taskDefinitions[i];
const auto& task = tasks[i];
Serial.printf(" %s: %s (interval: %lu ms)\n",
taskDef.name.c_str(),
task->isEnabled() ? "ENABLED" : "DISABLED",
task->getInterval());
}
Serial.println("==========================\n");
}
void TaskManager::execute() {
ctx.scheduler->execute();
}
Task* TaskManager::findTask(const std::string& name) const {
for (size_t i = 0; i < tasks.size() && i < taskDefinitions.size(); ++i) {
if (taskDefinitions[i].name == name) {
return tasks[i];
}
}
return nullptr;
}

View File

@@ -1,5 +0,0 @@
/*
* https://github.com/arkhipenko/TaskScheduler/tree/master/examples/Scheduler_example16_Multitab
*/
#include <TaskScheduler.h>

View File

@@ -4,47 +4,45 @@
#include "NetworkManager.h" #include "NetworkManager.h"
#include "ClusterManager.h" #include "ClusterManager.h"
#include "ApiServer.h" #include "ApiServer.h"
#include "TaskManager.h"
NodeContext ctx; NodeContext ctx;
NetworkManager network(ctx); NetworkManager network(ctx);
ClusterManager cluster(ctx); ClusterManager cluster(ctx);
ApiServer apiServer(ctx, ctx.config.api_server_port); TaskManager taskManager(ctx);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
Task tSendDiscovery(0, TASK_FOREVER, [](){ cluster.sendDiscovery(); }); // Task callback wrapper functions
Task tListenForDiscovery(0, TASK_FOREVER, [](){ cluster.listenForDiscovery(); }); void discoverySendTask() { cluster.sendDiscovery(); }
Task tUpdateStatus(0, TASK_FOREVER, [](){ cluster.updateAllNodeStatuses(); cluster.removeDeadNodes(); }); void discoveryListenTask() { cluster.listenForDiscovery(); }
Task tPrintMemberList(0, TASK_FOREVER, [](){ cluster.printMemberList(); }); void statusUpdateTask() { cluster.updateAllNodeStatuses(); cluster.removeDeadNodes(); }
Task tHeartbeat(0, TASK_FOREVER, [](){ cluster.heartbeatTaskCallback(); }); void printMembersTask() { cluster.printMemberList(); }
Task tUpdateAllMembersInfo(0, TASK_FOREVER, [](){ cluster.updateAllMembersInfoTaskCallback(); }); void heartbeatTask() { cluster.heartbeatTaskCallback(); }
void updateMembersInfoTask() { cluster.updateAllMembersInfoTaskCallback(); }
void setup() { void setup() {
// Setup WiFi first
network.setupWiFi(); network.setupWiFi();
ctx.scheduler->init();
// Set task intervals from config // Register all system tasks
tSendDiscovery.setInterval(ctx.config.discovery_interval_ms); taskManager.registerTask("discovery_send", ctx.config.discovery_interval_ms, discoverySendTask);
tListenForDiscovery.setInterval(ctx.config.discovery_interval_ms / 10); // Listen more frequently taskManager.registerTask("discovery_listen", ctx.config.discovery_interval_ms / 10, discoveryListenTask);
tUpdateStatus.setInterval(ctx.config.status_update_interval_ms); taskManager.registerTask("status_update", ctx.config.status_update_interval_ms, statusUpdateTask);
tPrintMemberList.setInterval(ctx.config.print_interval_ms); taskManager.registerTask("print_members", ctx.config.print_interval_ms, printMembersTask);
tHeartbeat.setInterval(ctx.config.heartbeat_interval_ms); taskManager.registerTask("heartbeat", ctx.config.heartbeat_interval_ms, heartbeatTask);
tUpdateAllMembersInfo.setInterval(ctx.config.member_info_update_interval_ms); taskManager.registerTask("update_members_info", ctx.config.member_info_update_interval_ms, updateMembersInfoTask);
ctx.scheduler->addTask(tSendDiscovery); // Initialize and start all tasks
ctx.scheduler->addTask(tListenForDiscovery); taskManager.initialize();
ctx.scheduler->addTask(tUpdateStatus);
ctx.scheduler->addTask(tPrintMemberList); // Start the API server
ctx.scheduler->addTask(tHeartbeat);
ctx.scheduler->addTask(tUpdateAllMembersInfo);
tSendDiscovery.enable();
tListenForDiscovery.enable();
tUpdateStatus.enable();
tPrintMemberList.enable();
tHeartbeat.enable();
tUpdateAllMembersInfo.enable();
apiServer.begin(); apiServer.begin();
// Print initial task status
taskManager.printTaskStatus();
} }
void loop() { void loop() {
ctx.scheduler->execute(); taskManager.execute();
yield(); yield();
} }