TimerService in Pico-Framework
TimerService
is a high-level scheduling component designed for embedded applications. It enables your application to schedule future actions using simple time-based declarations, ranging from fixed intervals to day-of-week schedules and start/stop durations.
This guide walks through the design, usage, and best practices for using TimerService
.
What TimerService Provides
Unlike raw FreeRTOS timers, TimerService
integrates with the EventManager
to deliver high-level Event
objects instead of raw callbacks. This enables non-blocking, event-driven scheduling using familiar application semantics.
Key features:
- One-shot timers (
scheduleAt
) - Repeating timers (
scheduleEvery
) - Day-of-week + time-of-day schedules (
scheduleDailyAt
) - Paired duration-based events (
scheduleDuration
) - Job ID tracking and cancellation
- Thread-safe on multicore systems
- Support for both
SystemNotification
andUserNotification
- Future: persistent job reloading and missed event recovery
Design Philosophy
TimerService
embodies the event-first architecture of Pico-Framework. Rather than relying on raw callbacks or polling loops, it publishes well-typed Event
objects to the EventManager
, enabling clear separation of logic, easy observability, and full testability.
How It Works
TimerService
wraps FreeRTOS timers internally and manages an active map of jobs using std::map<std::string, TimerHandle_t>
. Each job has a string ID and is automatically cleaned up when completed or canceled.
Events are posted via EventManager
, and can use either system or user notifications:
Event::user(UserNotification::SomethingHappened);
Event::system(SystemNotification::NetworkReady);
All timers are protected by a FreeRTOS mutex for thread and multicore safety.
One-Shot Events with scheduleAt()
Schedule a one-time event at a specific time_t
timestamp:
time_t when = PicoTime::now() + 10; // 10 seconds from now
Event e = Event::user(UserNotification::StartPump);
TimerService::instance().scheduleAt(when, e);
Optionally, provide a job ID to allow cancellation:
TimerService::instance().scheduleAt(when, e, "pump_start_10s");
Repeating Events with scheduleEvery()
Schedule a repeating timer every N milliseconds:
TimerService::instance().scheduleEvery(1000, Event::user(UserNotification::Heartbeat));
Or with a named job ID:
TimerService::instance().scheduleEvery(30000, Event::user(UserNotification::CheckStatus), "check_status");
These timers persist until explicitly canceled.
Daily Scheduling with scheduleDailyAt()
Run a job at a specific time on selected days:
TimeOfDay tod = TimeOfDay::fromString("06:30:00");
DaysOfWeek days = Days::Mon | Days::Wed | Days::Fri;
TimerService::instance().scheduleDailyAt(tod, days, Event::user(UserNotification::Irrigate));
Internally, this uses the TimerServiceTOD
subclass to compute the next match and re-schedule daily. Each run requeues itself for the next valid day/time.
Note: Daily jobs are not persistent across restarts and are not rehydrated. This will be addressed in a future version.
Duration-Based Start/Stop Events
You can pair a start event with a delayed stop event:
TimeOfDay start = TimeOfDay::fromString("07:00:00");
uint32_t duration = 30 * 60 * 1000; // 30 minutes
TimerService::instance().scheduleDuration(start, Days::All,
Event::user(UserNotification::ZoneOn),
Event::user(UserNotification::ZoneOff),
"morning_watering");
The stop event is automatically scheduled after the configured delay. You only need to track the base job ID.
Canceling a Job
To cancel any job:
bool removed = TimerService::instance().cancel("check_status");
This stops and deletes the FreeRTOS timer and removes the job from internal tracking.
Thread Safety
All access to internal state is protected by a StaticSemaphore_t
-based mutex, ensuring:
- Safe scheduling and cancellation from any task
- Multicore compatibility
- No race conditions on job map
Summary
Use TimerService
for:
Schedule Type | Method |
---|---|
One-shot at time | scheduleAt() |
Fixed interval | scheduleEvery() |
Time of day | scheduleDailyAt() |
Start + stop | scheduleDuration() |
All jobs are cancelable by ID, and all events are published to EventManager
.
Example: Irrigation Scheduling
void MyApp::scheduleIrrigation() {
TimeOfDay start = TimeOfDay::fromString("06:00");
TimerService::instance().scheduleDuration(start, Days::Mon | Days::Wed,
Event::user(UserNotification::Zone1Start),
Event::user(UserNotification::Zone1Stop),
"zone1_mw_6am");
}
This schedules a start event at 6:00 AM on Monday and Wednesday, and automatically schedules a stop after the configured duration.