Examples¶
The library ships seven examples covering the main usage patterns.
Available Examples¶
| Example | Description |
|---|---|
| TaskManager | Synchronous manager driven from loop(), with statistics |
| AsyncTaskManager | Manager running in its own FreeRTOS task |
| Watchdog | One sync and one async manager, the async one supervised by the TWDT |
| Json | JSON export of the task tree alongside classic logging |
| PauseResume | Quiesce an async manager before exclusive access, preserving task state |
| TaskStates | Drive a single task by hand and read its execution state |
| ConditionalTasks | Conditional scheduling, the per-task broadcasts and quiescing |
TaskManager¶
Shows the core patterns: two periodic tasks, an ONCE task re-armed from a callback, a delayed ONCE task, and periodic statistics logging.
Mycila::TaskManager loopTaskManager("loop()");
Mycila::Task sayHello("sayHello", [](void* params) { Serial.println("Hello"); delay(random(1, 500)); });
Mycila::Task sayGoodbye("sayGoodbye", [](void* params) { Serial.println("Hello"); delay(random(1, 500)); });
Mycila::Task ping("ping", [](void* params) { Serial.println((const char*)params); delay(random(1, 500)); });
Mycila::Task delayed("delayed", [](void* params) { Serial.println("Delayed!"); delay(random(1, 500)); });
Mycila::Task output("output", [](void* params) { loopTaskManager.log(); });
char* params = "Pong";
void setup() {
Serial.begin(115200);
while (!Serial)
continue;
sayHello.setType(Mycila::Task::Type::FOREVER);
sayHello.setInterval(1000);
sayHello.onDone([](const Mycila::Task& me, uint32_t elapsed) {
ESP_LOGD("app", "Task '%s' executed in %" PRIu32 " us", me.name(), elapsed);
});
sayHello.setEnabled(true);
loopTaskManager.addTask(sayHello);
sayGoodbye.setType(Mycila::Task::Type::FOREVER);
sayGoodbye.setInterval(3000);
sayGoodbye.onDone([](const Mycila::Task& me, uint32_t elapsed) {
ESP_LOGD("app", "Task '%s' executed in %" PRIu32 " us", me.name(), elapsed);
ping.setData(params);
ping.resume(); // re-arm the ONCE task
});
sayGoodbye.setEnabled(true);
loopTaskManager.addTask(sayGoodbye);
ping.setType(Mycila::Task::Type::ONCE);
ping.onDone([](const Mycila::Task& me, uint32_t elapsed) {
ESP_LOGD("app", "Task '%s' executed in %" PRIu32 " us", me.name(), elapsed);
});
ping.setEnabled(true);
loopTaskManager.addTask(ping);
output.setType(Mycila::Task::Type::FOREVER);
output.setInterval(5000);
output.setEnabled(true);
loopTaskManager.addTask(output);
delayed.setType(Mycila::Task::Type::ONCE);
delayed.setEnabled(true);
loopTaskManager.addTask(delayed);
loopTaskManager.enableProfiling(6);
delayed.resume(10000); // run once, 10 seconds from now
}
void loop() {
loopTaskManager.loop();
}
Key takeaways:
pingis aONCEtask, initially paused;sayGoodbye'sonDone()re-arms it with a payload every 3 s.delayed.resume(10000)is the idiomatic "run in 10 seconds" pattern.enableProfiling(6)must be called after the tasks are added.- The
delay(random(1, 500))calls simulate workloads — and illustrate the cooperative limitation: while one runs, the others wait.
AsyncTaskManager¶
Identical task graph, but the manager runs in a background FreeRTOS task and the Arduino loop task deletes itself:
void setup() {
// ... same task configuration ...
loopTaskManager.enableProfiling(6);
loopTaskManager.asyncStart();
}
void loop() {
vTaskDelete(NULL);
}
See Async Execution for the asyncStart() parameters.
Watchdog¶
Two managers: tm-1 runs asynchronously and is registered with the Task Watchdog, tm-2 runs synchronously from loop(). Each has a task that blocks for a random 1–6 seconds — long enough to trip a 5-second WDT:
Mycila::TaskManager taskManager1("tm-1");
Mycila::TaskManager taskManager2("tm-2");
Mycila::Task slowdown1("slowdown1", [](void* params) {
long d = random(1000, 6000);
Serial.printf("[slowdown1] Delaying for %ld ms\n", d);
delay(d);
});
Mycila::Task slowdown2("slowdown2", [](void* params) {
long d = random(1000, 6000);
Serial.printf("[slowdown2] Delaying for %ld ms\n", d);
delay(d);
});
void setup() {
Serial.begin(115200);
while (!Serial)
continue;
slowdown1.setType(Mycila::Task::Type::FOREVER);
taskManager1.addTask(slowdown1);
slowdown2.setType(Mycila::Task::Type::FOREVER);
taskManager2.addTask(slowdown2);
Mycila::TaskManager::configureWDT(5, false);
taskManager1.asyncStart(4096, -1, -1, 10, true); // register with the WDT
}
void loop() {
taskManager2.loop();
}
Json¶
Same task graph as TaskManager, but the output task serializes the whole manager:
Mycila::Task output("output", [](void* params) {
JsonDocument doc;
loopTaskManager.toJson(doc.to<JsonObject>());
serializeJson(doc, Serial);
Serial.println();
loopTaskManager.log();
});
Requires -D MYCILA_JSON_SUPPORT and ArduinoJson. See JSON Output.
PauseResume¶
Stops an async manager before touching a shared resource, then continues without disturbing task state:
manager.pause(); // request: non-blocking, touches no task
if (!manager.waitForPaused(5000)) { // confirmed: nothing active, nothing will start
manager.resume(); // cancel the request
return;
}
// exclusive access — no callback of this manager is running, onDone included
manager.resume(); // continue; task states were never modified
The example also shows the two properties that make this safe: a producer whose onDone re-arms a follow-up task cannot make it run while the manager is paused, and a completed ONCE task stays completed across pause() / resume(). The manager runs with the TWDT enabled, so a long pause does not trip the watchdog. See Async Execution.
TaskStates¶
A Task is useful on its own, without a manager. This example drives one by hand and reads the v5 execution state:
Mycila::Task led("led", Mycila::Task::Type::FOREVER, [](void*) {
Serial.println("[led] blink");
});
led.setInterval(2000);
led.tryRun(); // skipped: the interval has not elapsed
led.requestEarlyRun(); // ignore the interval, once
led.tryRun(); // runs now
led.pause();
led.forceRun(); // runs anyway: forceRun() ignores pause and enabled
The key v5 distinction is visible in the onDone callback: while it executes, the state is FINALIZING, so running() is false but active() is still true. Polling running() therefore exits before the task has completely finished — active() is the condition that covers the whole execution. See Tasks.
ConditionalTasks¶
Tasks can appear, disappear and switch on and off at runtime. This example combines the dynamic API:
// the manager owns the task: no global instance to declare
Mycila::Task& sensor = manager.newTask("sensor", sensorFn);
sensor.setEnabledWhen([]() { return linkUp; }); // scheduled() is false while down
// the v5 names of the v4 pause()/resume() broadcast
manager.pauseTasks();
if (manager.waitForAllTasksToComplete(0)) // 0 = wait forever
/* touch what the tasks use */;
manager.resumeTasks();
// a task can stop itself: pause() from inside is safe
watchPtr->pause();
setEnabledWhen() installs a predicate evaluated on every pass: when it returns false, the manager skips the task without touching its state — unlike pause(), the task becomes eligible again as soon as the predicate flips back. See Task Managers.
Building an Example¶
The repository is set up as a self-contained PlatformIO project: lib_dir = . compiles the library from the root, and PLATFORMIO_SRC_DIR selects the example. platformio.ini also ships commented src_dir lines for the same purpose.
PlatformIO¶
# regular examples
PLATFORMIO_SRC_DIR=examples/TaskManager pio run -e arduino-3
PLATFORMIO_SRC_DIR=examples/AsyncTaskManager pio run -e arduino-3
PLATFORMIO_SRC_DIR=examples/Watchdog pio run -e arduino-3
# JSON example (defines MYCILA_JSON_SUPPORT and pulls ArduinoJson)
PLATFORMIO_SRC_DIR=examples/Json pio run -e json
Alternatively, edit platformio.ini directly:
The CI uses dedicated environments that take the board from the PIO_BOARD environment variable:
PLATFORMIO_SRC_DIR=examples/TaskManager PIO_BOARD=esp32dev pio run -e ci-arduino-3
PLATFORMIO_SRC_DIR=examples/Json PIO_BOARD=esp32dev pio run -e ci-json
Arduino IDE¶
Open the .ino file of the example you want (for instance examples/TaskManager/TaskManager.ino). The sketch includes <MycilaTaskManager.h>, so the library must be installed through the Library Manager.
The Json example additionally needs ArduinoJson and the MYCILA_JSON_SUPPORT symbol. The Arduino IDE has no per-sketch preprocessor definition UI, so adding #define MYCILA_JSON_SUPPORT at the very top of the sketch — before the includes — is the simplest way to build it there. PlatformIO users get it automatically from the json environment.