diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index ccd9af3153..1cca5e8043 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -14,8 +14,13 @@ namespace sntp { static const char *const TAG = "sntp"; +#if defined(USE_ESP32) +SNTPComponent *SNTPComponent::instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#endif + void SNTPComponent::setup() { #if defined(USE_ESP32) + SNTPComponent::instance = this; if (esp_sntp_enabled()) { esp_sntp_stop(); } @@ -25,6 +30,11 @@ void SNTPComponent::setup() { esp_sntp_setservername(i++, server.c_str()); } esp_sntp_set_sync_interval(this->get_update_interval()); + esp_sntp_set_time_sync_notification_cb([](struct timeval *tv) { + if (SNTPComponent::instance != nullptr) { + SNTPComponent::instance->defer([]() { SNTPComponent::instance->time_synced(); }); + } + }); esp_sntp_init(); #else sntp_stop(); @@ -34,6 +44,14 @@ void SNTPComponent::setup() { for (auto &server : this->servers_) { sntp_setservername(i++, server.c_str()); } + +#if defined(USE_ESP8266) + settimeofday_cb([this](bool from_sntp) { + if (from_sntp) + this->time_synced(); + }); +#endif + sntp_init(); #endif } @@ -46,7 +64,8 @@ void SNTPComponent::dump_config() { } void SNTPComponent::update() { #if !defined(USE_ESP32) - // force resync + // Some platforms currently cannot set the sync interval at runtime so we need + // to do the re-sync by hand for now. if (sntp_enabled()) { sntp_stop(); this->has_time_ = false; @@ -55,23 +74,31 @@ void SNTPComponent::update() { #endif } void SNTPComponent::loop() { +// The loop is used to infer whether we have valid time on platforms where we +// cannot tell whether SNTP has succeeded. +// One limitation of this approach is that we cannot tell if it was the SNTP +// component that set the time. +// ESP-IDF and ESP8266 use callbacks from the SNTP task to trigger the +// `on_time_sync` trigger on successful sync events. +#if defined(USE_ESP32) || defined(USE_ESP8266) + this->disable_loop(); +#endif + if (this->has_time_) return; + this->time_synced(); +} + +void SNTPComponent::time_synced() { auto time = this->now(); - if (!time.is_valid()) + this->has_time_ = time.is_valid(); + if (!this->has_time_) return; ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); - this->has_time_ = true; - -#ifdef USE_ESP_IDF - // On ESP-IDF, time sync is permanent and update() doesn't force resync - // Time is now synchronized, no need to check anymore - this->disable_loop(); -#endif } } // namespace sntp diff --git a/esphome/components/sntp/sntp_component.h b/esphome/components/sntp/sntp_component.h index a4e8267383..dd4c71e082 100644 --- a/esphome/components/sntp/sntp_component.h +++ b/esphome/components/sntp/sntp_component.h @@ -26,9 +26,16 @@ class SNTPComponent : public time::RealTimeClock { void update() override; void loop() override; + void time_synced(); + protected: std::vector servers_; bool has_time_{false}; + +#if defined(USE_ESP32) + private: + static SNTPComponent *instance; +#endif }; } // namespace sntp