From ec5e98709cfc10857e961f86234acedbc090e9e2 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Thu, 8 May 2025 19:57:53 -0500 Subject: [PATCH 1/8] Menu option to update RTC from internet manually --- src/ui/page_clock.c | 172 ++++++++++++++++++++++++++++-------------- src/ui/page_clock.h | 3 +- src/util/ntp_client.c | 145 +++++++++++++++++++++++++++++++++++ src/util/ntp_client.h | 17 +++++ 4 files changed, 279 insertions(+), 58 deletions(-) create mode 100644 src/util/ntp_client.c create mode 100644 src/util/ntp_client.h diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 8d334047..24fe4761 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -1,4 +1,5 @@ #include "page_clock.h" +#include "util/ntp_client.h" // Incluir la cabecera aquí #include #include @@ -45,6 +46,7 @@ typedef enum { ITEM_SECOND, ITEM_FORMAT, ITEM_SET_CLOCK, + ITEM_SYNC_NTP, // Nueva opción para sincronización NTP ITEM_BACK, ITEM_LIST_TOTAL @@ -66,7 +68,7 @@ typedef struct { */ static const int MAX_YEARS_DROPDOWN = 300; // 2023 + 300 == 2323 static lv_coord_t col_dsc[] = {160, 160, 160, 160, 160, 160, LV_GRID_TEMPLATE_LAST}; -static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 15, 10, 60, 60, 60, LV_GRID_TEMPLATE_LAST}; +static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 60, 15, 10, 60, 60, 60, LV_GRID_TEMPLATE_LAST}; static item_t page_clock_items[ITEM_LIST_TOTAL] = {0}; static int page_clock_item_selected = ITEM_YEAR; static int page_clock_item_focused = 0; @@ -243,6 +245,13 @@ static void page_clock_set_clock_reset() { page_clock_set_clock_confirm = 0; } +/** + * Reset the 'Sync from Internet' text back to its initial state. + */ +static void page_clock_sync_reset_cb(struct _lv_timer_t *timer) { + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, _lang("Sync from Internet")); +} + /** * Determine if the type of object selected is a lv_obj_t. */ @@ -389,12 +398,17 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { page_clock_items[ITEM_SET_CLOCK].type = ITEM_TYPE_BTN; page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[3]; + // Nuevo botón para sincronización NTP + page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 4, 3); + page_clock_items[ITEM_SYNC_NTP].type = ITEM_TYPE_BTN; + page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[4]; + snprintf(buf, sizeof(buf), "< %s", _lang("Back")); - page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 4, 1); + page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 5, 1); page_clock_items[ITEM_BACK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_BACK].panel = arr->panel[4]; + page_clock_items[ITEM_BACK].panel = arr->panel[5]; - page_clock_create_datetime_item(cont, 5); + page_clock_create_datetime_item(cont, 6); if (rtc_has_battery() != 0) { lv_obj_t *note = lv_label_create(cont); @@ -405,7 +419,7 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { lv_obj_set_style_text_color(note, lv_color_make(255, 255, 255), 0); lv_obj_set_style_pad_top(note, 12, 0); lv_label_set_long_mode(note, LV_LABEL_LONG_WRAP); - lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 6, 2); + lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 7, 2); } page_clock_clear_datetime(); @@ -531,60 +545,104 @@ static void page_clock_on_click(uint8_t key, int sel) { page_clock_set_clock_confirm = 1; } break; - case ITEM_BACK: - submenu_exit(); - break; - default: - if (!page_clock_item_focused) { - page_clock_items[page_clock_item_selected].last_option = - lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); - - lv_obj_t *list = lv_dropdown_get_list(page_clock_items[page_clock_item_selected].data.obj); - lv_dropdown_open(page_clock_items[page_clock_item_selected].data.obj); - lv_obj_add_style(list, &style_dropdown, LV_PART_MAIN); - lv_obj_set_style_text_color(list, lv_color_make(0, 0, 0), LV_PART_SELECTED | LV_STATE_CHECKED); - page_clock_item_focused = 1; - } else { - lv_event_send(page_clock_items[page_clock_item_selected].data.obj, LV_EVENT_RELEASED, NULL); - page_clock_item_focused = 0; - int option = lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); - - if (page_clock_items[page_clock_item_selected].last_option != option) { - page_clock_items[page_clock_item_selected].last_option = option; - page_clock_is_dirty = 1; - if (!page_clock_set_clock_pending_timer) { - page_clock_set_clock_pending_timer = lv_timer_create(page_clock_set_clock_pending_cb, 50, NULL); - lv_timer_set_repeat_count(page_clock_set_clock_pending_timer, -1); - } - } - - if (page_clock_item_selected == ITEM_YEAR || - page_clock_item_selected == ITEM_MONTH) { - struct rtc_date date; - page_clock_build_date_from_selected(&date); - page_clock_build_options_from_date(&date); + case ITEM_SYNC_NTP: + if (page_clock_set_clock_confirm) { + snprintf(buf, sizeof(buf), "#FF0000 %s %s...#", _lang("Syncing"), _lang("Clock")); + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); + + // Sincronizar desde NTP + if (clock_sync_from_ntp() == 0) { + // Éxito, actualizar la interfaz + rtc_get_clock(&page_clock_rtc_date); + page_clock_build_options_from_date(&page_clock_rtc_date); + page_clock_refresh_datetime(); + + // Guardar en settings + g_setting.clock.year = page_clock_rtc_date.year; + g_setting.clock.month = page_clock_rtc_date.month; + g_setting.clock.day = page_clock_rtc_date.day; + g_setting.clock.hour = page_clock_rtc_date.hour; + g_setting.clock.min = page_clock_rtc_date.min; + g_setting.clock.sec = page_clock_rtc_date.sec; + + // Actualizar archivo de configuración + ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); + ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); + ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); + ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); + ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); + ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + + snprintf(buf, sizeof(buf), "#00FF00 %s#", _lang("Sync Complete")); + } else { + // Error + snprintf(buf, sizeof(buf), "#FF0000 %s#", _lang("Sync Failed")); } - } - break; - } + + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); + lv_timer_t *reset_timer = lv_timer_create(page_clock_sync_reset_cb, 2000, NULL); + lv_timer_set_repeat_count(reset_timer, 1); + page_clock_set_clock_confirm = 0; + } else { + snprintf(buf, sizeof(buf), "#FFFF00 %s...#", _lang("Click to confirm or Scroll to cancel")); + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); + page_clock_set_clock_confirm = 1; + } + break; + case ITEM_BACK: + submenu_exit(); + break; + default: + if (!page_clock_item_focused) { + page_clock_items[page_clock_item_selected].last_option = + lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); + + lv_obj_t *list = lv_dropdown_get_list(page_clock_items[page_clock_item_selected].data.obj); + lv_dropdown_open(page_clock_items[page_clock_item_selected].data.obj); + lv_obj_add_style(list, &style_dropdown, LV_PART_MAIN); + lv_obj_set_style_text_color(list, lv_color_make(0, 0, 0), LV_PART_SELECTED | LV_STATE_CHECKED); + page_clock_item_focused = 1; + } else { + lv_event_send(page_clock_items[page_clock_item_selected].data.obj, LV_EVENT_RELEASED, NULL); + page_clock_item_focused = 0; + int option = lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); + + if (page_clock_items[page_clock_item_selected].last_option != option) { + page_clock_items[page_clock_item_selected].last_option = option; + page_clock_is_dirty = 1; + if (!page_clock_set_clock_pending_timer) { + page_clock_set_clock_pending_timer = lv_timer_create(page_clock_set_clock_pending_cb, 50, NULL); + lv_timer_set_repeat_count(page_clock_set_clock_pending_timer, -1); + } + } + + if (page_clock_item_selected == ITEM_YEAR || + page_clock_item_selected == ITEM_MONTH) { + struct rtc_date date; + page_clock_build_date_from_selected(&date); + page_clock_build_options_from_date(&date); + } + } + break; + } } /** - * Main Menu page data structure, notice max is set to zero - * in order to allow us to override default user input logic. - */ +* Main Menu page data structure, notice max is set to zero +* in order to allow us to override default user input logic. +*/ page_pack_t pp_clock = { - .p_arr = { - .cur = 0, - .max = 0, - }, - .name = "Clock", - .create = page_clock_create, - .enter = page_clock_enter, - .exit = page_clock_exit, - .on_created = NULL, - .on_update = NULL, - .on_roller = page_clock_on_roller, - .on_click = page_clock_on_click, - .on_right_button = NULL, -}; + .p_arr = { + .cur = 0, + .max = 0, + }, + .name = "Clock", + .create = page_clock_create, + .enter = page_clock_enter, + .exit = page_clock_exit, + .on_created = NULL, + .on_update = NULL, + .on_roller = page_clock_on_roller, + .on_click = page_clock_on_click, + .on_right_button = NULL, +}; \ No newline at end of file diff --git a/src/ui/page_clock.h b/src/ui/page_clock.h index e9d83799..9578669b 100644 --- a/src/ui/page_clock.h +++ b/src/ui/page_clock.h @@ -5,9 +5,10 @@ extern "C" { #endif #include "ui/ui_main_menu.h" +#include "util/ntp_client.h" // Incluir la cabecera en lugar de declarar la función extern page_pack_t pp_clock; #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/src/util/ntp_client.c b/src/util/ntp_client.c new file mode 100644 index 00000000..f99f1a2c --- /dev/null +++ b/src/util/ntp_client.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "driver/rtc.h" +#include "util/system.h" +#include "core/settings.h" +#include "ui/page_clock.h" + +#define NTP_TIMESTAMP_DELTA 2208988800ull + +// Estructura del paquete NTP +typedef struct { + uint8_t li_vn_mode; /* leap indicator, version and mode */ + uint8_t stratum; /* stratum level */ + uint8_t poll; /* poll interval */ + uint8_t precision; /* precision */ + uint32_t rootdelay; /* root delay */ + uint32_t rootdispersion; /* root dispersion */ + uint32_t refid; /* reference ID */ + uint32_t refts_sec; /* reference timestamp seconds */ + uint32_t refts_frac; /* reference timestamp fraction */ + uint32_t origts_sec; /* originate timestamp seconds */ + uint32_t origts_frac; /* originate timestamp fraction */ + uint32_t recvts_sec; /* receive timestamp seconds */ + uint32_t recvts_frac; /* receive timestamp fraction */ + uint32_t xmitts_sec; /* transmit timestamp seconds */ + uint32_t xmitts_frac; /* transmit timestamp fraction */ +} ntp_packet; + +int clock_sync_from_ntp(void) { + int sockfd, n; + struct sockaddr_in serv_addr; + struct hostent *server; + ntp_packet packet = {0}; + + // Verificar si WiFi está habilitado + if (!g_setting.wifi.enable) { + LOGE("WiFi disabled, cannot sync time from NTP"); + return -1; + } + + // Habilitar WiFi si es necesario + system_exec("ifconfig wlan0 up"); + usleep(1000000); // Esperar 1 segundo para que se conecte + + // Inicializar paquete NTP + packet.li_vn_mode = 0x1b; // Leap = 0, Version = 3, Mode = 3 (cliente) + + // Crear socket + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) { + LOGE("Error opening socket for NTP"); + return -1; + } + + // Configurar timeout + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + LOGE("Error setting socket timeout"); + close(sockfd); + return -1; + } + + // Configurar servidor NTP - intentar conexión por nombre + server = gethostbyname("pool.ntp.org"); + if (server == NULL) { + LOGI("Could not resolve pool.ntp.org, trying with IP address"); + // Si falla, intentar con IPs directas + memset((char *)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(123); // Puerto NTP + + // Intentar con una IP estática de un servidor NTP conocido + if (inet_pton(AF_INET, "162.159.200.1", &serv_addr.sin_addr) <= 0) { + LOGE("Invalid NTP server address"); + close(sockfd); + return -1; + } + } else { + // Configuración normal si la resolución de nombres funciona + memset((char *)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + memcpy((char *)&serv_addr.sin_addr.s_addr, (char *)server->h_addr, server->h_length); + serv_addr.sin_port = htons(123); // Puerto NTP + } + + // Enviar paquete + n = sendto(sockfd, (char *)&packet, sizeof(ntp_packet), 0, + (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if (n < 0) { + LOGE("Error writing to socket for NTP"); + close(sockfd); + return -1; + } + + // Recibir respuesta + n = recvfrom(sockfd, (char *)&packet, sizeof(ntp_packet), 0, NULL, NULL); + if (n < 0) { + LOGE("Error reading from socket for NTP"); + close(sockfd); + return -1; + } + + close(sockfd); + + // Convertir tiempo recibido + uint32_t txTm = ntohl(packet.xmitts_sec); + time_t time_ntp = txTm - NTP_TIMESTAMP_DELTA; + + LOGI("NTP time received: %u", (unsigned int)time_ntp); + + // Actualizar RTC con el tiempo recibido + struct timeval tv_now; + tv_now.tv_sec = time_ntp; + tv_now.tv_usec = 0; + + struct rtc_date rd; + rtc_tv2rd(&tv_now, &rd); + + // Validar la fecha + if (rtc_has_valid_date(&rd) != 0) { + LOGE("NTP returned invalid date: %04d-%02d-%02d %02d:%02d:%02d", + rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + return -1; + } + + // Actualizar el RTC + rtc_set_clock(&rd); + LOGI("NTP time sync successful: %04d-%02d-%02d %02d:%02d:%02d", + rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + + return 0; +} \ No newline at end of file diff --git a/src/util/ntp_client.h b/src/util/ntp_client.h new file mode 100644 index 00000000..57be9ea8 --- /dev/null +++ b/src/util/ntp_client.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Sincroniza el reloj del sistema con un servidor NTP. + * Requiere que el WiFi esté habilitado y configurado. + * + * @return 0 si la sincronización fue exitosa, -1 en caso de error + */ +int clock_sync_from_ntp(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From e65055311f36d5e4779e9f613a1460e8c3d35d8f Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Thu, 8 May 2025 20:41:43 -0500 Subject: [PATCH 2/8] automatic RTC update when connected to wifi as client --- src/ui/page_wifi.c | 48 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/ui/page_wifi.c b/src/ui/page_wifi.c index c268e361..b5630b87 100644 --- a/src/ui/page_wifi.c +++ b/src/ui/page_wifi.c @@ -22,6 +22,8 @@ #include "ui/ui_style.h" #include "util/filesystem.h" #include "util/system.h" +#include "driver/rtc.h" +#include "util/ntp_client.h" /** * Types @@ -818,10 +820,52 @@ static void page_wifi_exit() { /** * Invoked periodically. */ -static void page_wifi_on_update(uint32_t delta_ms) { + static void page_wifi_on_update(uint32_t delta_ms) { static uint32_t elapsed = -1; + static bool was_connected = false; + bool is_connected = false; + + // Only proceed if WiFi is enabled and in client mode + if (g_setting.wifi.enable && g_setting.wifi.mode == WIFI_MODE_STA) { + // Check if we're connected by looking for a valid IP address + const char *current_ip = page_wifi_get_real_address(); + is_connected = (current_ip != NULL); + + // If we just connected (transition from disconnected to connected) + if (is_connected && !was_connected) { + LOGI("WiFi client connection established, syncing time via NTP"); + + // Sync clock from NTP + if (clock_sync_from_ntp() == 0) { + LOGI("NTP time sync successful"); + + // Update settings with new time values + struct rtc_date date; + rtc_get_clock(&date); + g_setting.clock.year = date.year; + g_setting.clock.month = date.month; + g_setting.clock.day = date.day; + g_setting.clock.hour = date.hour; + g_setting.clock.min = date.min; + g_setting.clock.sec = date.sec; + + // Save to settings file + ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); + ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); + ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); + ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); + ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); + ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + } else { + LOGE("NTP time sync failed"); + } + } + + // Update connection status for next check + was_connected = is_connected; + } - // Check immediately after running, then every 5 minutes. + // Check immediately after running, then every 5 minutes for updates. if (g_setting.wifi.enable && (elapsed == -1 || (elapsed += delta_ms) > 300000)) { switch (g_setting.wifi.mode) { case WIFI_MODE_STA: From 7f2e1930ca0e194429ec2ff9d33d2d65e63dae74 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Mon, 12 May 2025 19:56:39 -0500 Subject: [PATCH 3/8] Update RTC menu option and NTP client functionality - Refactor RTC menu option in settings and UI pages. - Update NTP client implementation for improved synchronization. - Modify related files: settings.c, settings.h, page_clock.c, page_wifi.c, ntp_client.c, ntp_client.h. --- src/core/settings.c | 5 + src/core/settings.h | 1 + src/ui/page_clock.c | 318 +++++++++++++++++++++-------- src/ui/page_wifi.c | 51 ++--- src/util/ntp_client.c | 464 +++++++++++++++++++++++++++++++++++------- src/util/ntp_client.h | 44 +++- 6 files changed, 687 insertions(+), 196 deletions(-) diff --git a/src/core/settings.c b/src/core/settings.c index 04985b01..f3586376 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -178,6 +178,7 @@ const setting_t g_setting_defaults = { }, }, }, + // En la inicialización de g_setting_defaults en settings.c .clock = { .year = 2023, .month = 3, @@ -186,6 +187,7 @@ const setting_t g_setting_defaults = { .min = 30, .sec = 30, .format = 0, + .utc_offset = 0, // UTC±00:00 por defecto }, // Refer to `page_input.c`'s arrays `rollerFunctionPointers` and `btnFunctionPointers` .inputs = { @@ -468,6 +470,9 @@ void settings_load(void) { g_setting.language.lang = ini_getl("language", "lang", g_setting_defaults.language.lang, SETTING_INI); } + // UTC + g_setting.clock.utc_offset = ini_getl("clock", "utc_offset", 0, SETTING_INI); // Por defecto UTC±00:00 + // Check if (fs_file_exists(SELF_TEST_FILE)) { unlink(SELF_TEST_FILE); diff --git a/src/core/settings.h b/src/core/settings.h index 27da5f5c..e19f5939 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -198,6 +198,7 @@ typedef struct { int min; int sec; int format; + int utc_offset; } setting_clock_t; #define WIFI_RF_CHANNELS 14 // World Channels diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 24fe4761..51fd636d 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -1,5 +1,4 @@ #include "page_clock.h" -#include "util/ntp_client.h" // Incluir la cabecera aquí #include #include @@ -15,6 +14,7 @@ #include "ui/page_common.h" #include "ui/ui_attribute.h" #include "ui/ui_style.h" +#include "util/ntp_client.h" /** * Various data types used to @@ -45,8 +45,9 @@ typedef enum { ITEM_MINUTE, ITEM_SECOND, ITEM_FORMAT, + ITEM_UTC, // Nuevo elemento para seleccionar UTC ITEM_SET_CLOCK, - ITEM_SYNC_NTP, // Nueva opción para sincronización NTP + ITEM_SYNC_NTP, ITEM_BACK, ITEM_LIST_TOTAL @@ -81,6 +82,90 @@ static int page_clock_set_clock_confirm = 0; static int page_clock_is_dirty = 0; static struct rtc_date page_clock_rtc_date = {0}; +// Opciones de zona horaria UTC +static char* utc_options[] = { + "UTC-12:00", "UTC-11:00", "UTC-10:00", "UTC-09:00", "UTC-08:00", + "UTC-07:00", "UTC-06:00", "UTC-05:00", "UTC-04:00", "UTC-03:00", + "UTC-02:00", "UTC-01:00", "UTC 00:00", "UTC+01:00", "UTC+02:00", + "UTC+03:00", "UTC+04:00", "UTC+05:00", "UTC+06:00", "UTC+07:00", + "UTC+08:00", "UTC+09:00", "UTC+10:00", "UTC+11:00", "UTC+12:00", + "UTC+13:00", "UTC+14:00" +}; + +// Valores de offset en segundos correspondientes a las opciones +static const int utc_seconds[] = { +-43200, // UTC−12:00 +-39600, // UTC−11:00 +-36000, // UTC−10:00 +-32400, // UTC−09:00 +-28800, // UTC−08:00 +-25200, // UTC−07:00 +-21600, // UTC−06:00 +-18000, // UTC−05:00 +-14400, // UTC−04:00 +-10800, // UTC−03:00 + -7200, // UTC−02:00 + -3600, // UTC−01:00 + 0, // UTC±00:00 + 3600, // UTC+01:00 + 7200, // UTC+02:00 + 10800, // UTC+03:00 + 14400, // UTC+04:00 + 18000, // UTC+05:00 + 21600, // UTC+06:00 + 25200, // UTC+07:00 + 28800, // UTC+08:00 + 32400, // UTC+09:00 + 36000, // UTC+10:00 + 39600, // UTC+11:00 + 43200, // UTC+12:00 + 46800, // UTC+13:00 + 50400 // UTC+14:00 +}; + +// Convertir offset en segundos a índice en el array +int utc_offset_to_index(int offset_seconds) { + // Buscar el offset más cercano + int index = 12; // Por defecto UTC±00:00 + int min_diff = abs(offset_seconds); + + for (int i = 0; i < sizeof(utc_seconds)/sizeof(utc_seconds[0]); i++) { + int diff = abs(offset_seconds - utc_seconds[i]); + if (diff < min_diff) { + min_diff = diff; + index = i; + } + } + + return index; +} + +// Convertir índice a offset en segundos +int index_to_utc_offset(int index) { + if (index >= 0 && index < sizeof(utc_seconds)/sizeof(utc_seconds[0])) { + return utc_seconds[index]; + } + + return 0; // Por defecto UTC±00:00 +} + +/** + * Callback para sincronización NTP asíncrona + */ +static void ntp_sync_callback(int result, void* user_data) { + if (result == 0) { + // Éxito, actualizar la interfaz en el hilo principal + // Usamos lv_async_call o enviamos un mensaje al hilo principal + // Esto dependerá de la implementación específica del sistema + + // En este caso, el UI se actualizará automáticamente en el próximo ciclo + // porque refrescamos la fecha/hora cada 250ms + } + + // El mensaje de éxito/error se mostrará directamente en la interfaz + // cuando el usuario inicia la sincronización +} + /** * Acquire index from selected dropdown option as a string. */ @@ -263,6 +348,7 @@ static int page_clock_selected_item_is_obj(int selected_item) { case ITEM_HOUR: case ITEM_MINUTE: case ITEM_SECOND: + case ITEM_UTC: return 1; default: return 0; @@ -321,8 +407,6 @@ static void page_clock_refresh_ui_timer_cb(struct _lv_timer_t *timer) { * Callback invoked once `Set Clock` is triggered and confirmed via the menu. */ static void page_clock_set_clock_timer_cb(struct _lv_timer_t *timer) { - char text[512]; - page_clock_build_date_from_selected(&page_clock_rtc_date); g_setting.clock.year = page_clock_rtc_date.year; @@ -339,7 +423,6 @@ static void page_clock_set_clock_timer_cb(struct _lv_timer_t *timer) { ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); - ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); rtc_set_clock(&page_clock_rtc_date); page_clock_refresh_datetime(); @@ -355,6 +438,10 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { rtc_get_clock(&page_clock_rtc_date); page_clock_build_options_from_date(&page_clock_rtc_date); + // Incrementar el tamaño de las filas para evitar solapamiento + static lv_coord_t col_dsc[] = {160, 200, 160, 200, 160, 160, LV_GRID_TEMPLATE_LAST}; + static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 60, 60, 15, 10, 60, 60, LV_GRID_TEMPLATE_LAST}; + lv_obj_t *page = lv_menu_page_create(parent, NULL); lv_obj_clear_flag(page, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(page, 1053, 900); @@ -369,7 +456,8 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { create_text(NULL, section, false, buf, LV_MENU_ITEM_BUILDER_VARIANT_2); lv_obj_t *cont = lv_obj_create(section); - lv_obj_set_size(cont, 1280, 800); + // Aumentar el tamaño del contenedor + lv_obj_set_size(cont, 1280, 900); lv_obj_set_pos(cont, 0, 0); lv_obj_set_layout(cont, LV_LAYOUT_GRID); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); @@ -388,27 +476,47 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { page_clock_create_dropdown(cont, ITEM_MINUTE, page_clock_rtc_date.min, 2, 1); page_clock_create_dropdown(cont, ITEM_SECOND, page_clock_rtc_date.sec, 3, 1); + // Format selection (AM/PM or 24H) - Ahora en una fila dedicada snprintf(buf, sizeof(buf), "%s/%s", _lang("AM"), _lang("PM")); create_btn_group_item(&page_clock_items[ITEM_FORMAT].data.btn, cont, 2, _lang("Format"), buf, _lang("24 Hour"), "", "", 2); page_clock_items[ITEM_FORMAT].type = ITEM_TYPE_BTN; page_clock_items[ITEM_FORMAT].panel = arr->panel[2]; btn_group_set_sel(&page_clock_items[ITEM_FORMAT].data.btn, g_setting.clock.format); - page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 3, 3); + // Time Zone label y dropdown - En una nueva fila + create_label_item(cont, _lang("Time Zone"), 1, 3, 1); + + lv_obj_t* utc_dropdown = lv_dropdown_create(cont); + lv_dropdown_clear_options(utc_dropdown); + for (int i = 0; i < sizeof(utc_options)/sizeof(utc_options[0]); i++) { + lv_dropdown_add_option(utc_dropdown, utc_options[i], LV_DROPDOWN_POS_LAST); + } + lv_obj_set_size(utc_dropdown, 200, 40); + lv_obj_set_grid_cell(utc_dropdown, LV_GRID_ALIGN_START, 2, 2, LV_GRID_ALIGN_START, 3, 1); + lv_dropdown_set_selected(utc_dropdown, utc_offset_to_index(g_setting.clock.utc_offset)); + + page_clock_items[ITEM_UTC].data.obj = utc_dropdown; + page_clock_items[ITEM_UTC].type = ITEM_TYPE_OBJ; + page_clock_items[ITEM_UTC].panel = arr->panel[3]; + + // Set Clock (ahora en fila 4) + page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 4, 3); page_clock_items[ITEM_SET_CLOCK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[3]; + page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[4]; - // Nuevo botón para sincronización NTP - page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 4, 3); + // Sync from Internet (ahora en fila 5) + page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 5, 3); page_clock_items[ITEM_SYNC_NTP].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[4]; + page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[5]; + // Back (ahora en fila 6) snprintf(buf, sizeof(buf), "< %s", _lang("Back")); - page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 5, 1); + page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 6, 1); page_clock_items[ITEM_BACK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_BACK].panel = arr->panel[5]; + page_clock_items[ITEM_BACK].panel = arr->panel[6]; - page_clock_create_datetime_item(cont, 6); + // Fecha/hora actual (ahora en fila 7) + page_clock_create_datetime_item(cont, 7); if (rtc_has_battery() != 0) { lv_obj_t *note = lv_label_create(cont); @@ -419,7 +527,7 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { lv_obj_set_style_text_color(note, lv_color_make(255, 255, 255), 0); lv_obj_set_style_pad_top(note, 12, 0); lv_label_set_long_mode(note, LV_LABEL_LONG_WRAP); - lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 7, 2); + lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 8, 2); } page_clock_clear_datetime(); @@ -519,6 +627,47 @@ static void page_clock_on_roller(uint8_t key) { /** * Main input selection routine for this page. */ +static void ntp_sync_check_timer_cb(lv_timer_t* timer) { + if (!clock_is_syncing_from_ntp()) { + struct rtc_date date; + rtc_get_clock(&date); + + if (date.year > 2020) { // Probable success + // Update screen date + page_clock_rtc_date = date; + page_clock_build_options_from_date(&page_clock_rtc_date); + page_clock_refresh_datetime(); + + // Save to settings + g_setting.clock.year = date.year; + g_setting.clock.month = date.month; + g_setting.clock.day = date.day; + g_setting.clock.hour = date.hour; + g_setting.clock.min = date.min; + g_setting.clock.sec = date.sec; + + // Update configuration file + ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); + ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); + ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); + ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); + ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); + ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#00FF00 Sync Complete#"); + } else { + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#FF0000 Sync Failed#"); + } + + // Create timer to restore text + lv_timer_t *reset_timer = lv_timer_create(page_clock_sync_reset_cb, 2000, NULL); + lv_timer_set_repeat_count(reset_timer, 1); + + // Delete this timer + lv_timer_del(timer); + } +} + static void page_clock_on_click(uint8_t key, int sel) { char buf[128]; // Ignore commands until timer has expired before allowing user to proceed. @@ -532,6 +681,34 @@ static void page_clock_on_click(uint8_t key, int sel) { g_setting.clock.format = btn_group_get_sel(&page_clock_items[ITEM_FORMAT].data.btn); ini_putl("clock", "format", g_setting.clock.format, SETTING_INI); break; + case ITEM_UTC: + if (!page_clock_item_focused) { + page_clock_items[ITEM_UTC].last_option = + lv_dropdown_get_selected(page_clock_items[ITEM_UTC].data.obj); + + lv_obj_t *list = lv_dropdown_get_list(page_clock_items[ITEM_UTC].data.obj); + lv_dropdown_open(page_clock_items[ITEM_UTC].data.obj); + lv_obj_add_style(list, &style_dropdown, LV_PART_MAIN); + lv_obj_set_style_text_color(list, lv_color_make(0, 0, 0), LV_PART_SELECTED | LV_STATE_CHECKED); + page_clock_item_focused = 1; + } else { + lv_event_send(page_clock_items[ITEM_UTC].data.obj, LV_EVENT_RELEASED, NULL); + page_clock_item_focused = 0; + int option = lv_dropdown_get_selected(page_clock_items[ITEM_UTC].data.obj); + + if (page_clock_items[ITEM_UTC].last_option != option) { + page_clock_items[ITEM_UTC].last_option = option; + g_setting.clock.utc_offset = index_to_utc_offset(option); + ini_putl("clock", "utc_offset", g_setting.clock.utc_offset, SETTING_INI); + page_clock_is_dirty = 1; + + if (!page_clock_set_clock_pending_timer) { + page_clock_set_clock_pending_timer = lv_timer_create(page_clock_set_clock_pending_cb, 50, NULL); + lv_timer_set_repeat_count(page_clock_set_clock_pending_timer, -1); + } + } + } + break; case ITEM_SET_CLOCK: if (page_clock_set_clock_confirm) { snprintf(buf, sizeof(buf), "#FF0000 %s %s...#", _lang("Updating"), _lang("Clock")); @@ -550,81 +727,58 @@ static void page_clock_on_click(uint8_t key, int sel) { snprintf(buf, sizeof(buf), "#FF0000 %s %s...#", _lang("Syncing"), _lang("Clock")); lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); - // Sincronizar desde NTP - if (clock_sync_from_ntp() == 0) { - // Éxito, actualizar la interfaz - rtc_get_clock(&page_clock_rtc_date); - page_clock_build_options_from_date(&page_clock_rtc_date); - page_clock_refresh_datetime(); - - // Guardar en settings - g_setting.clock.year = page_clock_rtc_date.year; - g_setting.clock.month = page_clock_rtc_date.month; - g_setting.clock.day = page_clock_rtc_date.day; - g_setting.clock.hour = page_clock_rtc_date.hour; - g_setting.clock.min = page_clock_rtc_date.min; - g_setting.clock.sec = page_clock_rtc_date.sec; - - // Actualizar archivo de configuración - ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); - ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); - ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); - ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); - ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); - ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); - - snprintf(buf, sizeof(buf), "#00FF00 %s#", _lang("Sync Complete")); + if (clock_sync_from_ntp_async(ntp_sync_callback, NULL) == 0) { + lv_timer_t *check_timer = lv_timer_create(ntp_sync_check_timer_cb, 500, NULL); + lv_timer_set_repeat_count(check_timer, 20); // Timeout after 10 seconds } else { - // Error snprintf(buf, sizeof(buf), "#FF0000 %s#", _lang("Sync Failed")); + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); + lv_timer_t *reset_timer = lv_timer_create(page_clock_sync_reset_cb, 2000, NULL); + lv_timer_set_repeat_count(reset_timer, 1); } - - lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); - lv_timer_t *reset_timer = lv_timer_create(page_clock_sync_reset_cb, 2000, NULL); - lv_timer_set_repeat_count(reset_timer, 1); page_clock_set_clock_confirm = 0; } else { snprintf(buf, sizeof(buf), "#FFFF00 %s...#", _lang("Click to confirm or Scroll to cancel")); lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); page_clock_set_clock_confirm = 1; - } - break; - case ITEM_BACK: - submenu_exit(); - break; - default: - if (!page_clock_item_focused) { - page_clock_items[page_clock_item_selected].last_option = - lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); - - lv_obj_t *list = lv_dropdown_get_list(page_clock_items[page_clock_item_selected].data.obj); - lv_dropdown_open(page_clock_items[page_clock_item_selected].data.obj); - lv_obj_add_style(list, &style_dropdown, LV_PART_MAIN); - lv_obj_set_style_text_color(list, lv_color_make(0, 0, 0), LV_PART_SELECTED | LV_STATE_CHECKED); - page_clock_item_focused = 1; - } else { - lv_event_send(page_clock_items[page_clock_item_selected].data.obj, LV_EVENT_RELEASED, NULL); - page_clock_item_focused = 0; - int option = lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); - - if (page_clock_items[page_clock_item_selected].last_option != option) { - page_clock_items[page_clock_item_selected].last_option = option; - page_clock_is_dirty = 1; - if (!page_clock_set_clock_pending_timer) { - page_clock_set_clock_pending_timer = lv_timer_create(page_clock_set_clock_pending_cb, 50, NULL); - lv_timer_set_repeat_count(page_clock_set_clock_pending_timer, -1); - } - } - - if (page_clock_item_selected == ITEM_YEAR || - page_clock_item_selected == ITEM_MONTH) { - struct rtc_date date; - page_clock_build_date_from_selected(&date); - page_clock_build_options_from_date(&date); - } - } - break; - } + } + break; + case ITEM_BACK: + submenu_exit(); + break; + default: + if (!page_clock_item_focused) { + page_clock_items[page_clock_item_selected].last_option = + lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); + + lv_obj_t *list = lv_dropdown_get_list(page_clock_items[page_clock_item_selected].data.obj); + lv_dropdown_open(page_clock_items[page_clock_item_selected].data.obj); + lv_obj_add_style(list, &style_dropdown, LV_PART_MAIN); + lv_obj_set_style_text_color(list, lv_color_make(0, 0, 0), LV_PART_SELECTED | LV_STATE_CHECKED); + page_clock_item_focused = 1; + } else { + lv_event_send(page_clock_items[page_clock_item_selected].data.obj, LV_EVENT_RELEASED, NULL); + page_clock_item_focused = 0; + int option = lv_dropdown_get_selected(page_clock_items[page_clock_item_selected].data.obj); + + if (page_clock_items[page_clock_item_selected].last_option != option) { + page_clock_items[page_clock_item_selected].last_option = option; + page_clock_is_dirty = 1; + if (!page_clock_set_clock_pending_timer) { + page_clock_set_clock_pending_timer = lv_timer_create(page_clock_set_clock_pending_cb, 50, NULL); + lv_timer_set_repeat_count(page_clock_set_clock_pending_timer, -1); + } + } + + if (page_clock_item_selected == ITEM_YEAR || + page_clock_item_selected == ITEM_MONTH) { + struct rtc_date date; + page_clock_build_date_from_selected(&date); + page_clock_build_options_from_date(&date); + } + } + break; + } } /** diff --git a/src/ui/page_wifi.c b/src/ui/page_wifi.c index b5630b87..d4b32e65 100644 --- a/src/ui/page_wifi.c +++ b/src/ui/page_wifi.c @@ -817,55 +817,43 @@ static void page_wifi_exit() { page_wifi.item_select = 0; } +/** + * Callback function for background NTP synchronization + */ +static void wifi_ntp_sync_callback(int result, void* user_data) { + if (result == 0) { + LOGI("Background NTP time sync successful"); + // Settings are already updated inside ntp_sync_thread + } else { + LOGE("Background NTP time sync failed"); + } +} + /** * Invoked periodically. */ - static void page_wifi_on_update(uint32_t delta_ms) { +static void page_wifi_on_update(uint32_t delta_ms) { static uint32_t elapsed = -1; static bool was_connected = false; bool is_connected = false; - // Only proceed if WiFi is enabled and in client mode if (g_setting.wifi.enable && g_setting.wifi.mode == WIFI_MODE_STA) { - // Check if we're connected by looking for a valid IP address const char *current_ip = page_wifi_get_real_address(); is_connected = (current_ip != NULL); - // If we just connected (transition from disconnected to connected) if (is_connected && !was_connected) { - LOGI("WiFi client connection established, syncing time via NTP"); + LOGI("WiFi client connection established"); - // Sync clock from NTP - if (clock_sync_from_ntp() == 0) { - LOGI("NTP time sync successful"); - - // Update settings with new time values - struct rtc_date date; - rtc_get_clock(&date); - g_setting.clock.year = date.year; - g_setting.clock.month = date.month; - g_setting.clock.day = date.day; - g_setting.clock.hour = date.hour; - g_setting.clock.min = date.min; - g_setting.clock.sec = date.sec; - - // Save to settings file - ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); - ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); - ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); - ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); - ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); - ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); - } else { - LOGE("NTP time sync failed"); + if (!clock_is_syncing_from_ntp()) { + LOGI("Starting background NTP sync after connection established"); + clock_sync_from_ntp_async(wifi_ntp_sync_callback, NULL); } } - // Update connection status for next check was_connected = is_connected; } - // Check immediately after running, then every 5 minutes for updates. + // Verificar inmediatamente después de ejecutar, luego cada 5 minutos para actualizaciones. if (g_setting.wifi.enable && (elapsed == -1 || (elapsed += delta_ms) > 300000)) { switch (g_setting.wifi.mode) { case WIFI_MODE_STA: @@ -880,7 +868,8 @@ static void page_wifi_exit() { elapsed = 0; } -} + + } /** * Main navigation routine for this page. diff --git a/src/util/ntp_client.c b/src/util/ntp_client.c index f99f1a2c..10dfe1c9 100644 --- a/src/util/ntp_client.c +++ b/src/util/ntp_client.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,6 +9,9 @@ #include #include #include +#include +#include +#include #include @@ -15,10 +19,21 @@ #include "util/system.h" #include "core/settings.h" #include "ui/page_clock.h" +#include "util/ntp_client.h" -#define NTP_TIMESTAMP_DELTA 2208988800ull +#define NTP_TIMESTAMP_DELTA 2208988800ull // Segundos entre 1900 (NTP) y 1970 (epoch) +#define NTP_PORT 123 +#define NTP_TIMEOUT_SEC 3 // Timeout reducido a 3 segundos -// Estructura del paquete NTP +// Estados para la sincronización NTP +typedef enum { + NTP_SYNC_IDLE = 0, + NTP_SYNC_IN_PROGRESS, + NTP_SYNC_SUCCESS, + NTP_SYNC_FAILED +} ntp_sync_state_t; + +// Estructura del paquete NTP según RFC 5905 typedef struct { uint8_t li_vn_mode; /* leap indicator, version and mode */ uint8_t stratum; /* stratum level */ @@ -35,111 +50,406 @@ typedef struct { uint32_t recvts_frac; /* receive timestamp fraction */ uint32_t xmitts_sec; /* transmit timestamp seconds */ uint32_t xmitts_frac; /* transmit timestamp fraction */ -} ntp_packet; +} ntp_packet_t; -int clock_sync_from_ntp(void) { - int sockfd, n; - struct sockaddr_in serv_addr; - struct hostent *server; - ntp_packet packet = {0}; - - // Verificar si WiFi está habilitado - if (!g_setting.wifi.enable) { - LOGE("WiFi disabled, cannot sync time from NTP"); +// Estructura para el callback +typedef struct { + ntp_callback_t callback_fn; + void* user_data; +} ntp_callback_data_t; + +// Variables globales +static volatile ntp_sync_state_t g_ntp_sync_state = NTP_SYNC_IDLE; +static pthread_mutex_t g_ntp_mutex = PTHREAD_MUTEX_INITIALIZER; +static char* g_ntp_servers[] = { + "pool.ntp.org", + "time.google.com", + "time.cloudflare.com", + "time.apple.com", + "time.windows.com" +}; +static const int g_ntp_server_count = sizeof(g_ntp_servers) / sizeof(g_ntp_servers[0]); + +// Lista de direcciones IP estáticas como respaldo +static char* g_ntp_fallback_ips[] = { + "162.159.200.1", // time.cloudflare.com + "216.239.35.4", // time.google.com + "17.253.114.125", // time.apple.com + "13.65.245.138" // time.windows.com +}; +static const int g_ntp_fallback_count = sizeof(g_ntp_fallback_ips) / sizeof(g_ntp_fallback_ips[0]); + +// Función para convertir el tiempo NTP a unix time +static time_t ntp_time_to_unix_time(uint32_t ntp_time) { + return (time_t)(ntp_time - NTP_TIMESTAMP_DELTA); +} + +// Función para establecer socket no bloqueante +static int set_socket_nonblocking(int sockfd) { + int flags = fcntl(sockfd, F_GETFL, 0); + if (flags == -1) { return -1; } - - // Habilitar WiFi si es necesario - system_exec("ifconfig wlan0 up"); - usleep(1000000); // Esperar 1 segundo para que se conecte - - // Inicializar paquete NTP - packet.li_vn_mode = 0x1b; // Leap = 0, Version = 3, Mode = 3 (cliente) - - // Crear socket - sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); +} + +// Función para intentar resolver y conectar a un servidor NTP +static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_addr) { + int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { - LOGE("Error opening socket for NTP"); + LOGE("Error creating socket for NTP"); return -1; } - + + // Configurar timeout + struct timeval tv; + tv.tv_sec = NTP_TIMEOUT_SEC; + tv.tv_usec = 0; + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 || + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { + LOGE("Error setting socket timeout"); + close(sockfd); + return -1; + } + + // Intentar resolver el nombre del servidor + struct hostent *host_entry = gethostbyname(server); + if (host_entry == NULL) { + LOGI("Could not resolve %s", server); + close(sockfd); + return -1; + } + + // Configurar dirección del servidor + memset(serv_addr, 0, sizeof(struct sockaddr_in)); + serv_addr->sin_family = AF_INET; + memcpy(&serv_addr->sin_addr.s_addr, host_entry->h_addr, host_entry->h_length); + serv_addr->sin_port = htons(NTP_PORT); + + return sockfd; +} + +// Función para intentar conectar a una IP de servidor NTP específica +static int connect_to_ntp_ip(const char* ip_address, struct sockaddr_in* serv_addr) { + int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) { + LOGE("Error creating socket for NTP"); + return -1; + } + // Configurar timeout struct timeval tv; - tv.tv_sec = 5; + tv.tv_sec = NTP_TIMEOUT_SEC; tv.tv_usec = 0; - if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 || + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { LOGE("Error setting socket timeout"); close(sockfd); return -1; } - // Configurar servidor NTP - intentar conexión por nombre - server = gethostbyname("pool.ntp.org"); - if (server == NULL) { - LOGI("Could not resolve pool.ntp.org, trying with IP address"); - // Si falla, intentar con IPs directas - memset((char *)&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(123); // Puerto NTP + // Configurar dirección del servidor + memset(serv_addr, 0, sizeof(struct sockaddr_in)); + serv_addr->sin_family = AF_INET; + serv_addr->sin_port = htons(NTP_PORT); + + if (inet_pton(AF_INET, ip_address, &serv_addr->sin_addr) <= 0) { + LOGE("Invalid IP address: %s", ip_address); + close(sockfd); + return -1; + } + + return sockfd; +} + +// Función para enviar y recibir paquete NTP (con reintentos) +static int send_receive_ntp_packet(int sockfd, struct sockaddr_in* serv_addr, ntp_packet_t* packet) { + int retries = 3; + socklen_t addr_len = sizeof(struct sockaddr_in); + + // Inicializar paquete NTP + memset(packet, 0, sizeof(ntp_packet_t)); + packet->li_vn_mode = 0x1b; // LI = 0, Version = 3, Mode = 3 (cliente) + + while (retries--) { + // Enviar paquete + if (sendto(sockfd, packet, sizeof(ntp_packet_t), 0, + (struct sockaddr *)serv_addr, addr_len) < 0) { + LOGE("Error sending NTP packet (retry %d): %s", 2-retries, strerror(errno)); + continue; + } - // Intentar con una IP estática de un servidor NTP conocido - if (inet_pton(AF_INET, "162.159.200.1", &serv_addr.sin_addr) <= 0) { - LOGE("Invalid NTP server address"); - close(sockfd); - return -1; + // Configurar select para timeout + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + + struct timeval timeout; + timeout.tv_sec = 1; // 1 segundo por intento + timeout.tv_usec = 0; + + int select_result = select(sockfd + 1, &readfds, NULL, NULL, &timeout); + + if (select_result <= 0) { + if (select_result == 0) { + LOGI("NTP response timeout (retry %d)", 2-retries); + } else { + LOGE("Select error (retry %d): %s", 2-retries, strerror(errno)); + } + continue; } - } else { - // Configuración normal si la resolución de nombres funciona - memset((char *)&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - memcpy((char *)&serv_addr.sin_addr.s_addr, (char *)server->h_addr, server->h_length); - serv_addr.sin_port = htons(123); // Puerto NTP - } - - // Enviar paquete - n = sendto(sockfd, (char *)&packet, sizeof(ntp_packet), 0, - (struct sockaddr *)&serv_addr, sizeof(serv_addr)); - if (n < 0) { - LOGE("Error writing to socket for NTP"); + + // Recibir respuesta + if (recvfrom(sockfd, packet, sizeof(ntp_packet_t), 0, + (struct sockaddr *)serv_addr, &addr_len) < 0) { + LOGE("Error receiving NTP packet (retry %d): %s", 2-retries, strerror(errno)); + continue; + } + + // Éxito + return 0; + } + + // Agotados los intentos + return -1; +} + +// Función para calcular correctamente la diferencia de zona horaria +static time_t apply_timezone_offset(time_t utc_time, int offset_seconds) { + // Simplemente sumar el offset en segundos + return utc_time + offset_seconds; +} + +// Función que realiza la sincronización NTP (ejecutada en un hilo) +static void* ntp_sync_thread(void* arg) { + ntp_callback_data_t* callback_data = (ntp_callback_data_t*)arg; + int result = -1; + int sockfd = -1; + ntp_packet_t packet; + struct sockaddr_in serv_addr; + + LOGI("NTP sync thread started"); + + // Verificar si WiFi está habilitado + if (!g_setting.wifi.enable) { + LOGE("WiFi disabled, cannot sync time from NTP"); + goto cleanup; + } + + // Asegurarse de que WiFi esté activo + system_exec("ifconfig wlan0 up"); + usleep(500000); // Espera reducida a 0.5 segundos + + // Intentar varios servidores NTP + for (int i = 0; i < g_ntp_server_count; i++) { + LOGI("Trying NTP server: %s", g_ntp_servers[i]); + + sockfd = connect_to_ntp_server(g_ntp_servers[i], &serv_addr); + if (sockfd < 0) { + continue; + } + + if (send_receive_ntp_packet(sockfd, &serv_addr, &packet) == 0) { + result = 0; + break; + } + close(sockfd); - return -1; + sockfd = -1; + } + + // Si ningún servidor funciona, intentar con las IPs de respaldo + if (result != 0) { + LOGI("Trying fallback NTP server IPs"); + for (int i = 0; i < g_ntp_fallback_count; i++) { + LOGI("Trying NTP fallback IP: %s", g_ntp_fallback_ips[i]); + + sockfd = connect_to_ntp_ip(g_ntp_fallback_ips[i], &serv_addr); + if (sockfd < 0) { + continue; + } + + if (send_receive_ntp_packet(sockfd, &serv_addr, &packet) == 0) { + result = 0; + break; + } + + close(sockfd); + sockfd = -1; + } + } + + if (result == 0) { + // Convertir tiempo recibido + uint32_t txTm = ntohl(packet.xmitts_sec); + time_t utc_time = ntp_time_to_unix_time(txTm); + + LOGI("NTP time received (UTC): %u", (unsigned int)utc_time); + + // Usar el offset configurado por el usuario + int timezone_offset = g_setting.clock.utc_offset; + LOGI("Using configured timezone offset: %d seconds", timezone_offset); + + // Aplicar la zona horaria para obtener la hora local + time_t local_time = apply_timezone_offset(utc_time, timezone_offset); + LOGI("Local time after timezone adjustment: %u", (unsigned int)local_time); + + // Actualizar RTC con el tiempo recibido ajustado a zona horaria local + struct timeval tv_now; + tv_now.tv_sec = local_time; + tv_now.tv_usec = 0; + + struct rtc_date rd; + rtc_tv2rd(&tv_now, &rd); + + // Validar la fecha + if (rtc_has_valid_date(&rd) != 0) { + LOGE("NTP returned invalid date: %04d-%02d-%02d %02d:%02d:%02d", + rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + result = -1; + } else { + // Actualizar el RTC + rtc_set_clock(&rd); + LOGI("NTP time sync successful: %04d-%02d-%02d %02d:%02d:%02d", + rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + + // Guardar en settings + g_setting.clock.year = rd.year; + g_setting.clock.month = rd.month; + g_setting.clock.day = rd.day; + g_setting.clock.hour = rd.hour; + g_setting.clock.min = rd.min; + g_setting.clock.sec = rd.sec; + + // Actualizar archivo de configuración + ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); + ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); + ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); + ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); + ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); + ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + } } - // Recibir respuesta - n = recvfrom(sockfd, (char *)&packet, sizeof(ntp_packet), 0, NULL, NULL); - if (n < 0) { - LOGE("Error reading from socket for NTP"); +cleanup: + if (sockfd >= 0) { close(sockfd); + } + + // Actualizar estado + pthread_mutex_lock(&g_ntp_mutex); + g_ntp_sync_state = (result == 0) ? NTP_SYNC_SUCCESS : NTP_SYNC_FAILED; + pthread_mutex_unlock(&g_ntp_mutex); + + // Llamar al callback si existe + if (callback_data != NULL) { + if (callback_data->callback_fn != NULL) { + callback_data->callback_fn(result, callback_data->user_data); + } + free(callback_data); + } + + LOGI("NTP sync thread finished with result: %d", result); + return NULL; +} + +// Función pública para verificar si hay una sincronización en progreso +int clock_is_syncing_from_ntp(void) { + int is_syncing = 0; + + pthread_mutex_lock(&g_ntp_mutex); + is_syncing = (g_ntp_sync_state == NTP_SYNC_IN_PROGRESS); + pthread_mutex_unlock(&g_ntp_mutex); + + return is_syncing; +} + +// Función pública para iniciar la sincronización NTP (asíncrona con callback) +int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data) { + int ret = -1; + + pthread_mutex_lock(&g_ntp_mutex); + + // Verificar si ya hay una sincronización en progreso + if (g_ntp_sync_state == NTP_SYNC_IN_PROGRESS) { + LOGI("NTP sync already in progress"); + pthread_mutex_unlock(&g_ntp_mutex); return -1; } - close(sockfd); + // Actualizar estado + g_ntp_sync_state = NTP_SYNC_IN_PROGRESS; - // Convertir tiempo recibido - uint32_t txTm = ntohl(packet.xmitts_sec); - time_t time_ntp = txTm - NTP_TIMESTAMP_DELTA; + // Preparar datos para el callback + ntp_callback_data_t* callback_data = malloc(sizeof(ntp_callback_data_t)); + if (callback_data == NULL) { + LOGE("Failed to allocate memory for callback data"); + g_ntp_sync_state = NTP_SYNC_IDLE; + pthread_mutex_unlock(&g_ntp_mutex); + return -1; + } - LOGI("NTP time received: %u", (unsigned int)time_ntp); + callback_data->callback_fn = callback_fn; + callback_data->user_data = user_data; - // Actualizar RTC con el tiempo recibido - struct timeval tv_now; - tv_now.tv_sec = time_ntp; - tv_now.tv_usec = 0; + // Crear hilo para sincronización + pthread_t thread_id; + if (pthread_create(&thread_id, NULL, ntp_sync_thread, callback_data) != 0) { + LOGE("Error creating NTP thread"); + g_ntp_sync_state = NTP_SYNC_IDLE; + free(callback_data); + ret = -1; + } else { + pthread_detach(thread_id); + ret = 0; + } - struct rtc_date rd; - rtc_tv2rd(&tv_now, &rd); + pthread_mutex_unlock(&g_ntp_mutex); + return ret; +} + +// Función pública para compatibilidad con el código existente (bloqueante) +int clock_sync_from_ntp(void) { + // Verificar si ya hay una sincronización en progreso + if (clock_is_syncing_from_ntp()) { + LOGI("NTP sync already in progress"); + return -1; + } - // Validar la fecha - if (rtc_has_valid_date(&rd) != 0) { - LOGE("NTP returned invalid date: %04d-%02d-%02d %02d:%02d:%02d", - rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + // Intentar iniciar sincronización asíncrona sin callback + if (clock_sync_from_ntp_async(NULL, NULL) != 0) { return -1; } - // Actualizar el RTC - rtc_set_clock(&rd); - LOGI("NTP time sync successful: %04d-%02d-%02d %02d:%02d:%02d", - rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); + // Esperar resultado (pero con timeout) + int result = -1; + int timeout_count = 0; + const int max_timeout = 10; // 10 * 100ms = 1 segundo máximo + + while (timeout_count < max_timeout) { + usleep(100000); // 100ms + + pthread_mutex_lock(&g_ntp_mutex); + if (g_ntp_sync_state == NTP_SYNC_SUCCESS) { + result = 0; + g_ntp_sync_state = NTP_SYNC_IDLE; + pthread_mutex_unlock(&g_ntp_mutex); + break; + } else if (g_ntp_sync_state == NTP_SYNC_FAILED) { + result = -1; + g_ntp_sync_state = NTP_SYNC_IDLE; + pthread_mutex_unlock(&g_ntp_mutex); + break; + } + pthread_mutex_unlock(&g_ntp_mutex); + + timeout_count++; + } + + // Si aún está en progreso después del timeout, asumimos que continuará en segundo plano + if (timeout_count >= max_timeout) { + LOGI("NTP sync still in progress, continuing in background"); + } - return 0; + return result; } \ No newline at end of file diff --git a/src/util/ntp_client.h b/src/util/ntp_client.h index 57be9ea8..9d861fe5 100644 --- a/src/util/ntp_client.h +++ b/src/util/ntp_client.h @@ -1,17 +1,49 @@ -#pragma once +#ifndef NTP_CLIENT_H +#define NTP_CLIENT_H #ifdef __cplusplus extern "C" { #endif /** - * Sincroniza el reloj del sistema con un servidor NTP. - * Requiere que el WiFi esté habilitado y configurado. - * - * @return 0 si la sincronización fue exitosa, -1 en caso de error + * @brief Tipo de función de callback para la sincronización NTP asíncrona + * + * @param result Resultado de la sincronización (0 = éxito, -1 = error) + * @param user_data Datos proporcionados por el usuario en la llamada inicial + */ +typedef void (*ntp_callback_t)(int result, void* user_data); + +/** + * @brief Inicia la sincronización del reloj con un servidor NTP (bloqueante) + * + * Esta función intenta sincronizar el reloj con un servidor NTP. + * Es bloqueante pero tiene un timeout interno de 1 segundo. + * + * @return 0 en caso de éxito, -1 en caso de error */ int clock_sync_from_ntp(void); +/** + * @brief Inicia la sincronización del reloj con un servidor NTP (asíncrona) + * + * Esta función inicia la sincronización en un hilo separado y retorna inmediatamente. + * El resultado se comunicará a través del callback proporcionado. + * + * @param callback_fn Función que será llamada al completar la sincronización (puede ser NULL) + * @param user_data Datos que serán pasados al callback (puede ser NULL) + * @return 0 si se inició correctamente, -1 si hubo un error al iniciar + */ +int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data); + +/** + * @brief Verifica si hay una sincronización NTP en progreso + * + * @return 1 si hay una sincronización en progreso, 0 en caso contrario + */ +int clock_is_syncing_from_ntp(void); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif + +#endif /* NTP_CLIENT_H */ \ No newline at end of file From d403cc84b511bc36816579deef492221a36ac74f Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Mon, 12 May 2025 20:52:15 -0500 Subject: [PATCH 4/8] fix timezone offset, fix sync status, more... --- CMakeLists.txt | 4 +- src/ui/page_clock.c | 83 +++++++++++++++++++++++++++++------------- src/ui/page_wifi.c | 38 ++++++++++++++++--- src/ui/page_wifi.h | 10 +++++ src/util/ntp_client.c | 32 ++++++++++++++++ src/util/ntp_client.h | 23 ++++++++++++ src/util/wifi_status.c | 51 ++++++++++++++++++++++++++ src/util/wifi_status.h | 19 ++++++++++ 8 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 src/util/wifi_status.c create mode 100644 src/util/wifi_status.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f59ce18..9d81e9b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,9 @@ if(EMULATOR_BUILD) else() list(APPEND SRC_FILES ${SRC_FILES_PLAYER}) endif() -add_executable(${PROJECT_NAME} ${SRC_FILES}) +add_executable(${PROJECT_NAME} ${SRC_FILES} + src/util/wifi_status.c +) target_include_directories(${PROJECT_NAME} PRIVATE src/ diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 51fd636d..0bdf2694 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -15,6 +15,7 @@ #include "ui/ui_attribute.h" #include "ui/ui_style.h" #include "util/ntp_client.h" +#include "ui/page_wifi.h" /** * Various data types used to @@ -401,6 +402,16 @@ static void page_clock_set_clock_pending_cb(struct _lv_timer_t *timer) { */ static void page_clock_refresh_ui_timer_cb(struct _lv_timer_t *timer) { page_clock_refresh_datetime(); + + // Actualizar estado del botón Sync from Internet + bool wifi_connected = page_wifi_is_sta_connected(); + if (wifi_connected) { + lv_obj_clear_state(page_clock_items[ITEM_SYNC_NTP].data.obj, LV_STATE_DISABLED); + lv_obj_set_style_text_color(page_clock_items[ITEM_SYNC_NTP].data.obj, lv_color_make(255, 255, 255), 0); + } else { + lv_obj_add_state(page_clock_items[ITEM_SYNC_NTP].data.obj, LV_STATE_DISABLED); + lv_obj_set_style_text_color(page_clock_items[ITEM_SYNC_NTP].data.obj, lv_color_make(128, 128, 128), 0); + } } /** @@ -629,34 +640,46 @@ static void page_clock_on_roller(uint8_t key) { */ static void ntp_sync_check_timer_cb(lv_timer_t* timer) { if (!clock_is_syncing_from_ntp()) { - struct rtc_date date; - rtc_get_clock(&date); + clock_sync_status_t sync_status = clock_get_last_sync_status(); - if (date.year > 2020) { // Probable success - // Update screen date - page_clock_rtc_date = date; - page_clock_build_options_from_date(&page_clock_rtc_date); - page_clock_refresh_datetime(); - - // Save to settings - g_setting.clock.year = date.year; - g_setting.clock.month = date.month; - g_setting.clock.day = date.day; - g_setting.clock.hour = date.hour; - g_setting.clock.min = date.min; - g_setting.clock.sec = date.sec; - - // Update configuration file - ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); - ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); - ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); - ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); - ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); - ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + switch(sync_status) { + case CLOCK_SYNC_SUCCESS: { + // Get updated time after successful sync + struct rtc_date date; + rtc_get_clock(&date); + + // Update screen date + page_clock_rtc_date = date; + page_clock_build_options_from_date(&page_clock_rtc_date); + page_clock_refresh_datetime(); + + // Save to settings + g_setting.clock.year = date.year; + g_setting.clock.month = date.month; + g_setting.clock.day = date.day; + g_setting.clock.hour = date.hour; + g_setting.clock.min = date.min; + g_setting.clock.sec = date.sec; + + // Update configuration file + ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); + ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); + ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); + ini_putl("clock", "hour", g_setting.clock.hour, SETTING_INI); + ini_putl("clock", "min", g_setting.clock.min, SETTING_INI); + ini_putl("clock", "sec", g_setting.clock.sec, SETTING_INI); + + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#00FF00 Sync Complete#"); + break; + } - lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#00FF00 Sync Complete#"); - } else { - lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#FF0000 Sync Failed#"); + case CLOCK_SYNC_FAILED: + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#FF0000 Sync Failed#"); + break; + + default: + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#FF0000 Sync Error#"); + break; } // Create timer to restore text @@ -723,6 +746,14 @@ static void page_clock_on_click(uint8_t key, int sel) { } break; case ITEM_SYNC_NTP: + // Verificar si el WiFi está habilitado y conectado + if (!page_wifi_is_sta_connected()) { + lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, "#FF0000 WiFi Client Mode Required#"); + lv_timer_t *reset_timer = lv_timer_create(page_clock_sync_reset_cb, 2000, NULL); + lv_timer_set_repeat_count(reset_timer, 1); + return; + } + if (page_clock_set_clock_confirm) { snprintf(buf, sizeof(buf), "#FF0000 %s %s...#", _lang("Syncing"), _lang("Clock")); lv_label_set_text(page_clock_items[ITEM_SYNC_NTP].data.obj, buf); diff --git a/src/ui/page_wifi.c b/src/ui/page_wifi.c index d4b32e65..de5d665e 100644 --- a/src/ui/page_wifi.c +++ b/src/ui/page_wifi.c @@ -24,6 +24,7 @@ #include "util/system.h" #include "driver/rtc.h" #include "util/ntp_client.h" +#include "util/wifi_status.h" /** * Types @@ -821,11 +822,27 @@ static void page_wifi_exit() { * Callback function for background NTP synchronization */ static void wifi_ntp_sync_callback(int result, void* user_data) { - if (result == 0) { - LOGI("Background NTP time sync successful"); - // Settings are already updated inside ntp_sync_thread - } else { - LOGE("Background NTP time sync failed"); + switch (result) { + case NTP_SUCCESS: + LOGI("Background NTP time sync successful"); + // Settings are already updated inside ntp_sync_thread + break; + + case NTP_ERR_CONNECT: + LOGE("Background NTP time sync failed - Connection error"); + break; + + case NTP_ERR_TIMEOUT: + LOGE("Background NTP time sync failed - Timeout"); + break; + + case NTP_ERR_INVALID: + LOGE("Background NTP time sync failed - Invalid data received"); + break; + + default: + LOGE("Background NTP time sync failed with error: %d", result); + break; } } @@ -844,7 +861,8 @@ static void page_wifi_on_update(uint32_t delta_ms) { if (is_connected && !was_connected) { LOGI("WiFi client connection established"); - if (!clock_is_syncing_from_ntp()) { + if (!clock_is_syncing_from_ntp() && + clock_get_last_sync_status() != NTP_SUCCESS) { LOGI("Starting background NTP sync after connection established"); clock_sync_from_ntp_async(wifi_ntp_sync_callback, NULL); } @@ -1254,3 +1272,11 @@ void page_wifi_get_statusbar_text(char *buffer, int size) { } } } + +bool page_wifi_is_sta_connected(void) { + WIFI_STA_CONNECT_STATUS_S connect_status; + if (wifi_sta_get_connect_status("wlan0", &connect_status) == 0) { + return connect_status.state == WIFI_STA_STATUS_CONNECTED; + } + return false; +} diff --git a/src/ui/page_wifi.h b/src/ui/page_wifi.h index 69cad202..330ecdce 100644 --- a/src/ui/page_wifi.h +++ b/src/ui/page_wifi.h @@ -9,7 +9,17 @@ extern "C" { extern page_pack_t pp_wifi; extern void page_wifi_get_statusbar_text(char *buffer, int size); +extern bool page_wifi_is_sta_connected(void); // Nueva función #ifdef __cplusplus } #endif + +#ifndef PAGE_WIFI_H +#define PAGE_WIFI_H + +#include + +bool page_wifi_is_sta_connected(void); + +#endif // PAGE_WIFI_H diff --git a/src/util/ntp_client.c b/src/util/ntp_client.c index 10dfe1c9..70ecec95 100644 --- a/src/util/ntp_client.c +++ b/src/util/ntp_client.c @@ -307,6 +307,9 @@ static void* ntp_sync_thread(void* arg) { LOGE("NTP returned invalid date: %04d-%02d-%02d %02d:%02d:%02d", rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); result = -1; + } else if (rd.year < 2023) { // Validación adicional para asegurar fecha razonable + LOGE("NTP returned unreasonable date: %04d", rd.year); + result = -1; } else { // Actualizar el RTC rtc_set_clock(&rd); @@ -452,4 +455,33 @@ int clock_sync_from_ntp(void) { } return result; +} + +// Función pública para obtener el estado de la última sincronización +clock_sync_status_t clock_get_last_sync_status(void) { + clock_sync_status_t status; + + pthread_mutex_lock(&g_ntp_mutex); + + switch (g_ntp_sync_state) { + case NTP_SYNC_IDLE: + status = CLOCK_SYNC_NONE; + break; + case NTP_SYNC_IN_PROGRESS: + status = CLOCK_SYNC_IN_PROGRESS; + break; + case NTP_SYNC_SUCCESS: + status = CLOCK_SYNC_SUCCESS; + break; + case NTP_SYNC_FAILED: + status = CLOCK_SYNC_FAILED; + break; + default: + status = CLOCK_SYNC_NONE; + break; + } + + pthread_mutex_unlock(&g_ntp_mutex); + + return status; } \ No newline at end of file diff --git a/src/util/ntp_client.h b/src/util/ntp_client.h index 9d861fe5..dcd00edb 100644 --- a/src/util/ntp_client.h +++ b/src/util/ntp_client.h @@ -5,6 +5,12 @@ extern "C" { #endif +// Error codes +#define NTP_SUCCESS 0 +#define NTP_ERR_CONNECT -1 +#define NTP_ERR_TIMEOUT -2 +#define NTP_ERR_INVALID -3 + /** * @brief Tipo de función de callback para la sincronización NTP asíncrona * @@ -13,6 +19,16 @@ extern "C" { */ typedef void (*ntp_callback_t)(int result, void* user_data); +/** + * @brief Clock sync status values + */ +typedef enum { + CLOCK_SYNC_NONE = 0, + CLOCK_SYNC_IN_PROGRESS, + CLOCK_SYNC_SUCCESS, + CLOCK_SYNC_FAILED +} clock_sync_status_t; + /** * @brief Inicia la sincronización del reloj con un servidor NTP (bloqueante) * @@ -42,6 +58,13 @@ int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data); */ int clock_is_syncing_from_ntp(void); +/** + * @brief Obtiene el estado de la última sincronización + * + * @return Estado de la última sincronización usando clock_sync_status_t + */ +clock_sync_status_t clock_get_last_sync_status(void); + #ifdef __cplusplus } #endif diff --git a/src/util/wifi_status.c b/src/util/wifi_status.c new file mode 100644 index 00000000..2fe2cf1d --- /dev/null +++ b/src/util/wifi_status.c @@ -0,0 +1,51 @@ +#include "util/wifi_status.h" +#include +#include +#include +#include +#include +#include +#include + +int wifi_sta_get_connect_status(const char *interface, WIFI_STA_CONNECT_STATUS_S *status) { + struct iwreq wreq; + int sockfd; + + if (!interface || !status) { + return -1; + } + + // Initialize status + memset(status, 0, sizeof(WIFI_STA_CONNECT_STATUS_S)); + + // Create socket + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + return -1; + } + + // Prepare request + memset(&wreq, 0, sizeof(struct iwreq)); + strncpy(wreq.ifr_name, interface, IFNAMSIZ-1); + + // Get connection status + if (ioctl(sockfd, SIOCGIWNAME, &wreq) >= 0) { + // Check if interface is up + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); + + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) >= 0) { + if (ifr.ifr_flags & IFF_UP) { + status->state = WIFI_STA_STATUS_CONNECTED; + } else { + status->state = WIFI_STA_STATUS_DISCONNECTED; + } + } + } else { + status->state = WIFI_STA_STATUS_FAILED; + } + + close(sockfd); + return 0; +} diff --git a/src/util/wifi_status.h b/src/util/wifi_status.h new file mode 100644 index 00000000..7617828b --- /dev/null +++ b/src/util/wifi_status.h @@ -0,0 +1,19 @@ +#ifndef WIFI_STATUS_H +#define WIFI_STATUS_H + +typedef enum { + WIFI_STA_STATUS_DISCONNECTED = 0, + WIFI_STA_STATUS_CONNECTING, + WIFI_STA_STATUS_CONNECTED, + WIFI_STA_STATUS_FAILED +} wifi_sta_status_t; + +typedef struct { + wifi_sta_status_t state; + char ssid[32]; + int signal_strength; +} WIFI_STA_CONNECT_STATUS_S; + +int wifi_sta_get_connect_status(const char *interface, WIFI_STA_CONNECT_STATUS_S *status); + +#endif // WIFI_STATUS_H From e17809ed8964c98fa4cf68e8ddaebf82c8425c87 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Tue, 13 May 2025 15:18:03 -0500 Subject: [PATCH 5/8] Center RTC menu option in page_clock.c --- src/ui/page_clock.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 0bdf2694..6500c4c5 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -495,15 +495,26 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { btn_group_set_sel(&page_clock_items[ITEM_FORMAT].data.btn, g_setting.clock.format); // Time Zone label y dropdown - En una nueva fila - create_label_item(cont, _lang("Time Zone"), 1, 3, 1); + lv_obj_t* label = create_label_item(cont, _lang("Time Zone"), 1, 3, 1); - lv_obj_t* utc_dropdown = lv_dropdown_create(cont); + lv_obj_t* utc_dropdown = create_dropdown_item( + cont, // parent + "", // inicialmente vacío + 2, // columna + 3, // fila + 200, // ancho + row_dsc[3], // altura (usar la altura de la fila) + 2, // col_span + 10, // padding_top fijo de 10 + LV_GRID_ALIGN_START, // alineación + &lv_font_montserrat_26 // fuente + ); + + // Agregar las opciones al dropdown lv_dropdown_clear_options(utc_dropdown); for (int i = 0; i < sizeof(utc_options)/sizeof(utc_options[0]); i++) { lv_dropdown_add_option(utc_dropdown, utc_options[i], LV_DROPDOWN_POS_LAST); } - lv_obj_set_size(utc_dropdown, 200, 40); - lv_obj_set_grid_cell(utc_dropdown, LV_GRID_ALIGN_START, 2, 2, LV_GRID_ALIGN_START, 3, 1); lv_dropdown_set_selected(utc_dropdown, utc_offset_to_index(g_setting.clock.utc_offset)); page_clock_items[ITEM_UTC].data.obj = utc_dropdown; From a6088bfb3cab55551cc2af09dc0219a12c977596 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Tue, 13 May 2025 16:19:13 -0500 Subject: [PATCH 6/8] add option to enable or disable auto sync --- src/core/settings.c | 2 ++ src/core/settings.h | 1 + src/ui/page_clock.c | 64 ++++++++++++++++++++++++++++----------------- src/ui/page_wifi.c | 10 +++++++ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/core/settings.c b/src/core/settings.c index f3586376..c7aa9b1c 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -323,6 +323,8 @@ void settings_init(void) { int file_version = ini_getl("settings", "file_version", SETTINGS_INI_VERSION_UNKNOWN, SETTING_INI); if (file_version != SETTING_INI_VERSION) settings_reset(); + + g_setting.clock.auto_sync = ini_getl("clock", "auto_sync", 0, SETTING_INI); } void settings_load(void) { diff --git a/src/core/settings.h b/src/core/settings.h index e19f5939..6ad2b752 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -199,6 +199,7 @@ typedef struct { int sec; int format; int utc_offset; + int auto_sync; // 0: Disabled, 1: Enabled } setting_clock_t; #define WIFI_RF_CHANNELS 14 // World Channels diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 6500c4c5..498c272b 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -46,7 +46,8 @@ typedef enum { ITEM_MINUTE, ITEM_SECOND, ITEM_FORMAT, - ITEM_UTC, // Nuevo elemento para seleccionar UTC + ITEM_UTC, + ITEM_AUTO_SYNC, // New item ITEM_SET_CLOCK, ITEM_SYNC_NTP, ITEM_BACK, @@ -487,27 +488,27 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { page_clock_create_dropdown(cont, ITEM_MINUTE, page_clock_rtc_date.min, 2, 1); page_clock_create_dropdown(cont, ITEM_SECOND, page_clock_rtc_date.sec, 3, 1); - // Format selection (AM/PM or 24H) - Ahora en una fila dedicada + // Format selection - Row 2 snprintf(buf, sizeof(buf), "%s/%s", _lang("AM"), _lang("PM")); create_btn_group_item(&page_clock_items[ITEM_FORMAT].data.btn, cont, 2, _lang("Format"), buf, _lang("24 Hour"), "", "", 2); page_clock_items[ITEM_FORMAT].type = ITEM_TYPE_BTN; page_clock_items[ITEM_FORMAT].panel = arr->panel[2]; btn_group_set_sel(&page_clock_items[ITEM_FORMAT].data.btn, g_setting.clock.format); - // Time Zone label y dropdown - En una nueva fila + // Time Zone label y dropdown - Row 3 lv_obj_t* label = create_label_item(cont, _lang("Time Zone"), 1, 3, 1); lv_obj_t* utc_dropdown = create_dropdown_item( - cont, // parent - "", // inicialmente vacío - 2, // columna - 3, // fila - 200, // ancho - row_dsc[3], // altura (usar la altura de la fila) - 2, // col_span - 10, // padding_top fijo de 10 - LV_GRID_ALIGN_START, // alineación - &lv_font_montserrat_26 // fuente + cont, + "", + 2, // col + 3, // row + 200, + row_dsc[3], + 2, + 10, + LV_GRID_ALIGN_START, + &lv_font_montserrat_26 ); // Agregar las opciones al dropdown @@ -521,24 +522,33 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { page_clock_items[ITEM_UTC].type = ITEM_TYPE_OBJ; page_clock_items[ITEM_UTC].panel = arr->panel[3]; - // Set Clock (ahora en fila 4) - page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 4, 3); + // Add Auto Sync selection - Row 4 + create_btn_group_item(&page_clock_items[ITEM_AUTO_SYNC].data.btn, cont, 2, + _lang("Auto Sync"), + _lang("Enable"), + _lang("Disable"), "", "", 4); // Changed row from 2 to 4 + page_clock_items[ITEM_AUTO_SYNC].type = ITEM_TYPE_BTN; + page_clock_items[ITEM_AUTO_SYNC].panel = arr->panel[4]; + btn_group_set_sel(&page_clock_items[ITEM_AUTO_SYNC].data.btn, !g_setting.clock.auto_sync); + + // Set Clock (now in row 5) + page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 5, 3); page_clock_items[ITEM_SET_CLOCK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[4]; + page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[5]; - // Sync from Internet (ahora en fila 5) - page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 5, 3); + // Sync from Internet (now in row 6) + page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 6, 3); page_clock_items[ITEM_SYNC_NTP].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[5]; + page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[6]; - // Back (ahora en fila 6) + // Back (now in row 7) snprintf(buf, sizeof(buf), "< %s", _lang("Back")); - page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 6, 1); + page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 7, 1); page_clock_items[ITEM_BACK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_BACK].panel = arr->panel[6]; + page_clock_items[ITEM_BACK].panel = arr->panel[7]; - // Fecha/hora actual (ahora en fila 7) - page_clock_create_datetime_item(cont, 7); + // Current date/time (now in row 8) + page_clock_create_datetime_item(cont, 8); if (rtc_has_battery() != 0) { lv_obj_t *note = lv_label_create(cont); @@ -743,6 +753,12 @@ static void page_clock_on_click(uint8_t key, int sel) { } } break; + case ITEM_AUTO_SYNC: + page_clock_is_dirty = 1; + btn_group_toggle_sel(&page_clock_items[ITEM_AUTO_SYNC].data.btn); + g_setting.clock.auto_sync = !btn_group_get_sel(&page_clock_items[ITEM_AUTO_SYNC].data.btn); + ini_putl("clock", "auto_sync", g_setting.clock.auto_sync, SETTING_INI); + break; case ITEM_SET_CLOCK: if (page_clock_set_clock_confirm) { snprintf(buf, sizeof(buf), "#FF0000 %s %s...#", _lang("Updating"), _lang("Clock")); diff --git a/src/ui/page_wifi.c b/src/ui/page_wifi.c index de5d665e..94ed0be7 100644 --- a/src/ui/page_wifi.c +++ b/src/ui/page_wifi.c @@ -340,6 +340,16 @@ static void page_wifi_update_settings() { system_exec("dropbear"); } } + + // Check if we should sync time when connected as client + if (g_setting.wifi.mode == WIFI_MODE_STA && g_setting.clock.auto_sync) { + WIFI_STA_CONNECT_STATUS_S status; + if (wifi_sta_get_connect_status("wlan0", &status) == 0 && + status.state == WIFI_STA_STATUS_CONNECTED) { + // Trigger NTP sync + clock_sync_from_ntp(); + } + } } /** From 83ec1ffa95b6f8777c999918d07bcef4389aa095 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Tue, 13 May 2025 18:13:23 -0500 Subject: [PATCH 7/8] re-organize page items and make page size dynamic --- src/ui/page_clock.c | 94 ++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index 498c272b..bdc1c87e 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -46,10 +46,10 @@ typedef enum { ITEM_MINUTE, ITEM_SECOND, ITEM_FORMAT, - ITEM_UTC, - ITEM_AUTO_SYNC, // New item ITEM_SET_CLOCK, + ITEM_UTC, ITEM_SYNC_NTP, + ITEM_AUTO_SYNC, // New item ITEM_BACK, ITEM_LIST_TOTAL @@ -71,7 +71,7 @@ typedef struct { */ static const int MAX_YEARS_DROPDOWN = 300; // 2023 + 300 == 2323 static lv_coord_t col_dsc[] = {160, 160, 160, 160, 160, 160, LV_GRID_TEMPLATE_LAST}; -static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 60, 15, 10, 60, 60, 60, LV_GRID_TEMPLATE_LAST}; +static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, LV_GRID_TEMPLATE_LAST}; static item_t page_clock_items[ITEM_LIST_TOTAL] = {0}; static int page_clock_item_selected = ITEM_YEAR; static int page_clock_item_focused = 0; @@ -446,30 +446,35 @@ static void page_clock_set_clock_timer_cb(struct _lv_timer_t *timer) { * Main allocation routine for this page. */ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { - char buf[256]; + char buf[288]; + int contentHeight = 0; + for (size_t i = 0; i < (ARRAY_SIZE(row_dsc) - 1); i++) { + contentHeight += row_dsc[i]; + } + contentHeight += row_dsc[1]; + int contentWidth = 0; + for (size_t i = 0; i < (ARRAY_SIZE(col_dsc) - 1); i++) { + contentWidth += col_dsc[i]; + } + rtc_get_clock(&page_clock_rtc_date); page_clock_build_options_from_date(&page_clock_rtc_date); - // Incrementar el tamaño de las filas para evitar solapamiento - static lv_coord_t col_dsc[] = {160, 200, 160, 200, 160, 160, LV_GRID_TEMPLATE_LAST}; - static lv_coord_t row_dsc[] = {60, 60, 60, 60, 60, 60, 60, 15, 10, 60, 60, LV_GRID_TEMPLATE_LAST}; - lv_obj_t *page = lv_menu_page_create(parent, NULL); lv_obj_clear_flag(page, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_set_size(page, 1053, 900); + lv_obj_set_size(page, contentWidth + 93, contentHeight + 300); lv_obj_add_style(page, &style_subpage, LV_PART_MAIN); lv_obj_set_style_pad_top(page, 94, 0); lv_obj_t *section = lv_menu_section_create(page); lv_obj_add_style(section, &style_submenu, LV_PART_MAIN); - lv_obj_set_size(section, 1053, 894); + lv_obj_set_size(section, contentWidth + 93, contentHeight + 294); snprintf(buf, sizeof(buf), "%s:", _lang("Clock")); create_text(NULL, section, false, buf, LV_MENU_ITEM_BUILDER_VARIANT_2); lv_obj_t *cont = lv_obj_create(section); - // Aumentar el tamaño del contenedor - lv_obj_set_size(cont, 1280, 900); + lv_obj_set_size(cont, contentWidth, contentHeight); lv_obj_set_pos(cont, 0, 0); lv_obj_set_layout(cont, LV_LAYOUT_GRID); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); @@ -480,74 +485,59 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { create_select_item(arr, cont); - // Current date/time or last saved setting. + // Row 0: Year/Month/Day dropdowns page_clock_create_dropdown(cont, ITEM_YEAR, page_clock_rtc_date.year, 1, 0); page_clock_create_dropdown(cont, ITEM_MONTH, page_clock_rtc_date.month, 2, 0); page_clock_create_dropdown(cont, ITEM_DAY, page_clock_rtc_date.day, 3, 0); + + // Row 1: Hour/Minute/Second dropdowns page_clock_create_dropdown(cont, ITEM_HOUR, page_clock_rtc_date.hour, 1, 1); page_clock_create_dropdown(cont, ITEM_MINUTE, page_clock_rtc_date.min, 2, 1); page_clock_create_dropdown(cont, ITEM_SECOND, page_clock_rtc_date.sec, 3, 1); - // Format selection - Row 2 + // Row 2: Format selection (AM/PM vs 24 Hour) snprintf(buf, sizeof(buf), "%s/%s", _lang("AM"), _lang("PM")); create_btn_group_item(&page_clock_items[ITEM_FORMAT].data.btn, cont, 2, _lang("Format"), buf, _lang("24 Hour"), "", "", 2); page_clock_items[ITEM_FORMAT].type = ITEM_TYPE_BTN; page_clock_items[ITEM_FORMAT].panel = arr->panel[2]; btn_group_set_sel(&page_clock_items[ITEM_FORMAT].data.btn, g_setting.clock.format); - // Time Zone label y dropdown - Row 3 - lv_obj_t* label = create_label_item(cont, _lang("Time Zone"), 1, 3, 1); - - lv_obj_t* utc_dropdown = create_dropdown_item( - cont, - "", - 2, // col - 3, // row - 200, - row_dsc[3], - 2, - 10, - LV_GRID_ALIGN_START, - &lv_font_montserrat_26 - ); - - // Agregar las opciones al dropdown + // Row 3: Set Clock + page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 3, 3); + page_clock_items[ITEM_SET_CLOCK].type = ITEM_TYPE_BTN; + page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[3]; + + // Row 4: Time Zone + create_label_item(cont, _lang("Time Zone"), 1, 4, 1); + lv_obj_t* utc_dropdown = create_dropdown_item(cont,"",2,4,200,row_dsc[4],2,10,LV_GRID_ALIGN_START,&lv_font_montserrat_26); lv_dropdown_clear_options(utc_dropdown); for (int i = 0; i < sizeof(utc_options)/sizeof(utc_options[0]); i++) { lv_dropdown_add_option(utc_dropdown, utc_options[i], LV_DROPDOWN_POS_LAST); } lv_dropdown_set_selected(utc_dropdown, utc_offset_to_index(g_setting.clock.utc_offset)); - page_clock_items[ITEM_UTC].data.obj = utc_dropdown; page_clock_items[ITEM_UTC].type = ITEM_TYPE_OBJ; - page_clock_items[ITEM_UTC].panel = arr->panel[3]; + page_clock_items[ITEM_UTC].panel = arr->panel[4]; + + // Row 5: Sync from Internet + page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 5, 3); + page_clock_items[ITEM_SYNC_NTP].type = ITEM_TYPE_BTN; + page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[5]; - // Add Auto Sync selection - Row 4 - create_btn_group_item(&page_clock_items[ITEM_AUTO_SYNC].data.btn, cont, 2, - _lang("Auto Sync"), - _lang("Enable"), - _lang("Disable"), "", "", 4); // Changed row from 2 to 4 + // Row 6: Auto Sync + create_btn_group_item(&page_clock_items[ITEM_AUTO_SYNC].data.btn, cont, 2, _lang("Auto Sync"), + _lang("Enable"), _lang("Disable"), "", "", 6); page_clock_items[ITEM_AUTO_SYNC].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_AUTO_SYNC].panel = arr->panel[4]; + page_clock_items[ITEM_AUTO_SYNC].panel = arr->panel[6]; btn_group_set_sel(&page_clock_items[ITEM_AUTO_SYNC].data.btn, !g_setting.clock.auto_sync); - // Set Clock (now in row 5) - page_clock_items[ITEM_SET_CLOCK].data.obj = create_label_item(cont, _lang("Set Clock"), 1, 5, 3); - page_clock_items[ITEM_SET_CLOCK].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SET_CLOCK].panel = arr->panel[5]; - - // Sync from Internet (now in row 6) - page_clock_items[ITEM_SYNC_NTP].data.obj = create_label_item(cont, _lang("Sync from Internet"), 1, 6, 3); - page_clock_items[ITEM_SYNC_NTP].type = ITEM_TYPE_BTN; - page_clock_items[ITEM_SYNC_NTP].panel = arr->panel[6]; - - // Back (now in row 7) + // Row 7: Back snprintf(buf, sizeof(buf), "< %s", _lang("Back")); page_clock_items[ITEM_BACK].data.obj = create_label_item(cont, buf, 1, 7, 1); page_clock_items[ITEM_BACK].type = ITEM_TYPE_BTN; page_clock_items[ITEM_BACK].panel = arr->panel[7]; - // Current date/time (now in row 8) + // Row 8: Current Date/Time Display page_clock_create_datetime_item(cont, 8); if (rtc_has_battery() != 0) { @@ -559,7 +549,7 @@ static lv_obj_t *page_clock_create(lv_obj_t *parent, panel_arr_t *arr) { lv_obj_set_style_text_color(note, lv_color_make(255, 255, 255), 0); lv_obj_set_style_pad_top(note, 12, 0); lv_label_set_long_mode(note, LV_LABEL_LONG_WRAP); - lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 8, 2); + lv_obj_set_grid_cell(note, LV_GRID_ALIGN_START, 1, 4, LV_GRID_ALIGN_START, 9, 2); } page_clock_clear_datetime(); From a3fba72cd1373ce14ff74f9b50a5611c79b330c0 Mon Sep 17 00:00:00 2001 From: mauriciobridge Date: Tue, 13 May 2025 18:25:34 -0500 Subject: [PATCH 8/8] translate comments to english --- src/ui/page_clock.c | 14 +++--- src/ui/page_wifi.c | 2 +- src/util/ntp_client.c | 114 +++++++++++++++++++++--------------------- src/util/ntp_client.h | 6 +-- 4 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/ui/page_clock.c b/src/ui/page_clock.c index bdc1c87e..595318ad 100644 --- a/src/ui/page_clock.c +++ b/src/ui/page_clock.c @@ -84,7 +84,7 @@ static int page_clock_set_clock_confirm = 0; static int page_clock_is_dirty = 0; static struct rtc_date page_clock_rtc_date = {0}; -// Opciones de zona horaria UTC +// UTC timezone options static char* utc_options[] = { "UTC-12:00", "UTC-11:00", "UTC-10:00", "UTC-09:00", "UTC-08:00", "UTC-07:00", "UTC-06:00", "UTC-05:00", "UTC-04:00", "UTC-03:00", @@ -94,7 +94,7 @@ static char* utc_options[] = { "UTC+13:00", "UTC+14:00" }; -// Valores de offset en segundos correspondientes a las opciones +// Offset values in seconds corresponding to the options static const int utc_seconds[] = { -43200, // UTC−12:00 -39600, // UTC−11:00 @@ -125,10 +125,10 @@ static const int utc_seconds[] = { 50400 // UTC+14:00 }; -// Convertir offset en segundos a índice en el array +// Convert seconds offset to array index int utc_offset_to_index(int offset_seconds) { - // Buscar el offset más cercano - int index = 12; // Por defecto UTC±00:00 + // Find closest offset + int index = 12; // Default UTC±00:00 int min_diff = abs(offset_seconds); for (int i = 0; i < sizeof(utc_seconds)/sizeof(utc_seconds[0]); i++) { @@ -142,13 +142,13 @@ int utc_offset_to_index(int offset_seconds) { return index; } -// Convertir índice a offset en segundos +// Convert index to seconds offset int index_to_utc_offset(int index) { if (index >= 0 && index < sizeof(utc_seconds)/sizeof(utc_seconds[0])) { return utc_seconds[index]; } - return 0; // Por defecto UTC±00:00 + return 0; // Default UTC±00:00 } /** diff --git a/src/ui/page_wifi.c b/src/ui/page_wifi.c index 94ed0be7..ed928c7f 100644 --- a/src/ui/page_wifi.c +++ b/src/ui/page_wifi.c @@ -881,7 +881,7 @@ static void page_wifi_on_update(uint32_t delta_ms) { was_connected = is_connected; } - // Verificar inmediatamente después de ejecutar, luego cada 5 minutos para actualizaciones. + // Check immediately after executing, then every 5 minutes for updates. if (g_setting.wifi.enable && (elapsed == -1 || (elapsed += delta_ms) > 300000)) { switch (g_setting.wifi.mode) { case WIFI_MODE_STA: diff --git a/src/util/ntp_client.c b/src/util/ntp_client.c index 70ecec95..e0a9bb1b 100644 --- a/src/util/ntp_client.c +++ b/src/util/ntp_client.c @@ -21,11 +21,11 @@ #include "ui/page_clock.h" #include "util/ntp_client.h" -#define NTP_TIMESTAMP_DELTA 2208988800ull // Segundos entre 1900 (NTP) y 1970 (epoch) +#define NTP_TIMESTAMP_DELTA 2208988800ull // Seconds between 1900 (NTP) and 1970 (epoch) #define NTP_PORT 123 -#define NTP_TIMEOUT_SEC 3 // Timeout reducido a 3 segundos +#define NTP_TIMEOUT_SEC 3 // Timeout reduced to 3 seconds -// Estados para la sincronización NTP +// NTP synchronization states typedef enum { NTP_SYNC_IDLE = 0, NTP_SYNC_IN_PROGRESS, @@ -33,7 +33,7 @@ typedef enum { NTP_SYNC_FAILED } ntp_sync_state_t; -// Estructura del paquete NTP según RFC 5905 +// NTP packet structure according to RFC 5905 typedef struct { uint8_t li_vn_mode; /* leap indicator, version and mode */ uint8_t stratum; /* stratum level */ @@ -52,13 +52,13 @@ typedef struct { uint32_t xmitts_frac; /* transmit timestamp fraction */ } ntp_packet_t; -// Estructura para el callback +// Callback structure typedef struct { ntp_callback_t callback_fn; void* user_data; } ntp_callback_data_t; -// Variables globales +// Global variables static volatile ntp_sync_state_t g_ntp_sync_state = NTP_SYNC_IDLE; static pthread_mutex_t g_ntp_mutex = PTHREAD_MUTEX_INITIALIZER; static char* g_ntp_servers[] = { @@ -70,7 +70,7 @@ static char* g_ntp_servers[] = { }; static const int g_ntp_server_count = sizeof(g_ntp_servers) / sizeof(g_ntp_servers[0]); -// Lista de direcciones IP estáticas como respaldo +// List of static IP addresses as backup static char* g_ntp_fallback_ips[] = { "162.159.200.1", // time.cloudflare.com "216.239.35.4", // time.google.com @@ -79,12 +79,12 @@ static char* g_ntp_fallback_ips[] = { }; static const int g_ntp_fallback_count = sizeof(g_ntp_fallback_ips) / sizeof(g_ntp_fallback_ips[0]); -// Función para convertir el tiempo NTP a unix time +// Function to convert NTP time to unix time static time_t ntp_time_to_unix_time(uint32_t ntp_time) { return (time_t)(ntp_time - NTP_TIMESTAMP_DELTA); } -// Función para establecer socket no bloqueante +// Function to set non-blocking socket static int set_socket_nonblocking(int sockfd) { int flags = fcntl(sockfd, F_GETFL, 0); if (flags == -1) { @@ -93,7 +93,7 @@ static int set_socket_nonblocking(int sockfd) { return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); } -// Función para intentar resolver y conectar a un servidor NTP +// Function to attempt resolving and connecting to an NTP server static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_addr) { int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { @@ -101,7 +101,7 @@ static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_ad return -1; } - // Configurar timeout + // Set timeout struct timeval tv; tv.tv_sec = NTP_TIMEOUT_SEC; tv.tv_usec = 0; @@ -112,7 +112,7 @@ static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_ad return -1; } - // Intentar resolver el nombre del servidor + // Attempt to resolve server name struct hostent *host_entry = gethostbyname(server); if (host_entry == NULL) { LOGI("Could not resolve %s", server); @@ -120,7 +120,7 @@ static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_ad return -1; } - // Configurar dirección del servidor + // Set server address memset(serv_addr, 0, sizeof(struct sockaddr_in)); serv_addr->sin_family = AF_INET; memcpy(&serv_addr->sin_addr.s_addr, host_entry->h_addr, host_entry->h_length); @@ -129,7 +129,7 @@ static int connect_to_ntp_server(const char* server, struct sockaddr_in* serv_ad return sockfd; } -// Función para intentar conectar a una IP de servidor NTP específica +// Function to attempt connecting to a specific NTP server IP static int connect_to_ntp_ip(const char* ip_address, struct sockaddr_in* serv_addr) { int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { @@ -137,7 +137,7 @@ static int connect_to_ntp_ip(const char* ip_address, struct sockaddr_in* serv_ad return -1; } - // Configurar timeout + // Set timeout struct timeval tv; tv.tv_sec = NTP_TIMEOUT_SEC; tv.tv_usec = 0; @@ -148,7 +148,7 @@ static int connect_to_ntp_ip(const char* ip_address, struct sockaddr_in* serv_ad return -1; } - // Configurar dirección del servidor + // Set server address memset(serv_addr, 0, sizeof(struct sockaddr_in)); serv_addr->sin_family = AF_INET; serv_addr->sin_port = htons(NTP_PORT); @@ -162,30 +162,30 @@ static int connect_to_ntp_ip(const char* ip_address, struct sockaddr_in* serv_ad return sockfd; } -// Función para enviar y recibir paquete NTP (con reintentos) +// Function to send and receive NTP packet (with retries) static int send_receive_ntp_packet(int sockfd, struct sockaddr_in* serv_addr, ntp_packet_t* packet) { int retries = 3; socklen_t addr_len = sizeof(struct sockaddr_in); - // Inicializar paquete NTP + // Initialize NTP packet memset(packet, 0, sizeof(ntp_packet_t)); - packet->li_vn_mode = 0x1b; // LI = 0, Version = 3, Mode = 3 (cliente) + packet->li_vn_mode = 0x1b; // LI = 0, Version = 3, Mode = 3 (client) while (retries--) { - // Enviar paquete + // Send packet if (sendto(sockfd, packet, sizeof(ntp_packet_t), 0, (struct sockaddr *)serv_addr, addr_len) < 0) { LOGE("Error sending NTP packet (retry %d): %s", 2-retries, strerror(errno)); continue; } - // Configurar select para timeout + // Set select for timeout fd_set readfds; FD_ZERO(&readfds); FD_SET(sockfd, &readfds); struct timeval timeout; - timeout.tv_sec = 1; // 1 segundo por intento + timeout.tv_sec = 1; // 1 second per attempt timeout.tv_usec = 0; int select_result = select(sockfd + 1, &readfds, NULL, NULL, &timeout); @@ -199,28 +199,28 @@ static int send_receive_ntp_packet(int sockfd, struct sockaddr_in* serv_addr, nt continue; } - // Recibir respuesta + // Receive response if (recvfrom(sockfd, packet, sizeof(ntp_packet_t), 0, (struct sockaddr *)serv_addr, &addr_len) < 0) { LOGE("Error receiving NTP packet (retry %d): %s", 2-retries, strerror(errno)); continue; } - // Éxito + // Success return 0; } - // Agotados los intentos + // Attempts exhausted return -1; } -// Función para calcular correctamente la diferencia de zona horaria +// Function to correctly calculate timezone difference static time_t apply_timezone_offset(time_t utc_time, int offset_seconds) { - // Simplemente sumar el offset en segundos + // Simply add the offset in seconds return utc_time + offset_seconds; } -// Función que realiza la sincronización NTP (ejecutada en un hilo) +// Function that performs NTP synchronization (executed in a thread) static void* ntp_sync_thread(void* arg) { ntp_callback_data_t* callback_data = (ntp_callback_data_t*)arg; int result = -1; @@ -230,17 +230,17 @@ static void* ntp_sync_thread(void* arg) { LOGI("NTP sync thread started"); - // Verificar si WiFi está habilitado + // Check if WiFi is enabled if (!g_setting.wifi.enable) { LOGE("WiFi disabled, cannot sync time from NTP"); goto cleanup; } - // Asegurarse de que WiFi esté activo + // Ensure WiFi is active system_exec("ifconfig wlan0 up"); - usleep(500000); // Espera reducida a 0.5 segundos + usleep(500000); // Wait reduced to 0.5 seconds - // Intentar varios servidores NTP + // Attempt multiple NTP servers for (int i = 0; i < g_ntp_server_count; i++) { LOGI("Trying NTP server: %s", g_ntp_servers[i]); @@ -258,7 +258,7 @@ static void* ntp_sync_thread(void* arg) { sockfd = -1; } - // Si ningún servidor funciona, intentar con las IPs de respaldo + // If no server works, try the backup IPs if (result != 0) { LOGI("Trying fallback NTP server IPs"); for (int i = 0; i < g_ntp_fallback_count; i++) { @@ -280,21 +280,21 @@ static void* ntp_sync_thread(void* arg) { } if (result == 0) { - // Convertir tiempo recibido + // Convert received time uint32_t txTm = ntohl(packet.xmitts_sec); time_t utc_time = ntp_time_to_unix_time(txTm); LOGI("NTP time received (UTC): %u", (unsigned int)utc_time); - // Usar el offset configurado por el usuario + // Use the user-configured offset int timezone_offset = g_setting.clock.utc_offset; LOGI("Using configured timezone offset: %d seconds", timezone_offset); - // Aplicar la zona horaria para obtener la hora local + // Apply the timezone to get local time time_t local_time = apply_timezone_offset(utc_time, timezone_offset); LOGI("Local time after timezone adjustment: %u", (unsigned int)local_time); - // Actualizar RTC con el tiempo recibido ajustado a zona horaria local + // Update RTC with the received time adjusted to local timezone struct timeval tv_now; tv_now.tv_sec = local_time; tv_now.tv_usec = 0; @@ -302,21 +302,21 @@ static void* ntp_sync_thread(void* arg) { struct rtc_date rd; rtc_tv2rd(&tv_now, &rd); - // Validar la fecha + // Validate the date if (rtc_has_valid_date(&rd) != 0) { LOGE("NTP returned invalid date: %04d-%02d-%02d %02d:%02d:%02d", rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); result = -1; - } else if (rd.year < 2023) { // Validación adicional para asegurar fecha razonable + } else if (rd.year < 2023) { // Additional validation to ensure reasonable date LOGE("NTP returned unreasonable date: %04d", rd.year); result = -1; } else { - // Actualizar el RTC + // Update the RTC rtc_set_clock(&rd); LOGI("NTP time sync successful: %04d-%02d-%02d %02d:%02d:%02d", rd.year, rd.month, rd.day, rd.hour, rd.min, rd.sec); - // Guardar en settings + // Save to settings g_setting.clock.year = rd.year; g_setting.clock.month = rd.month; g_setting.clock.day = rd.day; @@ -324,7 +324,7 @@ static void* ntp_sync_thread(void* arg) { g_setting.clock.min = rd.min; g_setting.clock.sec = rd.sec; - // Actualizar archivo de configuración + // Update configuration file ini_putl("clock", "year", g_setting.clock.year, SETTING_INI); ini_putl("clock", "month", g_setting.clock.month, SETTING_INI); ini_putl("clock", "day", g_setting.clock.day, SETTING_INI); @@ -339,12 +339,12 @@ static void* ntp_sync_thread(void* arg) { close(sockfd); } - // Actualizar estado + // Update state pthread_mutex_lock(&g_ntp_mutex); g_ntp_sync_state = (result == 0) ? NTP_SYNC_SUCCESS : NTP_SYNC_FAILED; pthread_mutex_unlock(&g_ntp_mutex); - // Llamar al callback si existe + // Call the callback if it exists if (callback_data != NULL) { if (callback_data->callback_fn != NULL) { callback_data->callback_fn(result, callback_data->user_data); @@ -356,7 +356,7 @@ static void* ntp_sync_thread(void* arg) { return NULL; } -// Función pública para verificar si hay una sincronización en progreso +// Public function to check if synchronization is in progress int clock_is_syncing_from_ntp(void) { int is_syncing = 0; @@ -367,23 +367,23 @@ int clock_is_syncing_from_ntp(void) { return is_syncing; } -// Función pública para iniciar la sincronización NTP (asíncrona con callback) +// Public function to start NTP synchronization (asynchronous with callback) int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data) { int ret = -1; pthread_mutex_lock(&g_ntp_mutex); - // Verificar si ya hay una sincronización en progreso + // Check if synchronization is already in progress if (g_ntp_sync_state == NTP_SYNC_IN_PROGRESS) { LOGI("NTP sync already in progress"); pthread_mutex_unlock(&g_ntp_mutex); return -1; } - // Actualizar estado + // Update state g_ntp_sync_state = NTP_SYNC_IN_PROGRESS; - // Preparar datos para el callback + // Prepare data for the callback ntp_callback_data_t* callback_data = malloc(sizeof(ntp_callback_data_t)); if (callback_data == NULL) { LOGE("Failed to allocate memory for callback data"); @@ -395,7 +395,7 @@ int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data) { callback_data->callback_fn = callback_fn; callback_data->user_data = user_data; - // Crear hilo para sincronización + // Create thread for synchronization pthread_t thread_id; if (pthread_create(&thread_id, NULL, ntp_sync_thread, callback_data) != 0) { LOGE("Error creating NTP thread"); @@ -411,23 +411,23 @@ int clock_sync_from_ntp_async(ntp_callback_t callback_fn, void* user_data) { return ret; } -// Función pública para compatibilidad con el código existente (bloqueante) +// Public function for compatibility with existing code (blocking) int clock_sync_from_ntp(void) { - // Verificar si ya hay una sincronización en progreso + // Check if synchronization is already in progress if (clock_is_syncing_from_ntp()) { LOGI("NTP sync already in progress"); return -1; } - // Intentar iniciar sincronización asíncrona sin callback + // Attempt to start asynchronous synchronization without callback if (clock_sync_from_ntp_async(NULL, NULL) != 0) { return -1; } - // Esperar resultado (pero con timeout) + // Wait for result (but with timeout) int result = -1; int timeout_count = 0; - const int max_timeout = 10; // 10 * 100ms = 1 segundo máximo + const int max_timeout = 10; // 10 * 100ms = 1 second max while (timeout_count < max_timeout) { usleep(100000); // 100ms @@ -449,7 +449,7 @@ int clock_sync_from_ntp(void) { timeout_count++; } - // Si aún está en progreso después del timeout, asumimos que continuará en segundo plano + // If still in progress after timeout, assume it will continue in background if (timeout_count >= max_timeout) { LOGI("NTP sync still in progress, continuing in background"); } @@ -457,7 +457,7 @@ int clock_sync_from_ntp(void) { return result; } -// Función pública para obtener el estado de la última sincronización +// Public function to get last synchronization status clock_sync_status_t clock_get_last_sync_status(void) { clock_sync_status_t status; diff --git a/src/util/ntp_client.h b/src/util/ntp_client.h index dcd00edb..760ae66e 100644 --- a/src/util/ntp_client.h +++ b/src/util/ntp_client.h @@ -12,10 +12,10 @@ extern "C" { #define NTP_ERR_INVALID -3 /** - * @brief Tipo de función de callback para la sincronización NTP asíncrona + * @brief Callback function type for asynchronous NTP synchronization * - * @param result Resultado de la sincronización (0 = éxito, -1 = error) - * @param user_data Datos proporcionados por el usuario en la llamada inicial + * @param result Synchronization result (0 = success, -1 = error) + * @param user_data User data provided in initial call */ typedef void (*ntp_callback_t)(int result, void* user_data);