From 017040ec8e9111c4542fa44eb1ce4869283205ac Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sat, 18 Jul 2026 09:09:52 +0200 Subject: [PATCH] [zigbee] add radio power off stats (#17521) --- esphome/components/zigbee/zigbee_zephyr.cpp | 45 +++++++++++++++++++-- esphome/components/zigbee/zigbee_zephyr.h | 3 ++ esphome/components/zigbee/zigbee_zephyr.py | 8 ++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp index 81aad7dcb1..fedcb4a9c2 100644 --- a/esphome/components/zigbee/zigbee_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -229,6 +229,7 @@ void ZigbeeComponent::dump_config() { " Wipe on boot: %s\n" " Device is joined to the network: %s\n" " Sleep time: %us\n" + " Radio sleep time: %us\n" " RX ON when idle: %s\n" " Current channel: %d\n" " Current page: %d\n" @@ -238,9 +239,10 @@ void ZigbeeComponent::dump_config() { " Short addr: 0x%04X\n" " Long pan id: 0x%s\n" " Short pan id: 0x%04X", - get_wipe_on_boot(), YESNO(zb_zdo_joined()), this->sleep_time_, YESNO(zb_get_rx_on_when_idle()), - zb_get_current_channel(), zb_get_current_page(), zb_get_sleep_threshold(), role(), ieee_addr_buf, - zb_get_short_address(), extended_pan_id_buf, zb_get_pan_id()); + get_wipe_on_boot(), YESNO(zb_zdo_joined()), this->sleep_time_, this->radio_sleep_time_, + YESNO(zb_get_rx_on_when_idle()), zb_get_current_channel(), zb_get_current_page(), + zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf, + zb_get_pan_id()); dump_reporting_(); } @@ -251,6 +253,13 @@ static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { void ZigbeeComponent::force_report() { this->force_report_ = true; } +void ZigbeeComponent::add_radio_sleep_time_ms(uint32_t ms) { + this->radio_sleep_remainder_ += ms; + uint32_t seconds = this->radio_sleep_remainder_ / 1000; + this->radio_sleep_remainder_ -= seconds * 1000; + this->radio_sleep_time_ += seconds; +} + void ZigbeeComponent::loop() { if (this->force_report_) { this->force_report_ = false; @@ -327,6 +336,36 @@ zb_ret_t __wrap_zb_zcl_put_reporting_info_from_req(zb_zcl_configure_reporting_re esphome::zigbee::global_zigbee->after_reporting_info(config_rep_req, attr_addr_info); return ret; } + +extern void __real_zb_trans_enter_sleep(void); +extern void __real_zb_trans_enter_receive(void); +extern zb_bool_t __real_zb_trans_transmit(zb_uint8_t wait_type, zb_time_t tx_at, zb_uint8_t *tx_buf, + zb_uint8_t current_channel); + +static uint32_t radio_sleep_start_ms = 0; + +static void stop_radio_sleep_timer() { + if (radio_sleep_start_ms) { + esphome::zigbee::global_zigbee->add_radio_sleep_time_ms(esphome::millis() - radio_sleep_start_ms); + } + radio_sleep_start_ms = 0; +} + +void __wrap_zb_trans_enter_sleep(void) { + __real_zb_trans_enter_sleep(); + radio_sleep_start_ms = esphome::millis(); +} + +void __wrap_zb_trans_enter_receive(void) { + stop_radio_sleep_timer(); + __real_zb_trans_enter_receive(); +} + +zb_bool_t __wrap_zb_trans_transmit(zb_uint8_t wait_type, zb_time_t tx_at, zb_uint8_t *tx_buf, + zb_uint8_t current_channel) { + stop_radio_sleep_timer(); + return __real_zb_trans_transmit(wait_type, tx_at, tx_buf, current_channel); +} // NOLINTEND(readability-identifier-naming,bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) } #endif diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h index 3b4a465361..8528aebff8 100644 --- a/esphome/components/zigbee/zigbee_zephyr.h +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -81,6 +81,7 @@ class ZigbeeComponent final : public Component { void force_report(); void loop() override; void set_sleepy(bool sleepy) { this->sleepy_ = sleepy; } + void add_radio_sleep_time_ms(uint32_t ms); protected: static void zcl_device_cb(zb_bufid_t bufid); @@ -94,6 +95,8 @@ class ZigbeeComponent final : public Component { bool force_report_{false}; uint32_t sleep_time_{}; uint32_t sleep_remainder_{}; + uint32_t radio_sleep_time_{}; + uint32_t radio_sleep_remainder_{}; bool sleepy_{}; }; diff --git a/esphome/components/zigbee/zigbee_zephyr.py b/esphome/components/zigbee/zigbee_zephyr.py index 1647fb28ae..f47cf6bd40 100644 --- a/esphome/components/zigbee/zigbee_zephyr.py +++ b/esphome/components/zigbee/zigbee_zephyr.py @@ -117,6 +117,14 @@ async def zephyr_to_code(config: ConfigType) -> "MockObj": cg.add_build_flag("-Wl,--wrap=zb_zcl_put_reporting_info_from_req") + # Wrap the transceiver sleep/receive/transmit calls to measure how long the + # radio is powered down. The span between a zb_trans_enter_sleep() and the + # following zb_trans_enter_receive() or zb_trans_transmit() is time the + # radio spent asleep. + cg.add_build_flag("-Wl,--wrap=zb_trans_enter_sleep") + cg.add_build_flag("-Wl,--wrap=zb_trans_enter_receive") + cg.add_build_flag("-Wl,--wrap=zb_trans_transmit") + if CONF_IEEE802154_VENDOR_OUI in config: zephyr_add_prj_conf("IEEE802154_VENDOR_OUI_ENABLE", True) random_number = config[CONF_IEEE802154_VENDOR_OUI]