44#include <stdlib.h>
55#include <string.h>
66#include <sys/stat.h>
7+ #include <sys/time.h>
8+ #include <time.h>
79
810#include "bsp/esp-bsp.h"
911#include "esp_app_desc.h"
1719#include "esp_netif.h"
1820#include "esp_ota_ops.h"
1921#include "esp_spiffs.h"
22+ #include "esp_sntp.h"
2023#include "esp_system.h"
2124#include "esp_wifi.h"
2225#include "esp_crt_bundle.h"
5659#define BATTERY_EMPTY_MV 6000
5760#define BATTERY_FULL_MV 8230
5861#define BATTERY_HISTORY_POINTS 60
62+ #define TIME_ZONE "CST6CDT,M3.2.0,M11.1.0"
5963
6064typedef struct __attribute__((packed )) {
6165 char magic [4 ];
@@ -105,6 +109,15 @@ static const ebook_default_t ebook_defaults[] = {
105109static lv_obj_t * content ;
106110static lv_obj_t * battery_label ;
107111static i2c_master_dev_handle_t battery_monitor ;
112+ static i2c_master_dev_handle_t rtc ;
113+ static lv_obj_t * time_label ;
114+ static lv_obj_t * clock_time ;
115+ static lv_obj_t * clock_date ;
116+ static lv_obj_t * clock_status ;
117+ static bool rtc_time_loaded ;
118+ static bool sntp_started ;
119+ static volatile bool internet_time_synced ;
120+ static volatile bool rtc_sync_pending ;
108121static lv_obj_t * battery_metrics ;
109122static lv_obj_t * battery_chart ;
110123static lv_chart_series_t * battery_series ;
@@ -224,9 +237,113 @@ static void show_settings(void);
224237static void show_chat (void );
225238static void show_browser (void );
226239static void show_ebooks (void );
240+ static void show_clock (void );
227241static void clear_content (void );
228242static void browser_link_clicked (lv_event_t * event );
229243
244+ static uint8_t bcd (int value )
245+ {
246+ return (value / 10 << 4 ) | value % 10 ;
247+ }
248+
249+ static int unbcd (uint8_t value )
250+ {
251+ return (value >> 4 ) * 10 + (value & 0x0f );
252+ }
253+
254+ static void rtc_write_system_time (void )
255+ {
256+ if (!rtc ) return ;
257+ time_t now = time (NULL );
258+ struct tm utc ;
259+ gmtime_r (& now , & utc );
260+ uint8_t values [] = {0x10 , bcd (utc .tm_sec ), bcd (utc .tm_min ), bcd (utc .tm_hour ),
261+ bcd (utc .tm_wday ), bcd (utc .tm_mday ), bcd (utc .tm_mon + 1 ), bcd (utc .tm_year % 100 )};
262+ if (i2c_master_transmit (rtc , values , sizeof (values ), 50 ) == ESP_OK ) {
263+ uint8_t flag_reg = 0x1d ;
264+ uint8_t flag ;
265+ if (i2c_master_transmit_receive (rtc , & flag_reg , 1 , & flag , 1 , 50 ) == ESP_OK ) {
266+ uint8_t clear_vlf [] = {0x1d , flag & ~0x02 };
267+ i2c_master_transmit (rtc , clear_vlf , sizeof (clear_vlf ), 50 );
268+ }
269+ rtc_time_loaded = true;
270+ }
271+ }
272+
273+ static void time_synced (struct timeval * tv )
274+ {
275+ (void )tv ;
276+ internet_time_synced = true;
277+ rtc_sync_pending = true;
278+ }
279+
280+ static void start_sntp (void )
281+ {
282+ if (sntp_started ) return ;
283+ esp_sntp_setoperatingmode (SNTP_OPMODE_POLL );
284+ esp_sntp_setservername (0 , "pool.ntp.org" );
285+ esp_sntp_set_time_sync_notification_cb (time_synced );
286+ esp_sntp_init ();
287+ sntp_started = true;
288+ }
289+
290+ static void clock_init (i2c_master_bus_handle_t bus )
291+ {
292+ i2c_device_config_t config = {
293+ .dev_addr_length = I2C_ADDR_BIT_LEN_7 ,
294+ .device_address = 0x32 ,
295+ .scl_speed_hz = 400000 ,
296+ };
297+ if (i2c_master_bus_add_device (bus , & config , & rtc ) != ESP_OK ) rtc = NULL ;
298+
299+ uint8_t reg = 0x10 ;
300+ uint8_t raw [14 ];
301+ if (rtc && i2c_master_transmit_receive (rtc , & reg , 1 , raw , sizeof (raw ), 50 ) == ESP_OK ) {
302+ struct tm value = {
303+ .tm_sec = unbcd (raw [0 ] & 0x7f ), .tm_min = unbcd (raw [1 ] & 0x7f ),
304+ .tm_hour = unbcd (raw [2 ] & 0x3f ), .tm_wday = unbcd (raw [3 ] & 0x7f ),
305+ .tm_mday = unbcd (raw [4 ] & 0x3f ), .tm_mon = unbcd (raw [5 ] & 0x1f ) - 1 ,
306+ .tm_year = unbcd (raw [6 ]) + 100 , .tm_isdst = 0 ,
307+ };
308+ if (!(raw [13 ] & 0x02 ) && value .tm_year >= 124 && value .tm_mon >= 0 && value .tm_mon < 12 &&
309+ value .tm_mday > 0 && value .tm_mday <= 31 && value .tm_hour < 24 && value .tm_min < 60 && value .tm_sec < 60 ) {
310+ setenv ("TZ" , "UTC0" , 1 );
311+ tzset ();
312+ struct timeval tv = {.tv_sec = mktime (& value )};
313+ settimeofday (& tv , NULL );
314+ rtc_time_loaded = true;
315+ }
316+ }
317+ setenv ("TZ" , TIME_ZONE , 1 );
318+ tzset ();
319+ assert (unbcd (bcd (59 )) == 59 );
320+ }
321+
322+ static void clock_tick (lv_timer_t * timer )
323+ {
324+ (void )timer ;
325+ if (rtc_sync_pending ) {
326+ rtc_sync_pending = false;
327+ rtc_write_system_time ();
328+ }
329+ time_t now = time (NULL );
330+ struct tm local ;
331+ localtime_r (& now , & local );
332+ bool valid = local .tm_year >= 124 ;
333+ char text [40 ];
334+ if (valid ) strftime (text , sizeof (text ), "%I:%M %p" , & local );
335+ else snprintf (text , sizeof (text ), "--:--" );
336+ if (time_label ) lv_label_set_text (time_label , text );
337+ if (clock_time ) lv_label_set_text (clock_time , text );
338+ if (clock_date ) {
339+ if (valid ) strftime (text , sizeof (text ), "%A, %B %d, %Y" , & local );
340+ else snprintf (text , sizeof (text ), "Time not set" );
341+ lv_label_set_text (clock_date , text );
342+ }
343+ if (clock_status ) lv_label_set_text (clock_status , internet_time_synced ? "Synced from internet" :
344+ rtc_time_loaded ? "Running from hardware RTC" : "Connect to Wi-Fi to set the clock" );
345+ }
346+
230347static void battery_tick (lv_timer_t * timer )
231348{
232349 (void )timer ;
@@ -321,6 +438,7 @@ static void wifi_event(void *arg, esp_event_base_t base, int32_t id, void *data)
321438 wifi_retries = 0 ;
322439 wifi_connecting = false;
323440 wifi_connected = true;
441+ start_sntp ();
324442 }
325443}
326444
@@ -1308,6 +1426,9 @@ static void clear_content(void)
13081426 battery_metrics = NULL ;
13091427 battery_chart = NULL ;
13101428 battery_series = NULL ;
1429+ clock_time = NULL ;
1430+ clock_date = NULL ;
1431+ clock_status = NULL ;
13111432 lv_obj_clean (content );
13121433 lv_obj_set_flex_flow (content , LV_FLEX_FLOW_COLUMN );
13131434 lv_obj_set_flex_align (content , LV_FLEX_ALIGN_START , LV_FLEX_ALIGN_CENTER , LV_FLEX_ALIGN_CENTER );
@@ -2165,6 +2286,23 @@ static void system_clicked(lv_event_t *event)
21652286 ota_timer = lv_timer_create (ota_tick , 250 , NULL );
21662287}
21672288
2289+ static void clock_clicked (lv_event_t * event )
2290+ {
2291+ (void )event ;
2292+ show_clock ();
2293+ }
2294+
2295+ static void show_clock (void )
2296+ {
2297+ clear_content ();
2298+ clock_time = lv_label_create (content );
2299+ lv_obj_set_style_text_font (clock_time , & lv_font_montserrat_48 , 0 );
2300+ clock_date = lv_label_create (content );
2301+ lv_obj_set_style_text_font (clock_date , & lv_font_montserrat_28 , 0 );
2302+ clock_status = lv_label_create (content );
2303+ clock_tick (NULL );
2304+ }
2305+
21682306static void show_launcher (void )
21692307{
21702308 static int32_t columns [] = {
@@ -2183,6 +2321,7 @@ static void show_launcher(void)
21832321 app_icon (content , LV_SYMBOL_ENVELOPE , "AI Chat" , 0xE91E63 , chat_clicked , 2 , 1 );
21842322 app_icon (content , LV_SYMBOL_EYE_OPEN , "Browser" , 0x3F51B5 , browser_clicked , 0 , 2 );
21852323 app_icon (content , LV_SYMBOL_FILE , "Ebooks" , 0x8D6E63 , ebooks_clicked , 1 , 2 );
2324+ app_icon (content , LV_SYMBOL_LOOP , "Clock" , 0x009688 , clock_clicked , 2 , 2 );
21862325}
21872326
21882327static void confirm_running_ota (void )
@@ -2204,6 +2343,7 @@ void app_main(void)
22042343 vTaskDelay (pdMS_TO_TICKS (50 ));
22052344 bsp_set_charge_en (true);
22062345 battery_init (bsp_i2c_get_handle ());
2346+ clock_init (bsp_i2c_get_handle ());
22072347 vTaskDelay (pdMS_TO_TICKS (250 ));
22082348
22092349 wifi_ready = start_wifi ();
@@ -2236,9 +2376,13 @@ void app_main(void)
22362376 lv_obj_set_style_text_font (brand , & lv_font_montserrat_28 , 0 );
22372377 lv_obj_align (brand , LV_ALIGN_CENTER , 0 , 0 );
22382378 battery_label = lv_label_create (header );
2239- lv_obj_align (battery_label , LV_ALIGN_RIGHT_MID , -20 , 0 );
2379+ lv_obj_align (battery_label , LV_ALIGN_RIGHT_MID , -20 , -18 );
2380+ time_label = lv_label_create (header );
2381+ lv_obj_align (time_label , LV_ALIGN_RIGHT_MID , -20 , 18 );
22402382 battery_tick (NULL );
22412383 lv_timer_create (battery_tick , 5000 , NULL );
2384+ clock_tick (NULL );
2385+ lv_timer_create (clock_tick , 1000 , NULL );
22422386
22432387 content = lv_obj_create (screen );
22442388 lv_obj_set_size (content , 720 , 1180 );
0 commit comments