From f80b594d21cdffbbcbf561e7fb736b5a42279390 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 21 Aug 2025 21:52:25 +0200 Subject: [PATCH] feat: task manager --- README.md | 94 ++++++++++++++++ examples/task_management_example.cpp | 68 +++++++++++ include/TaskManager.h | 63 +++++++++++ src/ApiServer.cpp | 76 ++++++++++++- src/ApiServer.h | 8 +- src/TaskManager.cpp | 162 +++++++++++++++++++++++++++ src/TaskScheduler.cpp | 5 - src/main.cpp | 54 +++++---- 8 files changed, 495 insertions(+), 35 deletions(-) create mode 100644 examples/task_management_example.cpp create mode 100644 include/TaskManager.h create mode 100644 src/TaskManager.cpp delete mode 100644 src/TaskScheduler.cpp diff --git a/README.md b/README.md index ed04fe3..4a3bf47 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,100 @@ The system includes automatic WiFi fallback: 2. If connection fails, creates an access point 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 - WiFi credentials are hardcoded in `Config.cpp` (should be configurable) diff --git a/examples/task_management_example.cpp b/examples/task_management_example.cpp new file mode 100644 index 0000000..f322018 --- /dev/null +++ b/examples/task_management_example.cpp @@ -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 +#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(); +} \ No newline at end of file diff --git a/include/TaskManager.h b/include/TaskManager.h new file mode 100644 index 0000000..ef9a18a --- /dev/null +++ b/include/TaskManager.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#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 tasks; + std::vector taskDefinitions; + + Task* findTask(const std::string& name) const; + void createTask(const TaskDefinition& taskDef); +}; \ No newline at end of file diff --git a/src/ApiServer.cpp b/src/ApiServer.cpp index 0290417..5713d9b 100644 --- a/src/ApiServer.cpp +++ b/src/ApiServer.cpp @@ -1,6 +1,6 @@ #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 requestHandler) { serviceRegistry.push_back(std::make_tuple(uri, method)); @@ -37,6 +37,13 @@ void ApiServer::begin() { ); addEndpoint("/api/node/restart", HTTP_POST, 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(); } @@ -173,4 +180,71 @@ void ApiServer::onRestartRequest(AsyncWebServerRequest *request) { delay(10); ESP.restart(); }); +} + +void ApiServer::onTaskStatusRequest(AsyncWebServerRequest *request) { + JsonDocument doc; + JsonArray tasksArr = doc["tasks"].to(); + + // This would need to be implemented in TaskManager to expose task status + // For now, we'll return a basic response + JsonObject taskObj = tasksArr.add(); + 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); + } } \ No newline at end of file diff --git a/src/ApiServer.h b/src/ApiServer.h index 1c73cd4..eb02fd9 100644 --- a/src/ApiServer.h +++ b/src/ApiServer.h @@ -9,13 +9,14 @@ #include "NodeContext.h" #include "NodeInfo.h" +#include "TaskManager.h" using namespace std; using namespace std::placeholders; class ApiServer { public: - ApiServer(NodeContext& ctx, uint16_t port = 80); + ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80); void begin(); void addEndpoint(const String& uri, int method, std::function requestHandler); void addEndpoint(const String& uri, int method, std::function requestHandler, @@ -23,6 +24,7 @@ public: private: AsyncWebServer server; NodeContext& ctx; + TaskManager& taskManager; std::vector> serviceRegistry; void onClusterMembersRequest(AsyncWebServerRequest *request); void methodToStr(const std::tuple &endpoint, ArduinoJson::V742PB22::JsonObject &apiObj); @@ -30,4 +32,8 @@ private: 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); }; diff --git a/src/TaskManager.cpp b/src/TaskManager.cpp new file mode 100644 index 0000000..4961a80 --- /dev/null +++ b/src/TaskManager.cpp @@ -0,0 +1,162 @@ +#include "TaskManager.h" +#include +#include + +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; +} \ No newline at end of file diff --git a/src/TaskScheduler.cpp b/src/TaskScheduler.cpp deleted file mode 100644 index c8b13c6..0000000 --- a/src/TaskScheduler.cpp +++ /dev/null @@ -1,5 +0,0 @@ -/* - * https://github.com/arkhipenko/TaskScheduler/tree/master/examples/Scheduler_example16_Multitab - */ - -#include \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6a0c2f1..2fa5b00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,47 +4,45 @@ #include "NetworkManager.h" #include "ClusterManager.h" #include "ApiServer.h" +#include "TaskManager.h" NodeContext ctx; NetworkManager network(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 tListenForDiscovery(0, TASK_FOREVER, [](){ cluster.listenForDiscovery(); }); -Task tUpdateStatus(0, TASK_FOREVER, [](){ cluster.updateAllNodeStatuses(); cluster.removeDeadNodes(); }); -Task tPrintMemberList(0, TASK_FOREVER, [](){ cluster.printMemberList(); }); -Task tHeartbeat(0, TASK_FOREVER, [](){ cluster.heartbeatTaskCallback(); }); -Task tUpdateAllMembersInfo(0, TASK_FOREVER, [](){ cluster.updateAllMembersInfoTaskCallback(); }); +// Task callback wrapper functions +void discoverySendTask() { cluster.sendDiscovery(); } +void discoveryListenTask() { cluster.listenForDiscovery(); } +void statusUpdateTask() { cluster.updateAllNodeStatuses(); cluster.removeDeadNodes(); } +void printMembersTask() { cluster.printMemberList(); } +void heartbeatTask() { cluster.heartbeatTaskCallback(); } +void updateMembersInfoTask() { cluster.updateAllMembersInfoTaskCallback(); } void setup() { + // Setup WiFi first network.setupWiFi(); - ctx.scheduler->init(); - // Set task intervals from config - tSendDiscovery.setInterval(ctx.config.discovery_interval_ms); - tListenForDiscovery.setInterval(ctx.config.discovery_interval_ms / 10); // Listen more frequently - tUpdateStatus.setInterval(ctx.config.status_update_interval_ms); - tPrintMemberList.setInterval(ctx.config.print_interval_ms); - tHeartbeat.setInterval(ctx.config.heartbeat_interval_ms); - tUpdateAllMembersInfo.setInterval(ctx.config.member_info_update_interval_ms); + // Register all system tasks + taskManager.registerTask("discovery_send", ctx.config.discovery_interval_ms, discoverySendTask); + taskManager.registerTask("discovery_listen", ctx.config.discovery_interval_ms / 10, discoveryListenTask); + taskManager.registerTask("status_update", ctx.config.status_update_interval_ms, statusUpdateTask); + taskManager.registerTask("print_members", ctx.config.print_interval_ms, printMembersTask); + taskManager.registerTask("heartbeat", ctx.config.heartbeat_interval_ms, heartbeatTask); + taskManager.registerTask("update_members_info", ctx.config.member_info_update_interval_ms, updateMembersInfoTask); - ctx.scheduler->addTask(tSendDiscovery); - ctx.scheduler->addTask(tListenForDiscovery); - ctx.scheduler->addTask(tUpdateStatus); - ctx.scheduler->addTask(tPrintMemberList); - ctx.scheduler->addTask(tHeartbeat); - ctx.scheduler->addTask(tUpdateAllMembersInfo); - tSendDiscovery.enable(); - tListenForDiscovery.enable(); - tUpdateStatus.enable(); - tPrintMemberList.enable(); - tHeartbeat.enable(); - tUpdateAllMembersInfo.enable(); + // Initialize and start all tasks + taskManager.initialize(); + + // Start the API server apiServer.begin(); + + // Print initial task status + taskManager.printTaskStatus(); } void loop() { - ctx.scheduler->execute(); + taskManager.execute(); yield(); }