From 94894d60b77e36f7c6e72dc74116b175fbbb7b4b Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 12 Oct 2026 00:15:23 +0200 Subject: [PATCH] [nrf52] fix zigbee time component (#20527) Co-authored-by: Claude --- esphome/components/time/__init__.py | 8 +++++++ esphome/components/zigbee/time/__init__.py | 2 ++ .../zigbee/time/zigbee_time_zephyr.cpp | 22 ++++++++++++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index a017109e7d3..2ad3cd9c314 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -26,6 +26,8 @@ from esphome.const import ( CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, + KEY_CORE, + KEY_FRAMEWORK_VERSION, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -455,6 +457,12 @@ async def register_time(time_var, config): async def to_code(config): if CORE.using_zephyr: zephyr_add_prj_conf("POSIX_CLOCK", True) + # From sdk-nrf 2.8.0 newlib time() gets the clock through gettimeofday(), + # which Zephyr only provides with XSI_SINGLE_PROCESS. Without it time() + # always returns -1. + if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(2, 8, 0): + zephyr_add_prj_conf("POSIX_SINGLE_PROCESS", True) + zephyr_add_prj_conf("XSI_SINGLE_PROCESS", True) cg.add_define("USE_TIME") cg.add_global(time_ns.using) diff --git a/esphome/components/zigbee/time/__init__.py b/esphome/components/zigbee/time/__init__.py index 74f81df2e96..0572e591cfc 100644 --- a/esphome/components/zigbee/time/__init__.py +++ b/esphome/components/zigbee/time/__init__.py @@ -14,6 +14,7 @@ from ..zigbee_zephyr import ( ZigbeeClusterDesc, ZigbeeComponent, get_slot_index, + zigbee_assign, zigbee_new_attr_list, zigbee_new_cluster_list, zigbee_new_variable, @@ -81,6 +82,7 @@ async def _add_time_zephyr(config: ConfigType) -> None: # Create the binary attributes structure time_attrs = zigbee_new_variable(attrs_name, "zb_zcl_time_attrs_t") + zigbee_assign(time_attrs.time, cg.RawExpression("ZB_ZCL_TIME_TIME_INVALID_VALUE")) attr_list = zigbee_new_attr_list( attr_list_name, "ZB_ZCL_DECLARE_TIME_ATTR_LIST", diff --git a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp index 7f0a5f979ce..0de0ee0ee48 100644 --- a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp +++ b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp @@ -1,6 +1,7 @@ #include "zigbee_time_zephyr.h" #if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_TIME) #include "esphome/core/log.h" +#include namespace esphome::zigbee { @@ -9,12 +10,14 @@ ESPHOME_LOG_TAG(TAG, "zigbee.time"); // This time standard is the number of // seconds since 0 hrs 0 mins 0 sec on 1st January 2000 UTC (Universal Coordinated Time). constexpr time_t EPOCH_2000 = 946684800; +// Largest ZCL time that still fits the 32-bit epoch. Also rejects ZB_ZCL_TIME_TIME_INVALID_VALUE. +constexpr uint32_t MAX_ZCL_TIME = std::numeric_limits::max() - EPOCH_2000; ZigbeeTime *global_time = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void ZigbeeTime::sync_time(zb_ret_t status, zb_uint32_t auth_level, zb_uint16_t short_addr, zb_uint8_t endpoint, zb_uint32_t nw_time) { - if (status == RET_OK && auth_level >= ZB_ZCL_TIME_HAS_SYNCHRONIZED_BIT) { + if (status == RET_OK && auth_level >= ZB_ZCL_TIME_HAS_SYNCHRONIZED_BIT && nw_time <= MAX_ZCL_TIME) { global_time->set_epoch_time(nw_time + EPOCH_2000); } else if (status != RET_TIMEOUT || !global_time->has_time_) { ESP_LOGE(TAG, "Status: %d, auth_level: %u, short_addr: %d, endpoint: %d, nw_time: %u", status, auth_level, @@ -25,7 +28,6 @@ void ZigbeeTime::sync_time(zb_ret_t status, zb_uint32_t auth_level, zb_uint16_t void ZigbeeTime::setup() { global_time = this; this->parent_->add_callback(this->endpoint_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); }); - synchronize_epoch_(EPOCH_2000); this->parent_->add_on_join_callback([this](bool x) { zb_zcl_time_server_synchronize(this->endpoint_, sync_time); }); } @@ -38,8 +40,12 @@ void ZigbeeTime::dump_config() { } void ZigbeeTime::update() { - time_t time = timestamp_now(); - this->cluster_attributes_->time = time - EPOCH_2000; + const time_t now = this->timestamp_now(); + if (now < EPOCH_2000) { + this->cluster_attributes_->time = ZB_ZCL_TIME_TIME_INVALID_VALUE; + } else { + this->cluster_attributes_->time = static_cast(now - EPOCH_2000); + } } void ZigbeeTime::set_epoch_time(uint32_t epoch) { @@ -61,8 +67,12 @@ void ZigbeeTime::zcl_device_cb_(zb_bufid_t bufid) { if (cluster_id == ZB_ZCL_CLUSTER_ID_TIME) { if (attr_id == ZB_ZCL_ATTR_TIME_TIME_ID) { zb_uint32_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data32; - ESP_LOGI(TAG, "Synchronize time to %u", value); - this->defer([this, value]() { synchronize_epoch_(value + EPOCH_2000); }); + if (value <= MAX_ZCL_TIME) { + ESP_LOGI(TAG, "Synchronize time to %u", value); + this->defer([this, value]() { this->synchronize_epoch_(value + EPOCH_2000); }); + } else { + ESP_LOGW(TAG, "Ignoring invalid time %u", value); + } } else if (attr_id == ZB_ZCL_ATTR_TIME_TIME_STATUS_ID) { zb_uint8_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data8; ESP_LOGI(TAG, "Time status %hd", value);