{"id":36967798,"url":"https://github.com/esptoolkit/esp-scheduler","last_synced_at":"2026-03-10T12:02:35.247Z","repository":{"id":328315243,"uuid":"1111873480","full_name":"ESPToolKit/esp-scheduler","owner":"ESPToolKit","description":"ESPScheduler is a C++17, class-based scheduler for ESP32 firmware that brings cron-like calendar patterns","archived":false,"fork":false,"pushed_at":"2026-02-20T10:37:09.000Z","size":79,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-20T14:31:12.300Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ESPToolKit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-07T19:43:44.000Z","updated_at":"2026-02-20T10:37:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ESPToolKit/esp-scheduler","commit_stats":null,"previous_names":["esptoolkit/esp-scheduler"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/ESPToolKit/esp-scheduler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ESPToolKit%2Fesp-scheduler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ESPToolKit%2Fesp-scheduler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ESPToolKit%2Fesp-scheduler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ESPToolKit%2Fesp-scheduler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ESPToolKit","download_url":"https://codeload.github.com/ESPToolKit/esp-scheduler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ESPToolKit%2Fesp-scheduler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30332857,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T05:25:20.737Z","status":"ssl_error","status_checked_at":"2026-03-10T05:25:17.430Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-13T20:45:09.575Z","updated_at":"2026-03-10T12:02:35.241Z","avatar_url":"https://github.com/ESPToolKit.png","language":"C++","funding_links":["https://ko-fi.com/esptoolkit"],"categories":[],"sub_categories":[],"readme":"# ESPScheduler\n\nESPScheduler is a C++17, class-based scheduler for ESP32 firmware that brings cron-like calendar patterns without parsing cron strings. It builds on [ESPDate](https://github.com/ESPToolKit/esp-date) for all wall-clock math and can run jobs either inline (driven by `tick()`) or on dedicated native FreeRTOS tasks.\n\n## CI / Release / License\n[![CI](https://github.com/ESPToolKit/esp-scheduler/actions/workflows/ci.yml/badge.svg)](https://github.com/ESPToolKit/esp-scheduler/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/ESPToolKit/esp-scheduler?sort=semver)](https://github.com/ESPToolKit/esp-scheduler/releases)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md)\n\n## Features\n- **Cron-style patterns, no strings**: express minute/hour/day/month/weekday filters with `ScheduleField` objects and helpers for daily/weekly/monthly runs.\n- **Inline or worker execution**: run callbacks inside `tick()` or on their own FreeRTOS task (with separate PSRAM policies for buffers and task stacks).\n- **One-shot UTC triggers**: schedule absolute UTC times alongside recurring patterns.\n- **Astronomical schedules**: trigger jobs at sunrise/sunset (with minute offsets), moon phase angles/names, and moon illumination crossings.\n- **Calendar-aware**: respects classic cron `dayOfMonth` vs `dayOfWeek` logic and always operates in local time.\n- **Clock guard for unset RTC**: defaults to idling until the wall clock reaches 2020-01-01 UTC (configurable) so jobs do not replay from the 1970 epoch when SNTP syncs later.\n- **Optional PSRAM buffer policy**: `ESPSchedulerConfig::usePSRAMBuffers` routes scheduler-owned job/context storage through ESPBufferManager with automatic fallback to default heap.\n- **Class-based API**: everything hangs off an `ESPScheduler` instance; no global namespaces or macros.\n- **Arduino / ESP-IDF friendly**: C++17, metadata for PlatformIO/Arduino CLI, and examples/tests ready for CI.\n\n## Getting Started\nInstall one of two ways:\n- Download the repository zip from GitHub, extract it, and drop the folder into your PlatformIO `lib/` directory, Arduino IDE `libraries/` directory, or add it as an ESP-IDF component.\n- Add the public GitHub URL to `lib_deps` in `platformio.ini` so PlatformIO fetches it for you:\n  ```\n  lib_deps = https://github.com/ESPToolKit/esp-scheduler.git\n  ```\n- Arduino CLI: install the library and its deps, then compile any example sketch:\n  ```bash\n  arduino-cli lib install --git-url https://github.com/ESPToolKit/esp-date.git\n  arduino-cli lib install --git-url https://github.com/ESPToolKit/esp-scheduler.git\n  arduino-cli compile --fqbn esp32:esp32:esp32 examples/inline_daily\n  ```\n\nThen include the scheduler together with its dependencies:\n\n```cpp\n#include \u003cArduino.h\u003e\n#include \u003cESPDate.h\u003e\n#include \u003cESPScheduler.h\u003e\n\nESPDate date;\nESPScheduler scheduler(date);\n\nvoid morningBackup(void* userData) {\n    Serial.println(\"Running morning backup...\");\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    // Configure SNTP/time zone before scheduling so ESPDate reports valid local time.\n    // Optional: raise the minimum valid clock to block jobs until SNTP sets time.\n    scheduler.setMinValidUtc(date.fromUtc(2020, 1, 1, 0, 0, 0));\n\n    // Run every weekday at 07:30 (local time) on a dedicated worker task\n    uint8_t weekdaysMask = 0b0111110; // Mon..Fri\n    scheduler.addJob(\n        Schedule::weeklyAtLocal(weekdaysMask, 7, 30),\n        SchedulerJobMode::WorkerTask,\n        \u0026morningBackup\n    );\n}\n\nvoid loop() {\n    scheduler.tick(); // still safe to call; worker jobs self-drive\n    delay(5000);\n}\n```\n\nCall `deinit()` explicitly when you no longer need scheduled jobs (for example before deep sleep or component shutdown):\n\n```cpp\nscheduler.deinit();\nif (!scheduler.isInitialized()) {\n    Serial.println(\"Scheduler stopped\");\n}\n```\n\n## API quick map\n- `SchedulerJobMode`: `Inline` (runs inside `tick()`) or `WorkerTask` (dedicated FreeRTOS task).\n- `ESPSchedulerConfig`: scheduler-level memory policy (`usePSRAMBuffers`) for scheduler-owned dynamic buffers.\n- `SchedulerTaskConfig`: optional worker task config (name, stack size, priority, core, PSRAM stack flag).\n- `SchedulerCallback`: `using SchedulerCallback = void (*)(void* userData);`\n- `SchedulerFunction`: `using SchedulerFunction = std::function\u003cvoid(void* userData)\u003e;` (capturing lambdas supported).\n- `SchedulerFunctionNoData`: `using SchedulerFunctionNoData = std::function\u003cvoid()\u003e;` (no-arg lambdas supported).\n- `setMinValidUnixSeconds` / `setMinValidUtc`: block all inline/worker jobs until the wall clock reaches this point (default: 2020-01-01 UTC).\n- `ScheduleField`: bitmask-backed allowed values for one cron field. Builders: `any()`, `only()`, `range()`, `every()`, `rangeEvery()`, `list()`.\n- `Schedule`: one-shot (`onceUtc`) or cron-like via helpers: `dailyAtLocal`, `weeklyAtLocal`, `monthlyOnDayLocal`, `custom`.\n- Astronomical helpers: `sunrise(offsetMin)`, `sunset(offsetMin)`, `moonPhase(name/tolerance)`, `moonPhaseAngle(angle/tolerance)`, `moonIlluminationPercent(percent/tolerance)`.\n- `JobInfo` / `getJobInfo(index, info)`: inspect active jobs (inline first, then worker), including enabled state, schedule copy, and next run (if known).\n- `cleanup()`: manually purge finished inline/worker jobs when you are not calling `tick()`.\n- `deinit()`: cancels and destroys all active jobs; destructor calls it automatically.\n- `isInitialized()`: reports whether the scheduler is currently active after construction/re-init and false after `deinit()`.\n\n```cpp\nESPSchedulerConfig schedCfg;\nschedCfg.usePSRAMBuffers = true;                    // falls back safely when PSRAM is unavailable\nESPScheduler scheduler(date, schedCfg);\nuint32_t id = scheduler.addJob(\n    Schedule::dailyAtLocal(7, 30),\n    SchedulerJobMode::Inline,\n    \u0026myCallback,\n    nullptr\n);\n\nscheduler.pauseJob(id);\nscheduler.resumeJob(id);\nscheduler.cancelJob(id);\n```\n\nCapturing lambda callbacks are supported via the `std::function` overload:\n\n```cpp\nDateTime bootTargetUtc = date.addMinutes(date.now(), 2);\nscheduler.addJobOnceUtc(\n    bootTargetUtc,\n    SchedulerJobMode::Inline,\n    [this](void* /*userData*/) {\n        doSomething();\n    }\n);\n```\n\nNo-arg lambdas are also supported:\n\n```cpp\nscheduler.addJobOnceUtc(\n    bootTargetUtc,\n    SchedulerJobMode::Inline,\n    [this]() {\n        doSomething();\n    }\n);\n```\n\n## Schedule recipes\n```cpp\n// One-shot absolute UTC\nSchedule once = Schedule::onceUtc(date.fromUtc(2025, 1, 1, 12, 0));\n\n// Daily 08:15 (local)\nSchedule daily = Schedule::dailyAtLocal(8, 15);\n\n// Weekdays at 18:30 (bitmask: 0=Sun, 1=Mon...)\nuint8_t weekdays = 0b0111110; // Mon..Fri\nSchedule weekly = Schedule::weeklyAtLocal(weekdays, 18, 30);\n\n// Monthly on the 1st at 09:00 (clamps 29/30/31 to valid)\nSchedule monthly = Schedule::monthlyOnDayLocal(1, 9, 0);\n\n// Custom cron-like: every 5 minutes between 9-17 on Mon/Wed/Fri\nint days[] = {1, 3, 5};\nSchedule custom = Schedule::custom(\n    ScheduleField::every(5),        // minute\n    ScheduleField::range(9, 17),    // hour\n    ScheduleField::any(),           // day of month\n    ScheduleField::any(),           // month\n    ScheduleField::list(days, 3)    // day of week\n);\n\n// Astronomical schedules (requires ESPDate initialized with latitude/longitude + TZ)\nSchedule sunriseNow = Schedule::sunrise();                       // exactly sunrise\nSchedule sunsetLate = Schedule::sunset(15);                      // sunset + 15 minutes\nSchedule preDawn = Schedule::sunrise(-30);                       // sunrise - 30 minutes\nSchedule lastQuarter = Schedule::moonPhase(MoonPhaseName::LastQuarter, 2);\nSchedule phase270 = Schedule::moonPhaseAngle(270, 2);            // explicit angle\nSchedule illum75 = Schedule::moonIlluminationPercent(75.0, 0.5); // percent + tolerance\n```\n\n### Execution modes\n- **Inline**: call `tick()` periodically; callbacks run in the caller’s context.\n- **WorkerTask**: each job gets its own FreeRTOS task that sleeps until due. Configure stacks/priority/affinity via `SchedulerTaskConfig`.\n- **Memory policy split**: `ESPSchedulerConfig::usePSRAMBuffers` controls scheduler-owned dynamic buffer placement; `SchedulerTaskConfig::usePsramStack` controls worker task stack placement.\n- Even if you only schedule `WorkerTask` jobs, call `tick()` or `cleanup()` occasionally so the scheduler can drop finished worker job metadata.\n\n### Cron semantics\n- Resolution: minutes (seconds always treated as zero).\n- Local time matching via ESPDate; honour your TZ/DST setup before scheduling.\n- `dayOfMonth` vs `dayOfWeek`: classic cron OR rule when both are restricted; either can satisfy the day check.\n- Astronomical moon jobs trigger on crossing events (with tolerance), not exact floating-point equality checks.\n- Clock validity guard: inline and worker paths stay idle while `now()` is before `setMinValidUnixSeconds()` (default 2020-01-01 UTC). Set it to `0` if you explicitly want to allow pre-2000 times.\n\n## Examples\n\nFor sun/moon schedules, see `examples/inline_astronomical/inline_astronomical.ino`.\n\n### Inline daily tick (no worker needed)\n```cpp\n#include \u003cArduino.h\u003e\n#include \u003cESPDate.h\u003e\n#include \u003cESPScheduler.h\u003e\n\nESPDate date;\nESPScheduler scheduler(date);  // inline jobs only\n\nstatic void waterPlants(void* /*userData*/) {\n    Serial.println(\"Watering plants...\");\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    // Set TZ + SNTP before scheduling so local time is valid\n    scheduler.setMinValidUtc(date.fromUtc(2020, 1, 1, 0, 0, 0));\n\n    // 07:00 every day, inline\n    scheduler.addJob(\n        Schedule::dailyAtLocal(7, 0),\n        SchedulerJobMode::Inline,\n        \u0026waterPlants\n    );\n}\n\nvoid loop() {\n    scheduler.tick();  // computes next runs using date.now()\n    delay(1000);\n}\n```\n\n### Worker task with custom stack/priority\n```cpp\n#include \u003cArduino.h\u003e\n#include \u003cESPDate.h\u003e\n#include \u003cESPScheduler.h\u003e\n\nESPDate date;\nESPScheduler scheduler(date);\n\nstatic void backupJob(void* /*userData*/) {\n    Serial.println(\"Backing up to cloud...\");\n    // heavy work is safe here; job owns its own FreeRTOS task\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    scheduler.setMinValidUtc(date.fromUtc(2020, 1, 1, 0, 0, 0));\n\n    SchedulerTaskConfig cfg;\n    cfg.name = \"backup\";\n    cfg.stackSize = 8192;\n    cfg.priority = 3;\n    cfg.coreId = 1;\n    cfg.usePsramStack = true;\n\n    scheduler.addJob(\n        Schedule::weeklyAtLocal(0b0000010, 2, 30), // Mondays 02:30 local\n        SchedulerJobMode::WorkerTask,\n        \u0026backupJob,\n        nullptr,\n        \u0026cfg\n    );\n}\n\nvoid loop() {\n    scheduler.tick();   // still safe to call; frees finished worker metadata\n    delay(1000);\n}\n```\n\n### One-shot + inspecting/pause/resume\n```cpp\n#include \u003cArduino.h\u003e\n#include \u003cESPDate.h\u003e\n#include \u003cESPScheduler.h\u003e\n\nESPDate date;\nESPScheduler scheduler(date);\n\nstatic void firmwareSwap(void* /*userData*/) {\n    Serial.println(\"Swapping firmware banks now\");\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    DateTime when = date.fromUtc(2025, 1, 15, 12, 0, 0);\n    uint32_t id = scheduler.addJobOnceUtc(\n        when,\n        SchedulerJobMode::Inline,\n        \u0026firmwareSwap\n    );\n\n    JobInfo info{};\n    if (scheduler.getJobInfo(0, info)) {\n        Serial.printf(\"Job %u next run: %lld\\n\", info.id, info.nextRunUtc.epochSeconds);\n        scheduler.pauseJob(info.id);   // stop until resumeJob is called\n        scheduler.resumeJob(info.id);\n    }\n}\n\nvoid loop() {\n    scheduler.tick();\n    delay(500);\n}\n```\n\nExample sketches in this repo:\n- `examples/inline_daily/inline_daily.ino` — inline daily tick loop.\n- `examples/inline_one_shot/inline_one_shot.ino` — single UTC trigger inline.\n- `examples/inline_pause_resume/inline_pause_resume.ino` — pausing/resuming a repeating inline job.\n- `examples/inline_every_day_midnight/inline_every_day_midnight.ino` — every day at local midnight.\n- `examples/inline_every_hour/inline_every_hour.ino` — every hour (top of hour) every day.\n- `examples/inline_every_minute/inline_every_minute.ino` — every minute all day.\n- `examples/inline_every_minute_selected_days/inline_every_minute_selected_days.ino` — every minute on selected weekdays.\n- `examples/inline_every_hour_selected_days/inline_every_hour_selected_days.ino` — every hour on selected weekdays.\n- `examples/inline_every_15_minutes_work_hours/inline_every_15_minutes_work_hours.ino` — every 15 minutes during business hours.\n- `examples/worker_weekly/worker_weekly.ino` — weekly heavy job on its own task with custom stack/priority.\n- `examples/worker_one_shot/worker_one_shot.ino` — one-shot worker task using PSRAM stack.\n- `examples/custom_fields/custom_fields.ino` — custom cron fields (every N minutes, selected weekdays/hours).\n- `examples/monthly_on_day/monthly_on_day.ino` — monthly day-of-month trigger with clamping.\n\n## Gotchas\n- Always set time zone and SNTP before scheduling; pair that with `setMinValidUtc` so jobs do not all replay at boot from the 1970 epoch.\n- Even when you only run worker tasks, call `tick()` or `cleanup()` periodically so finished worker metadata is freed.\n- `ScheduleField::list` drops out-of-range values; if every entry is invalid, `addJob` returns `0` because the schedule fails validation.\n- Matching happens at minute resolution; if you need per-second triggers, pair ESPScheduler with ESPTimer counters instead.\n\n## Restrictions\n- Designed for ESP32 boards (Arduino-ESP32 or ESP-IDF) with FreeRTOS and C++17 enabled.\n- Depends on ESPDate for wall-clock math.\n- Each worker job spawns its own task with its own stack; size those stacks (or enable PSRAM stacks) according to your workload.\n- Schedules operate in local time and clamp invalid calendar combinations (e.g., 31st on shorter months).\n\n## Examples (one focus per sketch)\n- `examples/inline_daily/inline_daily.ino` — inline daily tick loop.\n- `examples/inline_one_shot/inline_one_shot.ino` — single UTC trigger inline.\n- `examples/inline_pause_resume/inline_pause_resume.ino` — pausing/resuming a repeating inline job.\n- `examples/inline_every_day_midnight/inline_every_day_midnight.ino` — every day at local midnight.\n- `examples/inline_every_hour/inline_every_hour.ino` — every hour (top of hour) every day.\n- `examples/inline_every_minute/inline_every_minute.ino` — every minute all day.\n- `examples/inline_every_minute_selected_days/inline_every_minute_selected_days.ino` — every minute on selected weekdays.\n- `examples/inline_every_hour_selected_days/inline_every_hour_selected_days.ino` — every hour on selected weekdays.\n- `examples/inline_every_15_minutes_work_hours/inline_every_15_minutes_work_hours.ino` — every 15 minutes during business hours.\n- `examples/worker_weekly/worker_weekly.ino` — weekly heavy job on its own task with custom stack/priority.\n- `examples/worker_one_shot/worker_one_shot.ino` — one-shot worker task using PSRAM stack.\n- `examples/custom_fields/custom_fields.ino` — custom cron fields (every N minutes, selected weekdays/hours).\n- `examples/monthly_on_day/monthly_on_day.ino` — monthly day-of-month trigger with clamping.\n\n## Tests\n- Unity-based device tests live in `test/test_esp_scheduler`; drop the folder into a PlatformIO workspace and run `pio test -e esp32dev` against real hardware.\n- Host-side CTest is intentionally skipped because the scheduler relies on ESP32 FreeRTOS and ESPDate wall-clock helpers.\n- CI also compiles all examples through PlatformIO and Arduino CLI across ESP32, S3, C3, and P4 boards.\n\n## License\nMIT — see `LICENSE.md`.\n\n## ESPToolKit\n- Check out other libraries: https://github.com/orgs/ESPToolKit/repositories\n- Hang out on Discord: https://discord.gg/WG8sSqAy\n- Support the project: https://ko-fi.com/esptoolkit\n- Visit the website: https://www.esptoolkit.hu/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesptoolkit%2Fesp-scheduler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesptoolkit%2Fesp-scheduler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesptoolkit%2Fesp-scheduler/lists"}