Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
TimerService.cpp
Go to the documentation of this file.
1
39#include "events/TimerService.h"
40#include <cstdio>
41#include "time/PicoTime.h"
42#include "events/EventManager.h"
44
45StaticSemaphore_t TimerService::lockBuffer_;
46
48 lock_ = xSemaphoreCreateMutexStatic(&lockBuffer_);
50}
51
52void TimerService::withLock(const std::function<void()>& fn) {
53 xSemaphoreTake(lock_, portMAX_DELAY);
54 fn();
55 xSemaphoreGive(lock_);
56}
57
58// --- Helpers ---
59
60static uint32_t toSeconds(const TimeOfDay &tod)
61{
62 return tod.hour * 3600 + tod.minute * 60 + tod.second;
63}
64
65static uint32_t secondsUntilNextMatch(const TimeOfDay &tod, DaysOfWeek mask, time_t currentTime)
66{
67 struct tm now;
68 localtime_r(&currentTime, &now);
69 uint8_t today = now.tm_wday;
70 uint32_t nowSec = toSeconds({(uint8_t)now.tm_hour, (uint8_t)now.tm_min, (uint8_t)now.tm_sec});
71 uint32_t targetSec = toSeconds(tod);
72
73 for (int offset = 0; offset < 7; ++offset)
74 {
75 uint8_t checkDay = (today + offset) % 7;
76 if (mask & (1 << checkDay))
77 {
78 if (offset == 0 && targetSec <= nowSec)
79 continue;
80 return offset * 86400 + ((offset == 0) ? (targetSec - nowSec) : (targetSec + 86400 - nowSec));
81 }
82 }
83
84 return 86400;
85}
86
89{
90 static TimerService inst;
91 return inst;
92}
93
95void TimerService::scheduleAt(time_t unixTime, const Event &event)
96{
97 std::string defaultId = "at_" + std::to_string(unixTime);
98 scheduleAt(unixTime, event, defaultId);
99}
100
101void TimerService::scheduleAt(time_t unixTime, const Event &event, const std::string &jobId)
102{
103 time_t now = PicoTime::now();
104 uint32_t delaySeconds = (unixTime > now) ? (unixTime - now) : 0;
105
106 TimerHandle_t handle = xTimerCreate("AtTimer", pdMS_TO_TICKS(delaySeconds * 1000), pdFALSE, new Event(event),
107 [](TimerHandle_t xTimer)
108 {
109 Event *evt = static_cast<Event *>(pvTimerGetTimerID(xTimer));
110 if (evt)
111 {
112 AppContext::get<EventManager>()->postEvent(*evt);
113 delete evt;
114 }
115 xTimerDelete(xTimer, 0);
116 });
117
118 if (handle)
119 {
120 withLock([&]() {
121 // Cancel and delete old timer if it exists
122 auto it = scheduledJobs.find(jobId);
123 if (it != scheduledJobs.end()) {
124 xTimerStop(it->second, 0);
125 xTimerDelete(it->second, 0);
126 scheduledJobs.erase(it);
127 }
128 scheduledJobs[jobId] = handle;
129 });
130 xTimerStart(handle, 0);
131 }
132}
133
135void TimerService::scheduleEvery(uint32_t intervalMs, const Event &event)
136{
137 std::string defaultId = "interval_" + std::to_string((uint32_t)event.notification.code());
138 scheduleEvery(intervalMs, event, defaultId);
139}
140
141void TimerService::scheduleEvery(uint32_t intervalMs, const Event &event, const std::string &jobId)
142{
143 TimerHandle_t handle = xTimerCreate("EveryTimer", pdMS_TO_TICKS(intervalMs), pdTRUE, new Event(event),
144 [](TimerHandle_t xTimer)
145 {
146 Event *evt = static_cast<Event *>(pvTimerGetTimerID(xTimer));
147 if (evt)
148 {
149 AppContext::get<EventManager>()->postEvent(*evt);
150 }
151 });
152
153 if (handle)
154 {
155 withLock([&]() {
156 scheduledJobs[jobId] = handle;
157 });
158 xTimerStart(handle, 0);
159 }
160}
161
164{
165 std::string defaultId = "daily_" + std::to_string((uint32_t)event.notification.code()) + "_" + std::to_string(toSeconds(time));
166 scheduleDailyAt(time, days, event, defaultId);
167}
168
169void TimerService::scheduleDailyAt(TimeOfDay time, DaysOfWeek days, const Event &event, const std::string &jobId)
170{
171 time_t now = PicoTime::now();
172 uint32_t delaySeconds = secondsUntilNextMatch(time, days, now);
173
174 TimerHandle_t handle = xTimerCreate("DailyJob", pdMS_TO_TICKS(delaySeconds * 1000), pdFALSE, new Event(event),
175 [](TimerHandle_t xTimer)
176 {
177 Event *evt = static_cast<Event *>(pvTimerGetTimerID(xTimer));
178 if (evt)
179 {
180 AppContext::get<EventManager>()->postEvent(*evt);
181 delete evt;
182 }
183 xTimerDelete(xTimer, 0);
184 });
185
186 if (handle)
187 {
188 withLock([&]() {
189 scheduledJobs[jobId] = handle;
190 });
191 xTimerStart(handle, 0);
192 TimerJob job{time, days, 0, event, {}, true};
194 }
195}
196
198void TimerService::scheduleDuration(TimeOfDay start, DaysOfWeek days, uint32_t durationMs,
199 const Event &startEvent, const Event &stopEvent)
200{
201 std::string baseId = "duration_" + std::to_string((uint32_t)startEvent.notification.code());
202 scheduleDuration(start, days, durationMs, startEvent, stopEvent, baseId);
203}
204
205void TimerService::scheduleDuration(TimeOfDay start, DaysOfWeek days, uint32_t durationMs,
206 const Event &startEvent, const Event &stopEvent, const std::string &baseId)
207{
208 time_t now = PicoTime::now();
209 uint32_t startDelay = secondsUntilNextMatch(start, days, now);
210
211 std::string startId = baseId + "_start";
212 std::string stopId = baseId + "_stop";
213
214 TimerHandle_t startHandle = xTimerCreate("StartJob", pdMS_TO_TICKS(startDelay * 1000), pdFALSE, new Event(startEvent),
215 [](TimerHandle_t xTimer)
216 {
217 Event *evt = static_cast<Event *>(pvTimerGetTimerID(xTimer));
218 if (evt)
219 {
220 AppContext::get<EventManager>()->postEvent(*evt);
221 delete evt;
222 }
223 xTimerDelete(xTimer, 0);
224 });
225
226 if (startHandle)
227 {
228 withLock([&]() {
229 scheduledJobs[startId] = startHandle;
230 });
231 xTimerStart(startHandle, 0);
232 }
233
234 uint32_t stopDelay = startDelay + durationMs / 1000;
235
236 TimerHandle_t stopHandle = xTimerCreate("StopJob", pdMS_TO_TICKS(stopDelay * 1000), pdFALSE, new Event(stopEvent),
237 [](TimerHandle_t xTimer)
238 {
239 Event *evt = static_cast<Event *>(pvTimerGetTimerID(xTimer));
240 if (evt)
241 {
242 AppContext::get<EventManager>()->postEvent(*evt);
243 delete evt;
244 }
245 xTimerDelete(xTimer, 0);
246 });
247
248 if (stopHandle)
249 {
250 withLock([&]() {
251 scheduledJobs[stopId] = stopHandle;
252 });
253 xTimerStart(stopHandle, 0);
254 }
255
256 TimerJob job{start, days, durationMs, startEvent, stopEvent, true};
258}
259
261bool TimerService::cancel(const std::string &jobId) {
262 bool success = false;
263 withLock([&]() {
264 auto it = scheduledJobs.find(jobId);
265 if (it != scheduledJobs.end()) {
266 xTimerStop(it->second, 0);
267 xTimerDelete(it->second, 0);
268 scheduledJobs.erase(it);
269 success = true;
270 }
271 });
272 return success;
273}
274
277{
278 // Not implemented in v0.2
279}
280
283{
284 // Not implemented in v0.2
285}
286
287void TimerService::scheduleCallbackAt(time_t when, std::function<void()> callback) {
288 time_t now = PicoTime::now();
289 if (when <= now) {
290 printf("[TimerService] WARNING: scheduled time is in the past (%lld <= %lld)\n", when, now);
291 return;
292 }
293
294 auto* cb = new std::function<void()>(std::move(callback));
295 uint32_t delayMs = static_cast<uint32_t>((when - now) * 1000);
296
297 TimerHandle_t handle = xTimerCreate("CbTimer", pdMS_TO_TICKS(delayMs), pdFALSE, cb,
298 [](TimerHandle_t xTimer) {
299 auto* fn = static_cast<std::function<void()>*>(pvTimerGetTimerID(xTimer));
300 if (fn) {
301 (*fn)(); // 🔥 fire the user callback
302 delete fn; // ✅ clean up
303 }
304 xTimerDelete(xTimer, 0);
305 });
306
307 if (handle) {
308 xTimerStart(handle, 0);
309 } else {
310 printf("[TimerService] ERROR: Failed to create xTimer\n");
311 delete cb;
312 }
313}
314
315
uint8_t DaysOfWeek
Type alias for a set of days (bitmask).
Definition DaysOfWeek.h:31
Event pub/sub manager for embedded applications using FreeRTOS.
#define configASSERT(x)
Time utility functions for the Pico platform (RP2040/RP2350).
static uint32_t secondsUntilNextMatch(const TimeOfDay &tod, DaysOfWeek mask, time_t currentTime)
static uint32_t toSeconds(const TimeOfDay &tod)
Time-of-day and interval-based scheduler for embedded events.
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
static time_t now()
Get the current epoch time using platform RTC or AON.
Definition PicoTime.cpp:28
Central service for time-driven scheduling of framework events.
void scheduleAt(time_t unixTime, const Event &event)
Schedule a one-time event at an absolute UNIX timestamp.
void rescheduleDailyJob(const TimerJob &job)
Placeholder for persistence/rescheduling in the future.
void scheduleDailyAt(TimeOfDay time, DaysOfWeek days, const Event &event)
Schedule a recurring event based on time-of-day and day mask.
bool cancel(const std::string &jobId)
void scheduleCallbackAt(time_t unixTime, std::function< void()> callback)
Schedule a one-shot callback at a given absolute time.
void withLock(const std::function< void()> &fn)
SemaphoreHandle_t lock_
std::unordered_map< std::string, TimerHandle_t > scheduledJobs
Map of job IDs to TimerHandles.
void scheduleDuration(TimeOfDay start, DaysOfWeek days, uint32_t durationMs, const Event &startEvent, const Event &stopEvent)
Schedule a start event and stop event with a delay between them.
void checkMissedEvents(time_t now)
Detect and fire any missed events after a reboot (TBD).
void scheduleEvery(uint32_t intervalMs, const Event &event)
Schedule a repeating event at fixed intervals.
static StaticSemaphore_t lockBuffer_
static TimerService & instance()
Access the singleton instance.
Represents a framework event, optionally carrying payload data.
Definition Event.h:24
Notification notification
Notification identifier (system or user)
Definition Event.h:25
uint8_t code() const
Get the notification code.
A simple value type representing a time of day (hour, minute, second).
Definition TimeOfDay.h:22
uint8_t hour
Definition TimeOfDay.h:23
uint8_t minute
Definition TimeOfDay.h:24
uint8_t second
Definition TimeOfDay.h:25
Represents a job scheduled by the TimerService.