A tiny cooperative (run-to-completion) task scheduler for ESP8266 and
classic AVR. You pump it from loop(); each task runs to completion on the
main context — there is no preemption and no per-task stack, so a task that
blocks blocks everything.
#include <LionTask.h>
class Blink : public LionTimerTask {
public:
Blink() : LionTimerTask(F("Blink"), 500) {} // run every 500 ms
protected:
void TaskInnerBody() override { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); }
};
Blink blink;
void setup() { Serial.begin(115200); LionTask::Setup(); }
void loop() { LionTask::Loop(); }On ESP32 use real FreeRTOS tasks via
leva/LionRtosTaskinstead — a cooperative scheduler is pointless where you have an RTOS.
- Identity by pointer. Each task registers itself (
this) into a shared static list in its constructor and removes itself in its destructor. - Non-copyable. Copy ctor / assignment are
= deleted — create each task once, as a global/static or vianew, and never copy it. - Lifetime. A heap task may
delete thisfrom its own body (that is exactly what a one-shotLionSimpleTaskdoes); a stack/global task must outlive the lastLionTask::Loop()/Yield()call that can reach it. - Cooperation. Keep each task body short. Critical tasks additionally run
during
Yield()/Delay(), so keep those especially short. - Scheduling. By default one regular task runs per
Loop()call (round-robin fairness); critical tasks all run everyLoop()and everyYield(). - Not reentrant / not thread-safe. Create and pump tasks from one context.
LionSimpleTask(lambda task) andGetDebugDump()(returnsString) are ESP-only — both are compiled out on classic AVR (no<functional>, andStringchurn fragments the tiny AVR heap). Subclass aLion*Taskand useDebugDump()there instead.
PlatformIO (platformio.ini):
lib_deps = leva/LionTaskThis pulls in its only dependency, leva/LionArray,
automatically.
Arduino IDE: Library Manager → search "LionTask", or drop this folder into
your libraries/ directory (also install LionArray).
#include <LionTask.h>
class Heartbeat : public LionTimerTask {
public:
Heartbeat() : LionTimerTask(F("HB"), 1000) {} // every 1000 ms
protected:
void TaskInnerBody() override { Serial.println(F("tick")); }
};
Heartbeat hb;
void setup() { Serial.begin(115200); LionTask::Setup(); }
void loop() { LionTask::Loop(); }Task base classes, smallest delay storage first:
| Class | Delay type | Range |
|---|---|---|
LionTimerTask |
uint32_t |
up to ~49 days (ms) |
LionShortTimerTask |
uint16_t |
up to ~65 s (ms) |
LionCrudeTimerTask |
uint8_t |
1–255 s (whole seconds) |
LionCriticalTask |
uint16_t |
also runs during Yield() |
// periodic
LionSimpleTask::ExecuteEachMs([]() { Serial.println(F("ping")); }, 10000, F("Ping"));
// one-shot, after a delay (the task frees itself afterwards)
LionSimpleTask::Execute([]() { Serial.println(F("once")); }, 2000, F("Once"));| Member | Description |
|---|---|
static void Setup() |
One-time banner / init. Call from setup(). |
static void Loop() |
Pump the scheduler. Call as often as possible from loop(). |
static void Yield() |
Run due critical tasks only (use inside long work). |
static void Delay(uint32_t ms) |
Busy-wait ms while still running critical tasks. |
static int TaskNumber() |
Number of regular tasks. |
static void DebugDump() |
Print every task's next-run time to Serial. |
static String GetDebugDump() |
Same as a String (ESP only). |
const char *Name() |
This task's (truncated) name. |
void ExecuteNow() |
Schedule this task to run on the next Loop(). |
LionTimerTask, LionShortTimerTask, LionCrudeTimerTask, LionCriticalTask —
constructed with (name, delay) where name is a const __FlashStringHelper*
(F("...")) or String.
| Member | Description |
|---|---|
static void Execute(callable, ms, name) |
Run callable once, ms after creation, then self-delete. |
static void ExecuteEachMs(callable, ms, name) |
Run callable every ms forever. |
callable is any std::function<void()> — lambda, function pointer or functor.
0BSD — see LICENSE.