Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
TimeManager.cpp
Go to the documentation of this file.
1// TimeManager.cpp
2#include "time/TimeManager.h"
3
4#include <ctime>
5#include <iostream>
6#include <pico/stdlib.h>
7#include <pico/cyw43_arch.h>
8#include "pico/aon_timer.h"
9#include <lwip/apps/sntp.h>
10#include <FreeRTOS.h>
11#include <task.h>
12#include "events/EventManager.h"
13#include "events/Notification.h"
14#include "time/PicoTime.h"
16#include "http/HttpRequest.h"
17#include "http/HttpResponse.h"
18#include "framework_config.h"
19#include "DebugTrace.h"
21
22#if defined(PICO_RP2040)
23#include "hardware/rtc.h"
24#endif
25#include "time/PicoTime.h"
26
27
28#ifndef PICO_HTTP_ENABLE_LITTLEFS
29#include "FreeRTOS_time.h"
30#endif
31
32// Set system time from SNTP - this callback is defined in lwipopts.h
33extern "C" void sntp_set_system_time(uint32_t sec)
34{
35 //TRACE("[SNTP] callback made\n");
36 AppContext::get<TimeManager>()->setTimeFromEpoch(sec);
37}
38
40{
41 if (!sntp_enabled())
42 {
43 //TRACE("[Time Manager] Initializing SNTP client...\n");
44 sntp_setoperatingmode(SNTP_OPMODE_POLL);
45 sntp_setservername(0, "pool.ntp.org");
46 sntp_init();
47 }
48}
49
50bool TimeManager::syncTimeWithNtp(int timeoutSeconds)
51{
52 printf("[Time Manager] Waiting for NTP time sync...\n");
53
54 // Wait up to timeoutSeconds for time to be set
55 int waited = 0;
56 while (waited < timeoutSeconds)
57 {
58 if(timeSynced)
59 {
60 timespec ts;
61 if(aon_timer_get_time(&ts)){
62 if (ts.tv_sec > 1735689600)
63 { // sanity check: after Jan 1 2025
64 printf("[Time Manager] NTP time acquired successfuly\n");
65 return true;
66 }
67 else
68 {
69 printf("[Time Manager] System time epoch is invalid: %ld\n", ts.tv_sec);
70 return false;
71 }
72 }
73 else
74 {
75 printf("[Time Manager] Failed to get system time from AON timer.\n");
76 return false;
77 }
78 }
79 vTaskDelay(pdMS_TO_TICKS(1000));
80 waited++;
81 }
82
83 // Timeout occurred
84 if (!isTimeValid()) {
85 printf("[Time Manager] NTP sync failed and no valid time source available.\n");
86 Event event;
88 AppContext::get<EventManager>()->postEvent(event);
89 }
90 else {
91 printf("[Time Manager] NTP sync failed, but AON timer is running — time still valid.\n");
92 }
93 return false;
94}
95
97{
98 printf("[TimeManager] Setting system time from epoch: %u\n", epoch);
99 timespec ts = {
100 .tv_sec = epoch,
101 .tv_nsec = 0};
102 setTime(&ts);
103 timeSynced = true;
104 Event event;
106 AppContext::get<EventManager>()->postEvent(event);
107 printf("[TimeManager] System time set to: %s\n", ctime(&ts.tv_sec));
108}
109
119void TimeManager::setTime(timespec *ts)
120{
121 if (ts == nullptr)
122 {
123 printf("[TimeManager] Invalid timespec provided.\n");
124 return;
125 }
126
127 // if the AON timer is not running, start it
128 if (!aon_timer_is_running()){
129 printf("[TimeManager] AON timer is not running, starting it...\n");
130 aon_timer_start(ts);
131 }
132 else{
133 // if the AON timer is running, set the time
134 printf("[TimeManager] AON timer is running, syncing time...\n");
135 aon_timer_set_time(ts);
136 }
137 timespec currentTime;
138 if(!aon_timer_get_time(&currentTime))
139 {
140 printf("[TimeManager] Failed to get system time from AON timer.\n");
141 return;
142 }
143 else{
145 }
146}
147
148void TimeManager::applyFixedTimezoneOffset(int offsetSeconds, const char *stdName, const char *dstName)
149{
150 timezoneOffsetSeconds = offsetSeconds;
151 timezoneName = stdName;
152
154 AppContext::get<EventManager>()->postEvent(e);
155
156 // Log for trace/debug purposes
157 printf("[TimeManager] Timezone set to UTC %+d:00 (%s)\n",
158 offsetSeconds / 3600, stdName);
159}
160
161bool TimeManager::getLocationFromIp(std::string &tzName, float &lat, float &lon)
162{
163 HttpRequest req;
164 HttpResponse res = req.get("http://ip-api.com/json");
165
166 const std::string &body = res.getBody();
167 if (body.empty())
168 {
169 printf("[TimeManager] Failed to get IP geolocation.\n");
170 return false;
171 }
172
173 // Parse timezone
174 size_t tzPos = body.find("\"timezone\":\"");
175 if (tzPos != std::string::npos)
176 {
177 tzPos += strlen("\"timezone\":\"");
178 size_t end = body.find('"', tzPos);
179 if (end != std::string::npos)
180 {
181 tzName = body.substr(tzPos, end - tzPos);
182 }
183 }
184 else
185 {
186 tzName = "UTC";
187 }
188
189 // Parse lat/lon
190 size_t latPos = body.find("\"lat\":");
191 size_t lonPos = body.find("\"lon\":");
192 if (latPos == std::string::npos || lonPos == std::string::npos)
193 {
194 printf("[TimeManager] lat/lon not found. Using defaults.\n");
195 lat = 0.0f;
196 lon = 0.0f;
197 return false;
198 }
199
200 lat = std::stof(body.substr(latPos + 6));
201 lon = std::stof(body.substr(lonPos + 6));
202 return true;
203}
204
205void TimeManager::fetchAndApplyTimezoneFromOpenMeteo(float lat, float lon, const std::string &tzName)
206{
207 char url[256];
208 snprintf(url, sizeof(url),
209 "http://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f&current_weather=true&timezone=auto",
210 lat, lon);
211
212 HttpRequest req;
213 HttpResponse res = req.get(url);
214
215 const std::string &body = res.getBody();
216 TRACE("[TimeManager] Open-Meteo response: %s\n", body.c_str());
217 TRACE("Status code: %d\n", res.getStatusCode());
218 if (body.empty())
219 {
220 printf("[TimeManager] Open-Meteo response is empty.\n");
221 applyFixedTimezoneOffset(0, tzName.c_str(), tzName.c_str());
222 return;
223 }
224
225 size_t offsetPos = body.find("\"utc_offset_seconds\":");
226 int offsetSeconds = 0;
227 if (offsetPos != std::string::npos)
228 {
229 offsetPos += strlen("\"utc_offset_seconds\":");
230 size_t end = body.find(',', offsetPos);
231 if (end == std::string::npos)
232 end = body.size();
233 offsetSeconds = std::stoi(body.substr(offsetPos, end - offsetPos));
234 }
235 TRACE("[TimeManager] Timezone: %s, UTC offset: %d sec\n", tzName.c_str(), offsetSeconds);
236 applyFixedTimezoneOffset(offsetSeconds, tzName.c_str(), tzName.c_str());
237}
238
240{
241 std::string tzName = "UTC";
242 float lat = 0.0f, lon = 0.0f;
243
244 if (getLocationFromIp(tzName, lat, lon))
245 {
246 printf("[TimeManager] Location detected: lat=%.4f, lon=%.4f\n", lat, lon);
247 fetchAndApplyTimezoneFromOpenMeteo(lat, lon, tzName);
248 }
249 else
250 {
251 printf("[TimeManager] Could not determine location. Using default UTC.\n");
252 applyFixedTimezoneOffset(0, tzName.c_str(), tzName.c_str());
253 }
254}
255
256std::string TimeManager::formatTimeWithZone(time_t utcTime) const
257{
258 if (utcTime == 0) {
259 utcTime = PicoTime::now(); // Use the system time
260 }
261
262 time_t localTime = utcTime + timezoneOffsetSeconds;
263 struct tm tmBuf;
264 gmtime_r(&localTime, &tmBuf); // Adjusted time treated as UTC
265
266 char timeBuf[16] = {0};
267 strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", &tmBuf);
268
269 const char *zone = timezoneName.empty() ? "?" : timezoneName.c_str();
270
271 char formatted[48] = {0};
272 snprintf(formatted, sizeof(formatted), "[%s %s]", timeBuf, zone);
273 return std::string(formatted);
274}
275
277{
279}
280
286
288{
289 initNtpClient(); // Initialize SNTP client - this will automatically cause a timesync event when that happens
290 if (!isTimeValid())
291 {
293 }
294
295}
296
299 {
301 }
302 else
303 {
304 applyFixedTimezoneOffset(0, "UTC", "UTC");
305 }
306}
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
#define TRACE(...)
Default trace (INFO level).
Definition DebugTrace.h:187
Event pub/sub manager for embedded applications using FreeRTOS.
struct tm * gmtime_r(const time_t *pxTime, struct tm *tmStruct)
Defines the HttpRequest class for handling HTTP requests: headers, method, path, query string,...
HTTP HttpResponse class for managing status, headers, body, and streaming support.
constexpr uint32_t eventMask(Enum e)
Helper function to create an event mask from an enum value.
Time utility functions for the Pico platform (RP2040/RP2350).
void sntp_set_system_time(uint32_t sec)
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
HttpResponse get()
Send a GETPOST/PUT/DEL request.
Represents an HTTP response object.
int getStatusCode() const
Get the response status code.
const std::string & getBody() const
Get the response body.
static time_t now()
Get the current epoch time using platform RTC or AON.
Definition PicoTime.cpp:28
bool getLocationFromIp(std::string &tzName, float &lat, float &lon)
int timezoneOffsetSeconds
void applyFixedTimezoneOffset(int offsetSeconds, const char *stdName="UTC", const char *dstName="UTC")
Apply a fixed timezone offset.
void setTime(timespec *ts)
Set the system time using a timespec structure.
void detectAndApplyTimezone()
Detect and apply the timezone based on the current location or IP address.
std::string timezoneName
void fetchAndApplyTimezoneFromOpenMeteo(float lat, float lon, const std::string &tzName)
Fetch and apply the timezone from OpenMeteo API using latitude and longitude.
void onNetworkReady()
hanles network ready event.
void initNtpClient()
bool isTimeValid()
Definition TimeManager.h:84
void setTimeFromEpoch(uint32_t epochSeconds)
Set the system time from an epoch timestamp (e.g. from SNTP)
void onHttpServerStarted()
bool syncTimeWithNtp(int timeoutSeconds=20)
Initialize the time manager.
std::string formatTimeWithZone(time_t rawTime=0) const
std::string currentTimeForTrace() const
Get the current time formatted as a string.
void start()
Start the time manager.
Delegates to user or system configuration.
#define NTP_TIMEOUT_SECONDS
This setting defines the retry timeout The default SNTP retry time is 15 seconds, we set it to 5 in l...
#define DETECT_LOCAL_TIMEZONE
Represents a framework event, optionally carrying payload data.
Definition Event.h:24
Notification notification
Notification identifier (system or user)
Definition Event.h:25