diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py index a7e9d1096f7..286b395e183 100644 --- a/esphome/components/zigbee/__init__.py +++ b/esphome/components/zigbee/__init__.py @@ -79,10 +79,7 @@ CONFIG_SCHEMA = cv.All( cv.string, cv.Length(max=31) ), cv.Optional(CONF_ROUTER, default=False): cv.boolean, - cv.Optional(CONF_ON_JOIN): cv.All( - cv.requires_component("nrf52"), - automation.validate_automation(single=True), - ), + cv.Optional(CONF_ON_JOIN): automation.validate_automation({}), cv.OnlyWith(CONF_WIPE_ON_BOOT, "nrf52", default=False): cv.All( cv.Any( cv.boolean, @@ -146,17 +143,25 @@ FINAL_VALIDATE_SCHEMA = cv.All( ) +_CALLBACK_AUTOMATIONS = [ + automation.CallbackAutomation(CONF_ON_JOIN, "add_on_join_callback", [(bool, "x")]), +] + + @coroutine_with_priority(CoroPriority.CORE) async def to_code(config: ConfigType) -> None: cg.add_define("USE_ZIGBEE") + var = None if CORE.using_zephyr: from .zigbee_zephyr import zephyr_to_code - await zephyr_to_code(config) + var = await zephyr_to_code(config) if CORE.is_esp32: from .zigbee_esp32 import esp32_to_code - await esp32_to_code(config) + var = await esp32_to_code(config) + if var is not None: + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: diff --git a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp index 70ceb60abee..92d238629a1 100644 --- a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp +++ b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp @@ -26,7 +26,7 @@ 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_join_callback([this]() { zb_zcl_time_server_synchronize(this->endpoint_, sync_time); }); + this->parent_->add_on_join_callback([this](bool x) { zb_zcl_time_server_synchronize(this->endpoint_, sync_time); }); } void ZigbeeTime::dump_config() { diff --git a/esphome/components/zigbee/zigbee_esp32.cpp b/esphome/components/zigbee/zigbee_esp32.cpp index c16736236a4..733f45cc2ae 100644 --- a/esphome/components/zigbee/zigbee_esp32.cpp +++ b/esphome/components/zigbee/zigbee_esp32.cpp @@ -59,11 +59,13 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { ESP_LOGD(TAG, "Device started up in %sfactory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non "); global_zigbee->started = true; if (esp_zb_bdb_is_factory_new()) { + global_zigbee->factory_new = true; ESP_LOGD(TAG, "Start network steering"); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); } else { ESP_LOGD(TAG, "Device rebooted"); - global_zigbee->connected = true; + global_zigbee->joined = true; + global_zigbee->enable_loop_soon_any_context(); } } else { ESP_LOGE(TAG, "FIRST_START. Device started up in %sfactory-reset mode with an error %d (%s)", @@ -78,7 +80,8 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { steering_retry_count = 0; ESP_LOGI(TAG, "Joined network successfully (PAN ID: 0x%04hx, Channel:%d)", esp_zb_get_pan_id(), esp_zb_get_current_channel()); - global_zigbee->connected = true; + global_zigbee->joined = true; + global_zigbee->enable_loop_soon_any_context(); } else { ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status)); if (steering_retry_count < 10) { @@ -283,6 +286,15 @@ void ZigbeeComponent::setup() { } } xTaskCreate(esp_zb_task_, "Zigbee_main", 4096, NULL, 24, NULL); + this->disable_loop(); // loop is only needed for processing events, so disable until we join a network +} + +void ZigbeeComponent::loop() { + if (this->joined.exchange(false)) { + this->connected_ = true; + this->join_cb_.call(this->factory_new); + } + this->disable_loop(); } void ZigbeeComponent::dump_config() { diff --git a/esphome/components/zigbee/zigbee_esp32.h b/esphome/components/zigbee/zigbee_esp32.h index 80ecbfd639e..a9072f6c8d4 100644 --- a/esphome/components/zigbee/zigbee_esp32.h +++ b/esphome/components/zigbee/zigbee_esp32.h @@ -39,6 +39,7 @@ class ZigbeeAttribute; class ZigbeeComponent : public Component { public: void setup() override; + void loop() override; void dump_config() override; esp_err_t create_endpoint(uint8_t endpoint_id, zb_ha_standard_devs_e device_id, esp_zb_cluster_list_t *esp_zb_cluster_list); @@ -59,10 +60,13 @@ class ZigbeeComponent : public Component { esp_zb_lock_release(); } + template void add_on_join_callback(F &&cb) { this->join_cb_.add(std::forward(cb)); } + bool is_started() { return this->started; } - bool is_connected() { return this->connected; } - std::atomic connected = false; + bool is_connected() { return this->connected_; } std::atomic started = false; + std::atomic joined = false; + std::atomic factory_new = false; protected: struct { @@ -70,6 +74,7 @@ class ZigbeeComponent : public Component { uint8_t *manufacturer; uint8_t *date; } basic_cluster_data_; + bool connected_ = false; #ifdef ZB_ED_ROLE esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ED; #else @@ -89,6 +94,7 @@ class ZigbeeComponent : public Component { // key tuple could be replaced by single 64 (48) bit int with bit fields for endpoint, cluster, role and attr_id std::map, ZigbeeAttribute *> attributes_; esp_zb_ep_list_t *esp_zb_ep_list_ = esp_zb_ep_list_create(); + CallbackManager join_cb_{}; }; extern "C" void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); diff --git a/esphome/components/zigbee/zigbee_esp32.py b/esphome/components/zigbee/zigbee_esp32.py index 99238f758e8..5b1808ea60d 100644 --- a/esphome/components/zigbee/zigbee_esp32.py +++ b/esphome/components/zigbee/zigbee_esp32.py @@ -28,6 +28,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.coroutine import CoroPriority, coroutine_with_priority +from esphome.cpp_generator import MockObj import esphome.final_validate as fv from esphome.types import ConfigType @@ -289,7 +290,7 @@ async def attributes_to_code( cg.add(attr_var.connect(template_arg, device)) -async def esp32_to_code(config: ConfigType) -> None: +async def esp32_to_code(config: ConfigType) -> "MockObj": add_idf_component( name="espressif/esp-zboss-lib", ref="1.6.4", @@ -332,3 +333,4 @@ async def esp32_to_code(config: ConfigType) -> None: ) ) await attributes_to_code(var, ep[CONF_NUM], cl) + return var diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp index 26bef8fb174..81aad7dcb17 100644 --- a/esphome/components/zigbee/zigbee_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -40,7 +40,7 @@ void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) { case ZB_BDB_SIGNAL_DEVICE_REBOOT: ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status); if (status == RET_OK) { - on_join_(); + on_join_(false); } break; case ZB_BDB_SIGNAL_STEERING: @@ -88,7 +88,7 @@ void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) { for (int i = 0; i < addr_len; ++i) { if (ieee_addr_buf[i] != '0') { - on_join_(); + on_join_(true); break; } } @@ -130,11 +130,10 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) { p_device_cb_param->status = RET_NOT_IMPLEMENTED; } -void ZigbeeComponent::on_join_() { - this->defer([this]() { +void ZigbeeComponent::on_join_(bool factory_new) { + this->defer([this, factory_new]() { ESP_LOGD(TAG, "Joined the network"); - this->join_trigger_.trigger(); - this->join_cb_.call(); + this->join_cb_.call(factory_new); }); } diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h index 0a189ac1e04..d462d2a4031 100644 --- a/esphome/components/zigbee/zigbee_zephyr.h +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -74,25 +74,23 @@ class ZigbeeComponent : public Component { // endpoints are enumerated from 1 this->callbacks_[endpoint - 1] = std::move(cb); } - template void add_join_callback(F &&cb) { this->join_cb_.add(std::forward(cb)); } + template void add_on_join_callback(F &&cb) { this->join_cb_.add(std::forward(cb)); } void zboss_signal_handler_esphome(zb_bufid_t bufid); void after_reporting_info(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info); void factory_reset(); - Trigger<> *get_join_trigger() { return &this->join_trigger_; }; void force_report(); void loop() override; void set_sleepy(bool sleepy) { this->sleepy_ = sleepy; } protected: static void zcl_device_cb(zb_bufid_t bufid); - void on_join_(); + void on_join_(bool factory_new); #ifdef USE_ZIGBEE_WIPE_ON_BOOT void erase_flash_(int area); #endif void dump_reporting_(); std::array, ZIGBEE_ENDPOINTS_COUNT> callbacks_{}; - CallbackManager join_cb_; - Trigger<> join_trigger_; + CallbackManager join_cb_; bool force_report_{false}; uint32_t sleep_time_{}; uint32_t sleep_remainder_{}; diff --git a/esphome/components/zigbee/zigbee_zephyr.py b/esphome/components/zigbee/zigbee_zephyr.py index 033511691cc..aa16bbef538 100644 --- a/esphome/components/zigbee/zigbee_zephyr.py +++ b/esphome/components/zigbee/zigbee_zephyr.py @@ -1,7 +1,6 @@ import datetime import random -from esphome import automation import esphome.codegen as cg from esphome.components.zephyr import zephyr_add_prj_conf import esphome.config_validation as cv @@ -23,7 +22,6 @@ from esphome.types import ConfigType from .const import ( BACNET_UNIT_NO_UNITS, BACNET_UNITS, - CONF_ON_JOIN, CONF_POWER_SOURCE, CONF_ROUTER, CONF_WIPE_ON_BOOT, @@ -96,7 +94,7 @@ zephyr_number = cv.Schema( ) -async def zephyr_to_code(config: ConfigType) -> None: +async def zephyr_to_code(config: ConfigType) -> "MockObj": zephyr_add_prj_conf("ZIGBEE", True) zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True) if config[CONF_ROUTER]: @@ -141,15 +139,14 @@ async def zephyr_to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) - if on_join_config := config.get(CONF_ON_JOIN): - await automation.build_automation(var.get_join_trigger(), [], on_join_config) - await cg.register_component(var, config) CORE.add_job(_ctx_to_code, config) cg.add(var.set_sleepy(config[CONF_SLEEPY])) + return var + async def _attr_to_code(config: ConfigType) -> None: # Create the basic attributes structure and attribute list diff --git a/tests/components/zigbee/common_esp32.yaml b/tests/components/zigbee/common_esp32.yaml index 94e3f3c8c04..77e202b5235 100644 --- a/tests/components/zigbee/common_esp32.yaml +++ b/tests/components/zigbee/common_esp32.yaml @@ -12,3 +12,6 @@ binary_sensor: zigbee: model: zigbee_test router: true + on_join: + then: + - logger.log: "Joined network"