[zigbee] add on_join trigger for esp32 (#16060)
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Check import esphome.__main__ time (push) Has been cancelled
CI / Test downstream esphome/device-builder (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (${{ matrix.bucket.name }}) (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / Test components with native ESP-IDF (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04-arm) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04-arm) (push) Has been cancelled

Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
luar123
2026-05-11 17:54:35 +02:00
committed by GitHub
parent a52ca4f80a
commit ee8ca2a3bf
9 changed files with 51 additions and 29 deletions
+11 -6
View File
@@ -79,10 +79,7 @@ CONFIG_SCHEMA = cv.All(
cv.string, cv.Length(max=31) cv.string, cv.Length(max=31)
), ),
cv.Optional(CONF_ROUTER, default=False): cv.boolean, cv.Optional(CONF_ROUTER, default=False): cv.boolean,
cv.Optional(CONF_ON_JOIN): cv.All( cv.Optional(CONF_ON_JOIN): automation.validate_automation({}),
cv.requires_component("nrf52"),
automation.validate_automation(single=True),
),
cv.OnlyWith(CONF_WIPE_ON_BOOT, "nrf52", default=False): cv.All( cv.OnlyWith(CONF_WIPE_ON_BOOT, "nrf52", default=False): cv.All(
cv.Any( cv.Any(
cv.boolean, 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) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config: ConfigType) -> None: async def to_code(config: ConfigType) -> None:
cg.add_define("USE_ZIGBEE") cg.add_define("USE_ZIGBEE")
var = None
if CORE.using_zephyr: if CORE.using_zephyr:
from .zigbee_zephyr import zephyr_to_code from .zigbee_zephyr import zephyr_to_code
await zephyr_to_code(config) var = await zephyr_to_code(config)
if CORE.is_esp32: if CORE.is_esp32:
from .zigbee_esp32 import esp32_to_code 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: async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
@@ -26,7 +26,7 @@ void ZigbeeTime::setup() {
global_time = this; global_time = this;
this->parent_->add_callback(this->endpoint_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); }); this->parent_->add_callback(this->endpoint_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); });
synchronize_epoch_(EPOCH_2000); 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() { void ZigbeeTime::dump_config() {
+14 -2
View File
@@ -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 "); ESP_LOGD(TAG, "Device started up in %sfactory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non ");
global_zigbee->started = true; global_zigbee->started = true;
if (esp_zb_bdb_is_factory_new()) { if (esp_zb_bdb_is_factory_new()) {
global_zigbee->factory_new = true;
ESP_LOGD(TAG, "Start network steering"); ESP_LOGD(TAG, "Start network steering");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else { } else {
ESP_LOGD(TAG, "Device rebooted"); ESP_LOGD(TAG, "Device rebooted");
global_zigbee->connected = true; global_zigbee->joined = true;
global_zigbee->enable_loop_soon_any_context();
} }
} else { } else {
ESP_LOGE(TAG, "FIRST_START. Device started up in %sfactory-reset mode with an error %d (%s)", 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; steering_retry_count = 0;
ESP_LOGI(TAG, "Joined network successfully (PAN ID: 0x%04hx, Channel:%d)", esp_zb_get_pan_id(), ESP_LOGI(TAG, "Joined network successfully (PAN ID: 0x%04hx, Channel:%d)", esp_zb_get_pan_id(),
esp_zb_get_current_channel()); esp_zb_get_current_channel());
global_zigbee->connected = true; global_zigbee->joined = true;
global_zigbee->enable_loop_soon_any_context();
} else { } else {
ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status)); ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status));
if (steering_retry_count < 10) { if (steering_retry_count < 10) {
@@ -283,6 +286,15 @@ void ZigbeeComponent::setup() {
} }
} }
xTaskCreate(esp_zb_task_, "Zigbee_main", 4096, NULL, 24, NULL); 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() { void ZigbeeComponent::dump_config() {
+8 -2
View File
@@ -39,6 +39,7 @@ class ZigbeeAttribute;
class ZigbeeComponent : public Component { class ZigbeeComponent : public Component {
public: public:
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
esp_err_t create_endpoint(uint8_t endpoint_id, zb_ha_standard_devs_e device_id, 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); esp_zb_cluster_list_t *esp_zb_cluster_list);
@@ -59,10 +60,13 @@ class ZigbeeComponent : public Component {
esp_zb_lock_release(); esp_zb_lock_release();
} }
template<typename F> void add_on_join_callback(F &&cb) { this->join_cb_.add(std::forward<F>(cb)); }
bool is_started() { return this->started; } bool is_started() { return this->started; }
bool is_connected() { return this->connected; } bool is_connected() { return this->connected_; }
std::atomic<bool> connected = false;
std::atomic<bool> started = false; std::atomic<bool> started = false;
std::atomic<bool> joined = false;
std::atomic<bool> factory_new = false;
protected: protected:
struct { struct {
@@ -70,6 +74,7 @@ class ZigbeeComponent : public Component {
uint8_t *manufacturer; uint8_t *manufacturer;
uint8_t *date; uint8_t *date;
} basic_cluster_data_; } basic_cluster_data_;
bool connected_ = false;
#ifdef ZB_ED_ROLE #ifdef ZB_ED_ROLE
esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ED; esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ED;
#else #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 // key tuple could be replaced by single 64 (48) bit int with bit fields for endpoint, cluster, role and attr_id
std::map<std::tuple<uint8_t, uint16_t, uint8_t, uint16_t>, ZigbeeAttribute *> attributes_; std::map<std::tuple<uint8_t, uint16_t, uint8_t, uint16_t>, ZigbeeAttribute *> attributes_;
esp_zb_ep_list_t *esp_zb_ep_list_ = esp_zb_ep_list_create(); esp_zb_ep_list_t *esp_zb_ep_list_ = esp_zb_ep_list_create();
CallbackManager<void(bool)> join_cb_{};
}; };
extern "C" void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); extern "C" void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct);
+3 -1
View File
@@ -28,6 +28,7 @@ from esphome.const import (
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.coroutine import CoroPriority, coroutine_with_priority from esphome.coroutine import CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObj
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.types import ConfigType from esphome.types import ConfigType
@@ -289,7 +290,7 @@ async def attributes_to_code(
cg.add(attr_var.connect(template_arg, device)) 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( add_idf_component(
name="espressif/esp-zboss-lib", name="espressif/esp-zboss-lib",
ref="1.6.4", 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) await attributes_to_code(var, ep[CONF_NUM], cl)
return var
+5 -6
View File
@@ -40,7 +40,7 @@ void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) {
case ZB_BDB_SIGNAL_DEVICE_REBOOT: case ZB_BDB_SIGNAL_DEVICE_REBOOT:
ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status); ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status);
if (status == RET_OK) { if (status == RET_OK) {
on_join_(); on_join_(false);
} }
break; break;
case ZB_BDB_SIGNAL_STEERING: 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) { for (int i = 0; i < addr_len; ++i) {
if (ieee_addr_buf[i] != '0') { if (ieee_addr_buf[i] != '0') {
on_join_(); on_join_(true);
break; break;
} }
} }
@@ -130,11 +130,10 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) {
p_device_cb_param->status = RET_NOT_IMPLEMENTED; p_device_cb_param->status = RET_NOT_IMPLEMENTED;
} }
void ZigbeeComponent::on_join_() { void ZigbeeComponent::on_join_(bool factory_new) {
this->defer([this]() { this->defer([this, factory_new]() {
ESP_LOGD(TAG, "Joined the network"); ESP_LOGD(TAG, "Joined the network");
this->join_trigger_.trigger(); this->join_cb_.call(factory_new);
this->join_cb_.call();
}); });
} }
+3 -5
View File
@@ -74,25 +74,23 @@ class ZigbeeComponent : public Component {
// endpoints are enumerated from 1 // endpoints are enumerated from 1
this->callbacks_[endpoint - 1] = std::move(cb); this->callbacks_[endpoint - 1] = std::move(cb);
} }
template<typename F> void add_join_callback(F &&cb) { this->join_cb_.add(std::forward<F>(cb)); } template<typename F> void add_on_join_callback(F &&cb) { this->join_cb_.add(std::forward<F>(cb)); }
void zboss_signal_handler_esphome(zb_bufid_t bufid); 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 after_reporting_info(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info);
void factory_reset(); void factory_reset();
Trigger<> *get_join_trigger() { return &this->join_trigger_; };
void force_report(); void force_report();
void loop() override; void loop() override;
void set_sleepy(bool sleepy) { this->sleepy_ = sleepy; } void set_sleepy(bool sleepy) { this->sleepy_ = sleepy; }
protected: protected:
static void zcl_device_cb(zb_bufid_t bufid); static void zcl_device_cb(zb_bufid_t bufid);
void on_join_(); void on_join_(bool factory_new);
#ifdef USE_ZIGBEE_WIPE_ON_BOOT #ifdef USE_ZIGBEE_WIPE_ON_BOOT
void erase_flash_(int area); void erase_flash_(int area);
#endif #endif
void dump_reporting_(); void dump_reporting_();
std::array<std::function<void(zb_bufid_t bufid)>, ZIGBEE_ENDPOINTS_COUNT> callbacks_{}; std::array<std::function<void(zb_bufid_t bufid)>, ZIGBEE_ENDPOINTS_COUNT> callbacks_{};
CallbackManager<void()> join_cb_; CallbackManager<void(bool)> join_cb_;
Trigger<> join_trigger_;
bool force_report_{false}; bool force_report_{false};
uint32_t sleep_time_{}; uint32_t sleep_time_{};
uint32_t sleep_remainder_{}; uint32_t sleep_remainder_{};
+3 -6
View File
@@ -1,7 +1,6 @@
import datetime import datetime
import random import random
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.zephyr import zephyr_add_prj_conf from esphome.components.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv import esphome.config_validation as cv
@@ -23,7 +22,6 @@ from esphome.types import ConfigType
from .const import ( from .const import (
BACNET_UNIT_NO_UNITS, BACNET_UNIT_NO_UNITS,
BACNET_UNITS, BACNET_UNITS,
CONF_ON_JOIN,
CONF_POWER_SOURCE, CONF_POWER_SOURCE,
CONF_ROUTER, CONF_ROUTER,
CONF_WIPE_ON_BOOT, 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", True)
zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True) zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True)
if config[CONF_ROUTER]: if config[CONF_ROUTER]:
@@ -141,15 +139,14 @@ async def zephyr_to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID]) 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) await cg.register_component(var, config)
CORE.add_job(_ctx_to_code, config) CORE.add_job(_ctx_to_code, config)
cg.add(var.set_sleepy(config[CONF_SLEEPY])) cg.add(var.set_sleepy(config[CONF_SLEEPY]))
return var
async def _attr_to_code(config: ConfigType) -> None: async def _attr_to_code(config: ConfigType) -> None:
# Create the basic attributes structure and attribute list # Create the basic attributes structure and attribute list
@@ -12,3 +12,6 @@ binary_sensor:
zigbee: zigbee:
model: zigbee_test model: zigbee_test
router: true router: true
on_join:
then:
- logger.log: "Joined network"