From 8487ff6d2c6af6663534c77a2bb9fa856ea5cea1 Mon Sep 17 00:00:00 2001 From: liu weikai Date: Thu, 13 Aug 2026 21:36:59 +0800 Subject: [PATCH] fix(wifi): sync time on each IP lease --- .../c6_companion/components/tm_wifi/tm_wifi.c | 33 ++++++++++--------- .../test_wifi_connect_backoff_contract.cpp | 31 +++++++++++++++++ .../platform/esp/common/wifi_runtime_impl.h | 31 +++++++++-------- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/firmware/c6_companion/components/tm_wifi/tm_wifi.c b/firmware/c6_companion/components/tm_wifi/tm_wifi.c index 55b90818..bff36488 100644 --- a/firmware/c6_companion/components/tm_wifi/tm_wifi.c +++ b/firmware/c6_companion/components/tm_wifi/tm_wifi.c @@ -28,7 +28,6 @@ static bool s_netif_ready; static bool s_wifi_initialized; static bool s_wifi_started; static bool s_time_sync_in_progress; -static bool s_time_sync_attempted; static time_t s_time_sync_epoch; static esp_timer_handle_t s_time_sync_timeout_timer; static TaskHandle_t s_time_sync_emit_task; @@ -82,21 +81,28 @@ static void stop_sntp_once(void) esp_sntp_set_time_sync_notification_cb(NULL); } -static void cancel_network_time_sync(void) +// Keep the timer as Wi-Fi runtime state to avoid repeated heap allocation on +// every DHCP lease, but always return its active resources on each terminal +// path. A subsequent GOT_IP can then start a fresh one-shot sync. +static void finish_network_time_sync(void) { + s_time_sync_in_progress = false; if (s_time_sync_timeout_timer != NULL) { (void)esp_timer_stop(s_time_sync_timeout_timer); } stop_sntp_once(); - s_time_sync_in_progress = false; +} + +static void cancel_network_time_sync(void) +{ + finish_network_time_sync(); } static void time_sync_emit_task(void* arg) { (void)arg; const time_t epoch_seconds = s_time_sync_epoch; - stop_sntp_once(); if (valid_network_epoch(epoch_seconds)) { @@ -109,7 +115,7 @@ static void time_sync_emit_task(void* arg) emit_time_sync_event(TM_C6_ERROR_INTERNAL, 0, "c6_wifi_sntp"); } - s_time_sync_in_progress = false; + finish_network_time_sync(); s_time_sync_emit_task = NULL; vTaskDelete(NULL); } @@ -139,8 +145,7 @@ static void time_sync_notification_cb(struct timeval* tv) 5, &s_time_sync_emit_task) != pdPASS) { - stop_sntp_once(); - s_time_sync_in_progress = false; + finish_network_time_sync(); s_time_sync_emit_task = NULL; ESP_LOGW(TAG, "Failed to start SNTP emit task"); } @@ -155,8 +160,7 @@ static void time_sync_timeout_cb(void* arg) } ESP_LOGW(TAG, "SNTP sync timed out server=%s", TM_WIFI_SNTP_SERVER); - stop_sntp_once(); - s_time_sync_in_progress = false; + finish_network_time_sync(); } static bool ensure_time_sync_timer(void) @@ -204,12 +208,12 @@ static void start_or_restart_sntp(void) static void request_network_time_sync(void) { - if (s_time_sync_attempted || s_time_sync_in_progress) + if (s_time_sync_in_progress) { + ESP_LOGI(TAG, "Ignoring duplicate GOT_IP while SNTP is in progress"); return; } - s_time_sync_attempted = true; if (!ensure_time_sync_timer()) { return; @@ -220,7 +224,7 @@ static void request_network_time_sync(void) esp_timer_start_once(s_time_sync_timeout_timer, (uint64_t)TM_WIFI_SNTP_TIMEOUT_MS * 1000ULL); if (timer_err != ESP_OK) { - s_time_sync_in_progress = false; + finish_network_time_sync(); ESP_LOGW(TAG, "Failed to start SNTP timeout timer: %s", esp_err_to_name(timer_err)); return; } @@ -290,7 +294,6 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e } case WIFI_EVENT_STA_DISCONNECTED: cancel_network_time_sync(); - s_time_sync_attempted = false; emit_simple_event(TM_C6_WIFI_EVENT_STA_DISCONNECTED, TM_C6_OK, NULL, 0); break; case WIFI_EVENT_AP_START: @@ -314,6 +317,7 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e ipv4 = event->ip_info.ip.addr; } emit_simple_event(TM_C6_WIFI_EVENT_STA_GOT_IP, TM_C6_OK, s_config.sta_ssid, ipv4); + ESP_LOGI(TAG, "GOT_IP triggers one-shot SNTP"); request_network_time_sync(); } } @@ -381,7 +385,6 @@ esp_err_t tm_wifi_apply_config(const tm_c6_wifi_config_t* config) if (!config->wifi_enabled) { cancel_network_time_sync(); - s_time_sync_attempted = false; if (s_wifi_started) { (void)esp_wifi_disconnect(); @@ -470,7 +473,6 @@ esp_err_t tm_wifi_handle_control(const tm_c6_wifi_control_t* control) case TM_C6_WIFI_CMD_CONNECT: { cancel_network_time_sync(); - s_time_sync_attempted = false; s_config.wifi_enabled = 1; s_config.sta_enabled = 1; copy_text(s_config.sta_ssid, sizeof(s_config.sta_ssid), control->ssid); @@ -480,7 +482,6 @@ esp_err_t tm_wifi_handle_control(const tm_c6_wifi_control_t* control) } case TM_C6_WIFI_CMD_DISCONNECT: cancel_network_time_sync(); - s_time_sync_attempted = false; return esp_wifi_disconnect(); case TM_C6_WIFI_CMD_GET_IP: { diff --git a/modules/core_sys/tests/test_wifi_connect_backoff_contract.cpp b/modules/core_sys/tests/test_wifi_connect_backoff_contract.cpp index 6dd01e80..8e9cd219 100644 --- a/modules/core_sys/tests/test_wifi_connect_backoff_contract.cpp +++ b/modules/core_sys/tests/test_wifi_connect_backoff_contract.cpp @@ -21,6 +21,24 @@ bool contains(const std::string& haystack, const char* needle) return haystack.find(needle) != std::string::npos; } +std::size_t occurrences(const std::string& haystack, const char* needle) +{ + const std::string target = needle ? needle : ""; + if (target.empty()) + { + return 0; + } + + std::size_t count = 0; + std::size_t offset = 0; + while ((offset = haystack.find(target, offset)) != std::string::npos) + { + ++count; + offset += target.size(); + } + return count; +} + } // namespace int main(int argc, char** argv) @@ -36,6 +54,8 @@ int main(int argc, char** argv) const std::string wifi_runtime = readFile( repo_root / "platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h"); + const std::string c6_wifi_runtime = + readFile(repo_root / "firmware/c6_companion/components/tm_wifi/tm_wifi.c"); const std::string team_pairing_transport = readFile( repo_root / "platform/esp/arduino_common/src/team/pairing/team_pairing_transport_espnow.cpp"); @@ -58,6 +78,17 @@ int main(int argc, char** argv) assert(contains(wifi_runtime, "void profile_retry_timer_cb(void*)")); assert(contains(wifi_runtime, "schedule_profile_retry();")); assert(contains(wifi_runtime, "(void)connect(nullptr);")); + assert(!contains(wifi_runtime, "network_time_sync_attempted")); + assert(contains(wifi_runtime, "void finish_network_time_sync()")); + assert(contains(wifi_runtime, "GOT_IP triggers one-shot SNTP")); + assert(contains(wifi_runtime, "duplicate GOT_IP ignored while SNTP is in progress")); + assert(occurrences(wifi_runtime, "finish_network_time_sync();") >= 5); + + assert(!contains(c6_wifi_runtime, "s_time_sync_attempted")); + assert(contains(c6_wifi_runtime, "static void finish_network_time_sync(void)")); + assert(contains(c6_wifi_runtime, "GOT_IP triggers one-shot SNTP")); + assert(contains(c6_wifi_runtime, "Ignoring duplicate GOT_IP while SNTP is in progress")); + assert(occurrences(c6_wifi_runtime, "finish_network_time_sync();") >= 5); assert(contains(team_pairing_transport, "set_non_preemptible_activity(true, \"team_pairing\")")); assert(contains(team_pairing_transport, "set_non_preemptible_activity(false)")); diff --git a/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h b/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h index ad739a63..7500fb95 100644 --- a/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h +++ b/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h @@ -87,7 +87,6 @@ struct RuntimeState bool wifi_initialized = false; bool ble_paused_for_wifi = false; bool network_time_sync_in_progress = false; - bool network_time_sync_attempted = false; std::time_t network_time_sync_epoch = 0; bool config_cached = false; bool profiles_cached = false; @@ -758,7 +757,6 @@ void clear_connection_details() cancel_network_time_sync(); s_runtime.connected = false; s_runtime.connecting = false; - s_runtime.network_time_sync_attempted = false; s_runtime.rssi = -127; s_runtime.ssid[0] = '\0'; s_runtime.ip[0] = '\0'; @@ -848,20 +846,28 @@ void stop_sntp_once() esp_sntp_set_time_sync_notification_cb(nullptr); } -void cancel_network_time_sync() +// A time sync is strictly bound to the current IP lease. Keep the timer +// allocated with the Wi-Fi runtime so repeated DHCP events do not fragment +// scarce internal memory, but make every terminal path stop it and unregister +// the SNTP callback before another lease can start a new sync. +void finish_network_time_sync() { + s_runtime.network_time_sync_in_progress = false; if (s_runtime.network_time_sync_timeout_timer != nullptr) { (void)esp_timer_stop(s_runtime.network_time_sync_timeout_timer); } stop_sntp_once(); - s_runtime.network_time_sync_in_progress = false; +} + +void cancel_network_time_sync() +{ + finish_network_time_sync(); } void network_time_apply_task(void*) { const std::time_t epoch_seconds = s_runtime.network_time_sync_epoch; - stop_sntp_once(); if (is_valid_network_epoch(epoch_seconds)) { @@ -877,7 +883,7 @@ void network_time_apply_task(void*) static_cast(epoch_seconds)); } - s_runtime.network_time_sync_in_progress = false; + finish_network_time_sync(); s_runtime.network_time_apply_task = nullptr; vTaskDelete(nullptr); } @@ -908,8 +914,7 @@ void network_time_sync_notification_cb(timeval* tv) &s_runtime.network_time_apply_task); if (created != pdPASS) { - stop_sntp_once(); - s_runtime.network_time_sync_in_progress = false; + finish_network_time_sync(); s_runtime.network_time_apply_task = nullptr; std::printf("[WiFi][Time] failed to start SNTP apply task\n"); } @@ -923,8 +928,7 @@ void network_time_sync_timeout_cb(void*) } std::printf("[WiFi][Time] SNTP sync timed out server=%s\n", kNetworkTimeSyncServer); - stop_sntp_once(); - s_runtime.network_time_sync_in_progress = false; + finish_network_time_sync(); } bool ensure_network_time_sync_timer() @@ -971,12 +975,12 @@ void start_or_restart_sntp() void request_network_time_sync() { - if (s_runtime.network_time_sync_attempted || s_runtime.network_time_sync_in_progress) + if (s_runtime.network_time_sync_in_progress) { + std::printf("[WiFi][Time] duplicate GOT_IP ignored while SNTP is in progress\n"); return; } - s_runtime.network_time_sync_attempted = true; if (!ensure_network_time_sync_timer()) { return; @@ -988,7 +992,7 @@ void request_network_time_sync() static_cast(kNetworkTimeSyncTimeoutMs) * 1000ULL); if (timer_err != ESP_OK) { - s_runtime.network_time_sync_in_progress = false; + finish_network_time_sync(); std::printf("[WiFi][Time] failed to start SNTP timeout timer err=0x%x\n", static_cast(timer_err)); return; @@ -1225,6 +1229,7 @@ void wifi_event_handler(void*, s_runtime.ip[0] ? s_runtime.ip : "", s_runtime.ssid[0] ? s_runtime.ssid : current_config().ssid, s_runtime.rssi); + std::printf("[WiFi][Time] GOT_IP triggers one-shot SNTP\n"); request_network_time_sync(); ensure_reconnect_memory_reserve(); }