From 59b031857921deb03c00c14b1381cf0a1d569709 Mon Sep 17 00:00:00 2001 From: dark98 Date: Fri, 25 Sep 2026 20:57:19 +0100 Subject: [PATCH] feat: implement system time synchronization, web server handlers, and clock application components --- .vscode/settings.json | 2 +- components/sys_cli/sys_cli.c | 71 +++++++++- components/sys_core/CMakeLists.txt | 6 +- components/sys_core/include/sys_time.h | 61 ++++++++ components/sys_core/sys_time.c | 184 +++++++++++++++++++++++++ components/sys_wifi/sys_wifi.c | 4 +- components/web_server/pages/serial.c | 4 +- components/web_server/pages/wifi.c | 8 +- components/web_server/web_server.c | 43 +++++- main/apps/app_clock.c | 88 +++++++++--- main/main.c | 4 +- 11 files changed, 442 insertions(+), 33 deletions(-) create mode 100644 components/sys_core/include/sys_time.h create mode 100644 components/sys_core/sys_time.c diff --git a/.vscode/settings.json b/.vscode/settings.json index db4840b..36f91ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,5 @@ "*.c": "c" }, "idf.currentSetup": "D:\\SDK\\.espressif\\v6.1\\esp-idf", - "idf.portWin": "COM4" + "idf.port": "COM4" } \ No newline at end of file diff --git a/components/sys_cli/sys_cli.c b/components/sys_cli/sys_cli.c index 3b823a8..8f01ba0 100644 --- a/components/sys_cli/sys_cli.c +++ b/components/sys_cli/sys_cli.c @@ -1,13 +1,15 @@ -#include "os_cli.h" +#include "os_cli.h" #include "os_app.h" #include "os_input.h" #include "os_gfx.h" #include "os_oled.h" #include "os_status.h" #include "sys_uart.h" +#include "sys_time.h" #include #include #include +#include #include "esp_console.h" #include "esp_system.h" #include "esp_log.h" @@ -16,6 +18,57 @@ static const char *TAG = "SYS_CLI"; +static int cmd_time(int argc, char **argv) +{ + if (argc >= 2 && strcmp(argv[1], "sync") == 0) { + printf("Triggering SNTP network time sync...\n"); + sys_time_start_sntp(); + return 0; + } else if (argc >= 3 && strcmp(argv[1], "set") == 0) { + time_t epoch = (time_t)strtoull(argv[2], NULL, 10); + if (sys_time_set_epoch(epoch) == ESP_OK) { + printf("System time successfully set to epoch: %lld\n", (long long)epoch); + return 0; + } else { + printf("Failed to set time. Usage: time set \n"); + return 1; + } + } + + struct tm tm_info; + bool synced = sys_time_get_local(&tm_info); + time_t now = sys_time_get_epoch(); + char strftime_buf[64]; + strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S (%A)", &tm_info); + + printf("\n=== System Wall Clock ===\n"); + printf("Status: %s\n", synced ? "SYNCHRONIZED" : "UNSYNCHRONIZED (Uptime only)"); + printf("Local: %s\n", strftime_buf); + printf("Epoch: %lld\n", (long long)now); + printf("Timezone: %s\n", sys_time_get_timezone()); + printf("\nCommands: time sync | time set \n"); + return 0; +} + +static int cmd_tz(int argc, char **argv) +{ + if (argc >= 2) { + if (sys_time_set_timezone(argv[1]) == ESP_OK) { + printf("Timezone set to '%s'\n", argv[1]); + return 0; + } else { + printf("Failed to set timezone\n"); + return 1; + } + } + printf("Current Timezone: %s\n", sys_time_get_timezone()); + printf("Examples: tz \"GMT0BST,M3.5.0/1,M10.5.0\" (UK)\n"); + printf(" tz \"EST5EDT,M3.2.0,M11.1.0\" (US Eastern)\n"); + printf(" tz \"CST-8\" (China / Asia)\n"); + printf(" tz \"UTC0\" (UTC)\n"); + return 0; +} + static int cmd_uart(int argc, char **argv) { if (argc < 2) { @@ -170,6 +223,22 @@ esp_err_t sys_cli_init(void) esp_console_register_help_command(); + const esp_console_cmd_t time_cmd = { + .command = "time", + .help = "View or configure real-time clock (time | time sync | time set )", + .hint = ">", + .func = &cmd_time, + }; + esp_console_cmd_register(&time_cmd); + + const esp_console_cmd_t tz_cmd = { + .command = "tz", + .help = "View or configure timezone string", + .hint = "", + .func = &cmd_tz, + }; + esp_console_cmd_register(&tz_cmd); + const esp_console_cmd_t ps_cmd = { .command = "ps", .help = "List active FreeRTOS tasks and memory", diff --git a/components/sys_core/CMakeLists.txt b/components/sys_core/CMakeLists.txt index 113b2ab..4aef74f 100644 --- a/components/sys_core/CMakeLists.txt +++ b/components/sys_core/CMakeLists.txt @@ -1,8 +1,9 @@ -idf_component_register( +idf_component_register( SRCS "sys_event.c" "sys_status.c" "sys_uart.c" + "sys_time.c" INCLUDE_DIRS "include" REQUIRES @@ -13,4 +14,7 @@ idf_component_register( driver esp_driver_gpio esp_driver_uart + nvs_flash + lwip + esp_netif ) diff --git a/components/sys_core/include/sys_time.h b/components/sys_core/include/sys_time.h new file mode 100644 index 0000000..63f25a8 --- /dev/null +++ b/components/sys_core/include/sys_time.h @@ -0,0 +1,61 @@ +#ifndef SYS_TIME_H +#define SYS_TIME_H + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Default Timezone: UK (GMT / BST with Daylight Saving) */ +#define SYS_TIME_DEFAULT_TZ "GMT0BST,M3.5.0/1,M10.5.0" + +/** + * @brief Initialize system time service and load timezone from NVS. + */ +esp_err_t sys_time_init(void); + +/** + * @brief Start SNTP background synchronization over active network. + */ +void sys_time_start_sntp(void); + +/** + * @brief Check whether real-world wall clock has been synchronized. + */ +bool sys_time_is_synced(void); + +/** + * @brief Get current UTC epoch seconds. + */ +time_t sys_time_get_epoch(void); + +/** + * @brief Get breakdown of current local time. + * @param[out] out_tm Struct tm filled with current local time. + * @return true if time is synchronized, false otherwise. + */ +bool sys_time_get_local(struct tm *out_tm); + +/** + * @brief Set epoch time manually. + */ +esp_err_t sys_time_set_epoch(time_t epoch_sec); + +/** + * @brief Set and persist active timezone string in POSIX format. + */ +esp_err_t sys_time_set_timezone(const char *tz); + +/** + * @brief Get active timezone string. + */ +const char *sys_time_get_timezone(void); + +#ifdef __cplusplus +} +#endif + +#endif // SYS_TIME_H diff --git a/components/sys_core/sys_time.c b/components/sys_core/sys_time.c new file mode 100644 index 0000000..d1c365a --- /dev/null +++ b/components/sys_core/sys_time.c @@ -0,0 +1,184 @@ +#include "sys_time.h" +#include "os_event.h" +#include "os_status.h" +#include "esp_log.h" +#include "esp_netif_sntp.h" +#include "esp_sntp.h" +#include "nvs.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include +#include +#include + +static const char *TAG = "SYS_TIME"; +static char s_tz_str[64] = SYS_TIME_DEFAULT_TZ; +static TaskHandle_t s_sntp_task_handle = NULL; + +static const char *const s_ntp_servers[] = { + "time.google.com", + "time.cloudflare.com", + "pool.ntp.org", + "time.windows.com", + "216.239.35.0" /* Fallback direct IP for time.google.com */ +}; + +static void time_sync_notification_cb(struct timeval *tv) +{ + time_t now = tv ? tv->tv_sec : time(NULL); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + char strftime_buf[64]; + strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S %Z", &timeinfo); + ESP_LOGI(TAG, "SNTP packet received & verified: %s", strftime_buf); + sys_event_post(SYS_EVENT_TICK_1S, 0, NULL); +} + +esp_err_t sys_time_init(void) +{ + nvs_handle_t nvs; + size_t tz_len = sizeof(s_tz_str); + if (nvs_open("system", NVS_READONLY, &nvs) == ESP_OK) { + if (nvs_get_str(nvs, "tz", s_tz_str, &tz_len) != ESP_OK) { + strncpy(s_tz_str, SYS_TIME_DEFAULT_TZ, sizeof(s_tz_str) - 1); + } + nvs_close(nvs); + } else { + strncpy(s_tz_str, SYS_TIME_DEFAULT_TZ, sizeof(s_tz_str) - 1); + } + s_tz_str[sizeof(s_tz_str) - 1] = '\0'; + setenv("TZ", s_tz_str, 1); + tzset(); + + ESP_LOGI(TAG, "System time service initialized (Timezone: %s)", s_tz_str); + return ESP_OK; +} + +static void sntp_sync_task(void *pvParameters) +{ + const size_t num_servers = sizeof(s_ntp_servers) / sizeof(s_ntp_servers[0]); + + for (size_t i = 0; i < num_servers && !sys_time_is_synced(); i++) { + const char *server = s_ntp_servers[i]; + ESP_LOGI(TAG, "Attempting SNTP sync with server [%u/%u]: '%s'...", (unsigned int)(i + 1), (unsigned int)num_servers, server); + + esp_netif_sntp_deinit(); + + esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(server); + config.sync_cb = time_sync_notification_cb; + config.smooth_sync = false; + config.server_from_dhcp = false; + config.wait_for_sync = true; + config.start = true; + + esp_err_t err = esp_netif_sntp_init(&config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SNTP for '%s': %s", server, esp_err_to_name(err)); + continue; + } + + // Poll for sync completion up to 5 seconds per server + for (int r = 0; r < 5; r++) { + vTaskDelay(pdMS_TO_TICKS(1000)); + if (sys_time_is_synced() || sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) { + break; + } + } + + if (sys_time_is_synced()) { + break; + } + } + + if (sys_time_is_synced()) { + time_t now = time(NULL); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + char strftime_buf[64]; + strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S %Z", &timeinfo); + ESP_LOGI(TAG, "SNTP Sync Complete! Current Local Time: %s", strftime_buf); + } else { + ESP_LOGW(TAG, "SNTP sync could not reach any configured NTP server. Device will retry on reconnect."); + } + + s_sntp_task_handle = NULL; + vTaskDelete(NULL); +} + +void sys_time_start_sntp(void) +{ + if (s_sntp_task_handle != NULL) { + ESP_LOGI(TAG, "SNTP sync already running in background"); + return; + } + xTaskCreate(sntp_sync_task, "sntp_task", 4096, NULL, 5, &s_sntp_task_handle); +} + +bool sys_time_is_synced(void) +{ + time_t now = time(NULL); + // Synced if timestamp is after Jan 1, 2024 (1704067200) + return (now >= 1704067200ULL); +} + +time_t sys_time_get_epoch(void) +{ + return time(NULL); +} + +bool sys_time_get_local(struct tm *out_tm) +{ + if (!out_tm) return false; + time_t now = time(NULL); + localtime_r(&now, out_tm); + return sys_time_is_synced(); +} + +esp_err_t sys_time_set_epoch(time_t epoch_sec) +{ + if (epoch_sec < 1704067200ULL) { + ESP_LOGE(TAG, "Invalid epoch timestamp: %lld", (long long)epoch_sec); + return ESP_ERR_INVALID_ARG; + } + struct timeval tv = { + .tv_sec = epoch_sec, + .tv_usec = 0 + }; + int ret = settimeofday(&tv, NULL); + if (ret != 0) { + ESP_LOGE(TAG, "Failed to set time of day"); + return ESP_FAIL; + } + + struct tm timeinfo; + localtime_r(&epoch_sec, &timeinfo); + char strftime_buf[64]; + strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S %Z", &timeinfo); + ESP_LOGI(TAG, "Manual time set: %s", strftime_buf); + sys_event_post(SYS_EVENT_TICK_1S, 0, NULL); + return ESP_OK; +} + +esp_err_t sys_time_set_timezone(const char *tz) +{ + if (!tz || strlen(tz) == 0) return ESP_ERR_INVALID_ARG; + strncpy(s_tz_str, tz, sizeof(s_tz_str) - 1); + s_tz_str[sizeof(s_tz_str) - 1] = '\0'; + setenv("TZ", s_tz_str, 1); + tzset(); + + nvs_handle_t nvs; + if (nvs_open("system", NVS_READWRITE, &nvs) == ESP_OK) { + nvs_set_str(nvs, "tz", s_tz_str); + nvs_commit(nvs); + nvs_close(nvs); + } + ESP_LOGI(TAG, "Timezone updated to: %s", s_tz_str); + return ESP_OK; +} + +const char *sys_time_get_timezone(void) +{ + return s_tz_str; +} diff --git a/components/sys_wifi/sys_wifi.c b/components/sys_wifi/sys_wifi.c index a660783..fe9e2e2 100644 --- a/components/sys_wifi/sys_wifi.c +++ b/components/sys_wifi/sys_wifi.c @@ -1,5 +1,6 @@ -#include "sys_wifi.h" +#include "sys_wifi.h" #include "web_server.h" +#include "sys_time.h" #include "os_event.h" #include "os_status.h" #include "sdkconfig.h" @@ -84,6 +85,7 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t e if (esp_wifi_sta_get_ap_info(&record) == ESP_OK) rssi = record.rssi; set_status(WIFI_STATE_CONNECTED, rssi); web_server_start(); + sys_time_start_sntp(); const ip_event_got_ip_t *got_ip = (const ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "Wi-Fi connected, web server at http://" IPSTR, IP2STR(&got_ip->ip_info.ip)); } diff --git a/components/web_server/pages/serial.c b/components/web_server/pages/serial.c index fbd60dc..c34d19a 100644 --- a/components/web_server/pages/serial.c +++ b/components/web_server/pages/serial.c @@ -1,4 +1,4 @@ -#include "web_server_pages.h" +#include "web_server_pages.h" #include "sys_uart.h" #include "esp_log.h" #include "esp_http_server.h" @@ -227,7 +227,7 @@ static const char SERIAL_HTML[] = " }" "});" "log('[ABrobot Web Serial Connected on TX:GPIO21 / RX:GPIO20 (Internal Pull-Up Active)]\\n', 'log-sys');" -"pollData();" +"try{fetch('/time/sync?epoch='+Math.floor(Date.now()/1000));}catch(e){}pollData();" ""; static esp_err_t serial_page_get(httpd_req_t *request) diff --git a/components/web_server/pages/wifi.c b/components/web_server/pages/wifi.c index 9f63051..4ba5c4c 100644 --- a/components/web_server/pages/wifi.c +++ b/components/web_server/pages/wifi.c @@ -1,4 +1,4 @@ -#include "web_server_pages.h" +#include "web_server_pages.h" #include "sdkconfig.h" #include "esp_log.h" #include "esp_wifi.h" @@ -91,7 +91,7 @@ static const char WIFI_HTML[] = " " "
" " " -" " +" " "
" " " " " @@ -112,12 +112,12 @@ static const char WIFI_HTML[] = "const list=document.getElementById('networks'),manual=document.getElementById('manual'),manualBox=document.getElementById('manualBox'),hidden=document.getElementById('hidden'),passInput=document.getElementById('password'),msg=document.getElementById('statusMsg'),btnConnect=document.getElementById('btnConnect'),btnScan=document.getElementById('btnScan'),scanCount=document.getElementById('scanCount');" "function showMsg(text,type){msg.className='msg-box msg-'+(type||'info');msg.textContent=text;msg.style.display='block';}" "function toggleManual(){let on=hidden.checked;manualBox.style.display=on?'block':'none';manual.required=on;if(on){manual.value='';manual.focus();}else if(list.value)manual.value=list.value;}" -"function togglePassword(){let isPass=passInput.type==='password';passInput.type=isPass?'text':'password';document.getElementById('eyeBtn').textContent=isPass?'🔒':'👁️';}" +"function togglePassword(){let isPass=passInput.type==='password';passInput.type=isPass?'text':'password';document.getElementById('eyeBtn').textContent=isPass?'🔒':'👁️';}" "async function scan(){btnScan.disabled=true;btnScan.textContent='Scanning...';scanCount.textContent='Scanning...';showMsg('Scanning for 2.4 GHz Wi-Fi access points...','info');try{const res=await fetch('/scan');if(!res.ok)throw new Error('HTTP '+res.status);const data=await res.json();list.innerHTML='';if(!data.length){list.innerHTML='';scanCount.textContent='0 APs';showMsg('No networks found. Check router or use manual SSID entry.','error');}else{data.forEach(n=>{const opt=document.createElement('option');opt.value=n.ssid;const qual=n.rssi>-60?'Strong':(n.rssi>-75?'Good':'Weak');opt.textContent=n.ssid+' ('+n.rssi+' dBm - '+qual+')';list.appendChild(opt);});scanCount.textContent=data.length+' APs';if(!hidden.checked)manual.value=list.value;showMsg('Found '+data.length+' Wi-Fi network(s). Select your network and enter credentials.','success');}}catch(e){scanCount.textContent='Error';showMsg('Wi-Fi scan failed: '+e.message,'error');}finally{btnScan.disabled=false;btnScan.textContent='Refresh';}}" "list.onchange=()=>{if(!hidden.checked)manual.value=list.value;};" "async function submitWifi(e){e.preventDefault();const ssidVal=manual.value.trim();const passVal=passInput.value;if(!ssidVal){showMsg('Please select or enter an SSID network name.','error');return;}btnConnect.disabled=true;btnConnect.textContent='Connecting...';showMsg('Saving credentials and connecting to \"'+ssidVal+'\"...','info');try{const body='ssid='+encodeURIComponent(ssidVal)+'&password='+encodeURIComponent(passVal);const res=await fetch('/configure',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body});if(res.ok){showMsg('Credentials saved for \"'+ssidVal+'\". ESP32 is switching to Station mode. Please connect your device to \"'+ssidVal+'\".','success');}else{const err=await res.text();showMsg('Connection failed: '+err,'error');btnConnect.disabled=false;btnConnect.textContent='Connect to Network';}}catch(e){showMsg('Credentials saved for \"'+ssidVal+'\". ESP32 is connecting to Wi-Fi. Please connect to \"'+ssidVal+'\".','success');}}" "if(!hidden.checked&&list.value)manual.value=list.value;" -"scan();" +"try{fetch('/time/sync?epoch='+Math.floor(Date.now()/1000));}catch(e){}scan();" ""; static void copy_string(uint8_t *destination, size_t size, const char *source) diff --git a/components/web_server/web_server.c b/components/web_server/web_server.c index 7a13eed..d1be2d5 100644 --- a/components/web_server/web_server.c +++ b/components/web_server/web_server.c @@ -1,11 +1,42 @@ -#include "web_server.h" +#include "web_server.h" #include "web_server_pages.h" +#include "sys_time.h" #include "esp_http_server.h" #include "esp_log.h" +#include +#include +#include static const char *TAG = "SYS_WEB"; static httpd_handle_t s_server; +static esp_err_t time_sync_handler(httpd_req_t *req) +{ + char query[128] = {0}; + if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { + char val[32] = {0}; + if (httpd_query_key_value(query, "epoch", val, sizeof(val)) == ESP_OK) { + time_t epoch = (time_t)strtoull(val, NULL, 10); + if (epoch >= 1704067200ULL) { + sys_time_set_epoch(epoch); + } + } + } + + struct tm tm_info; + bool synced = sys_time_get_local(&tm_info); + time_t now = sys_time_get_epoch(); + char strftime_buf[64]; + strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d %H:%M:%S", &tm_info); + + char resp[160]; + snprintf(resp, sizeof(resp), "{\"synced\":%s,\"epoch\":%lld,\"time\":\"%s\",\"tz\":\"%s\"}", + synced ? "true" : "false", (long long)now, strftime_buf, sys_time_get_timezone()); + + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, resp); +} + esp_err_t web_server_start(void) { if (s_server) return ESP_OK; @@ -19,6 +50,14 @@ esp_err_t web_server_start(void) web_server_register_wifi_pages(s_server); web_server_register_ota_page(s_server); web_server_register_serial_pages(s_server); - ESP_LOGI(TAG, "Web server started"); + + const httpd_uri_t time_uri = { + .uri = "/time/sync", + .method = HTTP_GET, + .handler = time_sync_handler + }; + httpd_register_uri_handler(s_server, &time_uri); + + ESP_LOGI(TAG, "Web server started with real-time sync service"); return ESP_OK; } diff --git a/main/apps/app_clock.c b/main/apps/app_clock.c index 7ce4e13..4217865 100644 --- a/main/apps/app_clock.c +++ b/main/apps/app_clock.c @@ -1,11 +1,14 @@ #include "apps.h" #include "os_gfx.h" #include "os_status.h" +#include "sys_time.h" #include #include #include "esp_timer.h" static uint8_t s_view_mode = 0; +static const char *const s_days[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; +static const char *const s_months[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; static void clock_init(void) { @@ -23,41 +26,86 @@ static void clock_update(uint32_t delta_ms) sys_gfx_draw_status_bar(&status); - uint32_t sec = status.uptime_sec % 60; - uint32_t min = (status.uptime_sec / 60) % 60; - uint32_t hrs = (status.uptime_sec / 3600); + struct tm tm_info; + bool is_synced = sys_time_get_local(&tm_info); - char time_str[16]; - char sub_str[20]; + uint32_t sec, min, hrs; + if (is_synced) { + sec = tm_info.tm_sec; + min = tm_info.tm_min; + hrs = tm_info.tm_hour; + } else { + sec = status.uptime_sec % 60; + min = (status.uptime_sec / 60) % 60; + hrs = (status.uptime_sec / 3600); + } + + char time_str[32]; + char sub_str[48]; if (s_view_mode == 0) { + // Mode 0: Digital Clock (HH:MM:SS) + Date / Status snprintf(time_str, sizeof(time_str), "%02lu:%02lu:%02lu", (unsigned long)hrs, (unsigned long)min, (unsigned long)sec); sys_gfx_draw_str_5x7(12, 11, time_str, COLOR_WHITE); - snprintf(sub_str, sizeof(sub_str), "RAM:%luK", (unsigned long)(status.free_heap / 1024)); - sys_gfx_draw_str_3x5(4, 21, sub_str, COLOR_WHITE); - - snprintf(sub_str, sizeof(sub_str), "CPU:160M"); - sys_gfx_draw_str_3x5(40, 21, sub_str, COLOR_WHITE); + if (is_synced) { + int wday = tm_info.tm_wday % 7; + int mon = tm_info.tm_mon % 12; + int mday = tm_info.tm_mday % 100; + int year = (tm_info.tm_year + 1900) % 100; + snprintf(sub_str, sizeof(sub_str), "%s %02d-%s-%02d", s_days[wday], mday, s_months[mon], year); + sys_gfx_draw_str_3x5(9, 21, sub_str, COLOR_WHITE); + } else { + snprintf(sub_str, sizeof(sub_str), "NTP: WAITING..."); + sys_gfx_draw_str_3x5(8, 21, sub_str, COLOR_WHITE); + } sys_gfx_draw_progress_bar(4, 27, 64, 4, (uint8_t)((sec * 100) / 59)); sys_gfx_draw_action_bar("C:MODE", "H:HOME"); } else if (s_view_mode == 1) { - snprintf(time_str, sizeof(time_str), "%02lu:%02lu", (unsigned long)min, (unsigned long)sec); - sys_gfx_draw_str_5x7(18, 14, time_str, COLOR_WHITE); - sys_gfx_draw_str_5x7(19, 14, time_str, COLOR_WHITE); + // Mode 1: Large Minimalist Time (HH:MM) + Seconds + Date + snprintf(time_str, sizeof(time_str), "%02lu:%02lu", (unsigned long)hrs, (unsigned long)min); + sys_gfx_draw_str_5x7(16, 13, time_str, COLOR_WHITE); + sys_gfx_draw_str_5x7(17, 13, time_str, COLOR_WHITE); // Bold effect - snprintf(sub_str, sizeof(sub_str), "UP:%luh %lum", (unsigned long)hrs, (unsigned long)min); - sys_gfx_draw_str_3x5(12, 24, sub_str, COLOR_WHITE); + char sec_str[16]; + snprintf(sec_str, sizeof(sec_str), ":%02lu", (unsigned long)sec); + sys_gfx_draw_str_3x5(49, 14, sec_str, COLOR_WHITE); + + if (is_synced) { + int wday = tm_info.tm_wday % 7; + int mon = tm_info.tm_mon % 12; + int mday = tm_info.tm_mday % 100; + snprintf(sub_str, sizeof(sub_str), "%s %02d %s", s_days[wday], mday, s_months[mon]); + sys_gfx_draw_str_3x5(14, 23, sub_str, COLOR_WHITE); + } else { + snprintf(sub_str, sizeof(sub_str), "UP:%luh %lum (SYNC..)", (unsigned long)hrs, (unsigned long)min); + sys_gfx_draw_str_3x5(4, 23, sub_str, COLOR_WHITE); + } sys_gfx_draw_action_bar("C:MODE", "H:HOME"); } else { - snprintf(sub_str, sizeof(sub_str), "UP:%02lu:%02lu:%02lu", (unsigned long)hrs, (unsigned long)min, (unsigned long)sec); - sys_gfx_draw_str_3x5(3, 10, sub_str, COLOR_WHITE); + // Mode 2: Detailed System Clock & Telemetry Diagnostics + if (is_synced) { + snprintf(sub_str, sizeof(sub_str), "TIME:%02lu:%02lu:%02lu", (unsigned long)hrs, (unsigned long)min, (unsigned long)sec); + sys_gfx_draw_str_3x5(3, 10, sub_str, COLOR_WHITE); - snprintf(sub_str, sizeof(sub_str), "HEAP:%lu B", (unsigned long)status.free_heap); - sys_gfx_draw_str_3x5(3, 17, sub_str, COLOR_WHITE); + int year = (tm_info.tm_year + 1900) % 10000; + int mon = (tm_info.tm_mon + 1) % 100; + int mday = tm_info.tm_mday % 100; + snprintf(sub_str, sizeof(sub_str), "DATE:%04d-%02d-%02d", year, mon, mday); + sys_gfx_draw_str_3x5(3, 17, sub_str, COLOR_WHITE); + } else { + snprintf(sub_str, sizeof(sub_str), "NTP: NOT SYNCED"); + sys_gfx_draw_str_3x5(3, 10, sub_str, COLOR_WHITE); - snprintf(sub_str, sizeof(sub_str), "MINH:%lu B", (unsigned long)status.min_free_heap); + snprintf(sub_str, sizeof(sub_str), "HEAP:%lu KB", (unsigned long)(status.free_heap / 1024)); + sys_gfx_draw_str_3x5(3, 17, sub_str, COLOR_WHITE); + } + + uint32_t up_sec = status.uptime_sec % 60; + uint32_t up_min = (status.uptime_sec / 60) % 60; + uint32_t up_hrs = (status.uptime_sec / 3600); + snprintf(sub_str, sizeof(sub_str), "UP: %02lu:%02lu:%02lu", (unsigned long)up_hrs, (unsigned long)up_min, (unsigned long)up_sec); sys_gfx_draw_str_3x5(3, 24, sub_str, COLOR_WHITE); sys_gfx_draw_action_bar("C:MODE", "H:HOME"); diff --git a/main/main.c b/main/main.c index d2b90c3..9894a97 100644 --- a/main/main.c +++ b/main/main.c @@ -1,4 +1,4 @@ -#include +#include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -12,6 +12,7 @@ /* OS Subsystems */ #include "os_event.h" #include "os_status.h" +#include "sys_time.h" #include "os_gfx.h" #include "os_input.h" #include "os_app.h" @@ -87,6 +88,7 @@ void app_main(void) // 1. Core Services & Telemetry sys_event_init(); sys_status_init(); + sys_time_init(); esp_err_t err; // 1b. Wi-Fi and provisioning services