diff --git a/components/esp_netif/include/esp_netif_sntp.h b/components/esp_netif/include/esp_netif_sntp.h index 3b58b73807..8266b7400f 100644 --- a/components/esp_netif/include/esp_netif_sntp.h +++ b/components/esp_netif/include/esp_netif_sntp.h @@ -17,6 +17,29 @@ extern "C" { #endif +/** + * @brief SNTP event base for esp-netif + */ +ESP_EVENT_DECLARE_BASE(NETIF_SNTP_EVENT); + +/** + * @brief Event IDs for NETIF_SNTP_EVENT + */ +typedef enum { + NETIF_SNTP_TIME_SYNC = 0, /**< System time synchronized via SNTP */ +} esp_netif_sntp_event_t; + +/** + * @brief Event payload for NETIF_SNTP_TIME_SYNC + * + * The event data passed to handlers registered for + * `NETIF_SNTP_EVENT`/`NETIF_SNTP_TIME_SYNC` is a pointer to this struct. + */ +typedef struct esp_netif_sntp_time_sync { + struct timeval tv; ///< Time of synchronization as reported by SNTP +} esp_netif_sntp_time_sync_t; + + /** * @brief Time sync notification function */ diff --git a/components/esp_netif/lwip/esp_netif_sntp.c b/components/esp_netif/lwip/esp_netif_sntp.c index 1a7da5e442..c924bf33ed 100644 --- a/components/esp_netif/lwip/esp_netif_sntp.c +++ b/components/esp_netif/lwip/esp_netif_sntp.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,8 @@ static const char *TAG = "esp_netif_sntp"; +ESP_EVENT_DEFINE_BASE(NETIF_SNTP_EVENT); + typedef struct sntp_storage { esp_sntp_time_cb_t sync_cb; SemaphoreHandle_t sync_sem; @@ -38,6 +40,14 @@ static void sync_time_cb(struct timeval *tv) if (s_storage && s_storage->sync_cb) { s_storage->sync_cb(tv); } + // Post event with the timeval payload + esp_netif_sntp_time_sync_t evt = {0}; + if (tv) { + evt.tv = *tv; + } + if (esp_event_post(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &evt, sizeof(evt), 0) != ESP_OK) { + ESP_LOGE(TAG, "Error posting SNTP time sync"); + } } static esp_err_t sntp_init_api(void *ctx) diff --git a/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c b/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c index 9dcd8c6623..99ef82224e 100644 --- a/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c +++ b/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c @@ -9,6 +9,7 @@ #include "unity_fixture.h" #include "esp_netif.h" #include "esp_netif_sntp.h" +#include "esp_event.h" #include "esp_netif_net_stack.h" #include "esp_wifi.h" #include "nvs_flash.h" @@ -18,8 +19,9 @@ #include "memory_checks.h" #include "lwip/netif.h" #include "esp_netif_test.h" -#include "esp_event.h" +#include "freertos/FreeRTOS.h" #include "freertos/semphr.h" +#include "sntp/sntp_get_set_time.h" TEST_GROUP(esp_netif); @@ -68,6 +70,44 @@ TEST(esp_netif, init_and_destroy_sntp) esp_netif_sntp_deinit(); } +static SemaphoreHandle_t s_sntp_evt_sem; +static void sntp_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) +{ + (void)arg; (void)base; (void)id; (void)data; + if (s_sntp_evt_sem) { + xSemaphoreGive(s_sntp_evt_sem); + } +} + +TEST(esp_netif, sntp_posts_time_sync_event) +{ + test_case_uses_tcpip(); + TEST_ESP_OK(esp_event_loop_create_default()); + + // Register handler for NETIF_SNTP_EVENT + s_sntp_evt_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(s_sntp_evt_sem); + TEST_ESP_OK(esp_event_handler_register(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &sntp_event_handler, NULL)); + + // Initialize esp-netif SNTP (no auto-start needed for this test) + esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("127.0.0.1"); + config.start = false; + TEST_ESP_OK(esp_netif_sntp_init(&config)); + + // Trigger the SNTP time sync callback path artificially + sntp_set_system_time(1, 0); + + // Wait for event to be posted + TEST_ASSERT_EQUAL(pdTRUE, xQueueSemaphoreTake(s_sntp_evt_sem, pdMS_TO_TICKS(1000))); + + // Cleanup + esp_netif_sntp_deinit(); + TEST_ESP_OK(esp_event_handler_unregister(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &sntp_event_handler)); + vSemaphoreDelete(s_sntp_evt_sem); + s_sntp_evt_sem = NULL; + TEST_ESP_OK(esp_event_loop_delete_default()); +} + TEST(esp_netif, convert_ip_addresses) { const char *ipv4_src[] = {"127.168.1.1", "255.255.255.0", "305.500.721.801", "127.168.1..", "abc.def.***.ddd"}; @@ -666,6 +706,7 @@ TEST_GROUP_RUNNER(esp_netif) */ RUN_TEST_CASE(esp_netif, init_and_destroy) RUN_TEST_CASE(esp_netif, init_and_destroy_sntp) + RUN_TEST_CASE(esp_netif, sntp_posts_time_sync_event) RUN_TEST_CASE(esp_netif, convert_ip_addresses) RUN_TEST_CASE(esp_netif, get_from_if_key) RUN_TEST_CASE(esp_netif, create_delete_multiple_netifs) diff --git a/docs/en/api-reference/network/esp_netif_programming.rst b/docs/en/api-reference/network/esp_netif_programming.rst index a07bf12750..18bebcd2ff 100644 --- a/docs/en/api-reference/network/esp_netif_programming.rst +++ b/docs/en/api-reference/network/esp_netif_programming.rst @@ -40,6 +40,31 @@ This section provides more details on specific use cases for the SNTP service, s 3. Wait for the system time to synchronize using :cpp:func:`esp_netif_sntp_sync_wait()` (if required). 4. Stop and destroy the service using :cpp:func:`esp_netif_sntp_deinit()`. +Events +^^^^^^ + +The SNTP wrapper posts an event when the system time is synchronized: + +- Event base: ``NETIF_SNTP_EVENT`` +- Event ID: ``NETIF_SNTP_TIME_SYNC`` +- Event data: pointer to :cpp:type:`esp_netif_sntp_time_sync_t` with the synchronized ``timeval`` + +Register a handler after creating the default event loop: + +.. code-block:: c + + static void sntp_evt_handler(void *arg, esp_event_base_t base, int32_t id, void *data) + { + const esp_netif_sntp_time_sync_t *evt = (const esp_netif_sntp_time_sync_t *)data; + if (evt) { + ESP_LOGI("sntp", "time synchronized: %ld.%06ld", (long)evt->tv.tv_sec, (long)evt->tv.tv_usec); + // Optionally convert to human-readable time using localtime_r() or gmtime_r() + } + } + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &sntp_evt_handler, NULL)); + Basic Mode with Statically Defined Server(s) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/en/api-reference/system/system_time.rst b/docs/en/api-reference/system/system_time.rst index 2cba208e15..bab70f28e8 100644 --- a/docs/en/api-reference/system/system_time.rst +++ b/docs/en/api-reference/system/system_time.rst @@ -105,7 +105,7 @@ To initialize a particular SNTP server and also start the SNTP service, simply c esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org"); esp_netif_sntp_init(&config); -This code automatically performs time synchronization once a reply from the SNTP server is received. Sometimes it is useful to wait until the time gets synchronized, :cpp:func:`esp_netif_sntp_sync_wait()` can be used for this purpose: +This code automatically performs time synchronization once a reply from the SNTP server is received. Sometimes it is useful to wait until the time gets synchronized, :cpp:func:`esp_netif_sntp_sync_wait()` can be used for this purpose. Applications can also subscribe to an event posted when time is synchronized (``NETIF_SNTP_EVENT``, ID ``NETIF_SNTP_TIME_SYNC``). The event data is a pointer to :cpp:type:`esp_netif_sntp_time_sync_t` containing the synchronized ``timeval``: .. code-block:: c diff --git a/docs/zh_CN/api-reference/system/system_time.rst b/docs/zh_CN/api-reference/system/system_time.rst index d4c0e2e105..86a7ff0861 100644 --- a/docs/zh_CN/api-reference/system/system_time.rst +++ b/docs/zh_CN/api-reference/system/system_time.rst @@ -105,7 +105,7 @@ SNTP 时间同步 esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org"); esp_netif_sntp_init(&config); -一旦收到 SNTP 服务器的响应,此代码会自动执行时间同步。有时等待时间同步很有意义,调用 :cpp:func:`esp_netif_sntp_sync_wait()` 可实现此目的: +一旦收到 SNTP 服务器的响应,此代码会自动执行时间同步。有时等待时间同步很有意义,调用 :cpp:func:`esp_netif_sntp_sync_wait()` 可实现此目的。应用也可以订阅时间同步事件(事件基座 ``NETIF_SNTP_EVENT``,事件 ID ``NETIF_SNTP_TIME_SYNC``): .. code-block:: c diff --git a/examples/protocols/sntp/README.md b/examples/protocols/sntp/README.md index 176001af20..230c3b2342 100644 --- a/examples/protocols/sntp/README.md +++ b/examples/protocols/sntp/README.md @@ -83,6 +83,15 @@ This example can use 3 time synchronization method: - `sntp_get_sync_status()` and `sntp_set_sync_status()` - get/set time synchronization status. - `sntp_get_sync_mode()` and `sntp_set_sync_mode()` - get/set the sync mode. Allowable two mode: `SNTP_SYNC_MODE_IMMED` and `SNTP_SYNC_MODE_SMOOTH`. +## Events + +The esp-netif SNTP wrapper emits an event when the system time is synchronized: + +- Event base: `NETIF_SNTP_EVENT` +- Event ID: `NETIF_SNTP_TIME_SYNC` + +Register a handler after creating the default event loop (the event data is a pointer to `esp_netif_sntp_time_sync_t` containing the synchronized `timeval`). You can convert the timeval to human-readable local time or UTC if desired using `localtime_r()`/`gmtime_r()`. + ## Mode of update time `sntp_set_sync_mode()` - Set the sync mode of the system time. It has two mode: diff --git a/examples/protocols/sntp/main/sntp_example_main.c b/examples/protocols/sntp/main/sntp_example_main.c index cdb681890d..6149878677 100644 --- a/examples/protocols/sntp/main/sntp_example_main.c +++ b/examples/protocols/sntp/main/sntp_example_main.c @@ -34,6 +34,22 @@ RTC_DATA_ATTR static int boot_count = 0; static void obtain_time(void); +static void sntp_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) +{ + (void)arg; (void)base; (void)id; + const esp_netif_sntp_time_sync_t *evt = (const esp_netif_sntp_time_sync_t *)data; + if (evt) { + char ts[64]; + time_t t = evt->tv.tv_sec; + struct tm tm_utc; + gmtime_r(&t, &tm_utc); + strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tm_utc); + ESP_LOGI(TAG, "SNTP event: time synced (UTC): %s.%06ld", ts, (long)evt->tv.tv_usec); + } else { + ESP_LOGI(TAG, "SNTP event: time synced (no timeval provided)"); + } +} + #ifdef CONFIG_SNTP_TIME_SYNC_METHOD_CUSTOM void sntp_sync_time(struct timeval *tv) { @@ -140,6 +156,8 @@ static void obtain_time(void) ESP_ERROR_CHECK( nvs_flash_init() ); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK( esp_event_loop_create_default() ); + // Register handler to log SNTP event with timeval payload + ESP_ERROR_CHECK(esp_event_handler_register(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &sntp_event_handler, NULL)); #if LWIP_DHCP_GET_NTP_SRV /** @@ -223,4 +241,5 @@ static void obtain_time(void) ESP_ERROR_CHECK( example_disconnect() ); esp_netif_sntp_deinit(); + ESP_ERROR_CHECK(esp_event_handler_unregister(NETIF_SNTP_EVENT, NETIF_SNTP_TIME_SYNC, &sntp_event_handler)); }