ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
zigbee_esp32.cpp
Go to the documentation of this file.
2#ifdef USE_ESP32
3#ifdef USE_ZIGBEE
4
5#include "freertos/FreeRTOS.h"
6#include "freertos/task.h"
7#include "esp_check.h"
8#include "nvs_flash.h"
10#include "zigbee_esp32.h"
12#include "esphome/core/log.h"
14#ifdef USE_WIFI
15#include "esp_coexist.h"
16#endif
17
18namespace esphome::zigbee {
19
20static const char *const TAG = "zigbee";
21
22static ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
23
24uint8_t *get_zcl_string(const char *str, uint8_t max_size, bool use_max_size) {
25 uint8_t str_len = static_cast<uint8_t>(strlen(str));
26 uint8_t zcl_str_size = use_max_size ? max_size : std::min(max_size, str_len);
27 uint8_t *zcl_str = new uint8_t[zcl_str_size + 1]; // string + length octet
28 zcl_str[0] = zcl_str_size;
29
30 // Initialize payload to avoid leaking uninitialized heap contents and clamp copy length
31 memset(zcl_str + 1, 0, zcl_str_size);
32 uint8_t copy_len = std::min(zcl_str_size, str_len);
33 if (copy_len > 0) {
34 memcpy(zcl_str + 1, str, copy_len);
35 }
36 return zcl_str;
37}
38
40 if (!esp_zigbee_lock_acquire(10 / portTICK_PERIOD_MS)) {
41 global_zigbee->set_timeout("zb_init", 10, [mode]() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(mode); });
42 return;
43 }
44 if (ezb_bdb_start_top_level_commissioning(mode) != EZB_ERR_NONE) {
45 ESP_LOGE(TAG, "Start top level commissioning failed!");
46 }
47 esp_zigbee_lock_release();
48}
49
50bool ZigbeeComponent::app_signal_handler(const ezb_app_signal_t *app_signal) {
51 static uint8_t steering_retry_count = 0;
52 ezb_app_signal_type_t signal_type = ezb_app_signal_get_type(app_signal);
53 switch (signal_type) {
54 case EZB_ZDO_SIGNAL_SKIP_STARTUP:
55 ESP_LOGD(TAG, "Zigbee stack initialized");
56 ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION);
57 break;
58 case EZB_BDB_SIGNAL_DEVICE_FIRST_START:
59 case EZB_BDB_SIGNAL_DEVICE_REBOOT: {
60 ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *) ezb_app_signal_get_params(app_signal));
61 if (status == EZB_BDB_STATUS_SUCCESS) {
62 ESP_LOGD(TAG, "Device started up in %sfactory-reset mode", ezb_bdb_is_factory_new() ? "" : "non ");
63 global_zigbee->started = true;
64 if (ezb_bdb_is_factory_new()) {
65 global_zigbee->factory_new = true;
66 ESP_LOGD(TAG, "Start network steering");
67 ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
68 } else {
69 ESP_LOGD(TAG, "Device rebooted");
70 global_zigbee->joined = true;
71 global_zigbee->enable_loop_soon_any_context();
72 }
73 } else {
74 ESP_LOGW(TAG, "The %s failed with status(0x%02x), please retry", ezb_app_signal_to_string(signal_type), status);
75 global_zigbee->set_timeout("zb_init", 1000, []() {
76 ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_INITIALIZATION);
77 });
78 }
79 } break;
80 case EZB_BDB_SIGNAL_STEERING: {
81 ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *) ezb_app_signal_get_params(app_signal));
82 if (status == EZB_BDB_STATUS_SUCCESS) {
83 steering_retry_count = 0;
84 ezb_extpanid_t extended_pan_id;
85 ezb_nwk_get_extended_panid(&extended_pan_id);
86 ESP_LOGD(TAG, "Joined network successfully: PAN ID(0x%04hx, EXT: 0x%llx), Channel(%d), Short Address(0x%04hx)",
87 ezb_nwk_get_panid(), extended_pan_id.u64, ezb_nwk_get_current_channel(), ezb_nwk_get_short_address());
88 global_zigbee->joined = true;
89 global_zigbee->enable_loop_soon_any_context();
90 } else {
91 ESP_LOGD(TAG, "Failed to join network with status(0x%02x)", status);
92 if (steering_retry_count < 10) {
93 steering_retry_count++;
94 global_zigbee->set_timeout("zb_init", 1000, []() {
95 ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
96 });
97 } else {
98 global_zigbee->set_timeout("zb_init", 600 * 1000, []() {
99 ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
100 });
101 }
102 }
103 } break;
104 case EZB_ZDO_SIGNAL_LEAVE: {
105 const ezb_zdo_signal_leave_params_t *leave_params =
106 (const ezb_zdo_signal_leave_params_t *) ezb_app_signal_get_params(app_signal);
107 if (leave_params->leave_type == EZB_ZDO_LEAVE_TYPE_RESET) {
108 esp_zigbee_factory_reset();
109 }
110 } break;
111 default:
112 ESP_LOGD(TAG, "Zigbee APP Signal: %s(type: 0x%02x)", ezb_app_signal_to_string(signal_type), signal_type);
113 break;
114 }
115 return true;
116}
117
118static void zb_attribute_handler(ezb_zcl_set_attr_value_message_t *message) {
119 ESP_RETURN_ON_FALSE(message, , TAG, "Empty message");
120 ESP_RETURN_ON_FALSE(message->info.status == EZB_ZCL_STATUS_SUCCESS, , TAG, "Received message: error status(%d)",
121 message->info.status);
122 ESP_LOGD(TAG, "ZCL SetAttributeValue message for endpoint(%d) cluster(0x%04x) %s with status(0x%02x)",
123 message->info.dst_ep, message->info.cluster_id,
124 message->info.cluster_role == EZB_ZCL_CLUSTER_SERVER ? "server" : "client", message->info.status);
125}
126
127static void zb_action_handler(ezb_zcl_core_action_callback_id_t callback_id, void *message) {
128 switch (callback_id) {
129 case EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID:
130 zb_attribute_handler((ezb_zcl_set_attr_value_message_t *) message);
131 break;
132 case EZB_ZCL_CORE_DEFAULT_RSP_CB_ID: {
133#ifdef ESPHOME_LOG_HAS_VERBOSE
134 ezb_zcl_cmd_default_rsp_message_t *default_rsp = (ezb_zcl_cmd_default_rsp_message_t *) message;
135 ESP_LOGV(TAG, "Received ZCL Default Response: 0x%02x", default_rsp->in.status_code);
136#endif
137 } break;
138 default:
139 ESP_LOGD(TAG, "Receive Zigbee action(0x%04x) callback", static_cast<unsigned>(callback_id));
140 break;
141 }
142}
143
144void ZigbeeComponent::create_default_cluster(uint8_t endpoint_id, uint16_t device_id) {
145 ezb_af_ep_config_t config = {
146 .ep_id = endpoint_id,
147 .app_profile_id = EZB_AF_HA_PROFILE_ID,
148 .app_device_id = device_id,
149 .app_device_version = 0,
150 };
151 ezb_af_ep_desc_t ep_desc = ezb_af_create_endpoint_desc(&config);
152 if (ezb_af_device_add_endpoint_desc(this->dev_desc_, ep_desc) != EZB_ERR_NONE) {
153 ESP_LOGE(TAG, "Could not create endpoint %u", endpoint_id);
154 }
155 // Add basic cluster
156 this->update_basic_cluster_(ep_desc);
157 // Add identify cluster if not already present
158 this->add_cluster(endpoint_id, EZB_ZCL_CLUSTER_ID_IDENTIFY, EZB_ZCL_CLUSTER_SERVER);
159}
160
161void ZigbeeComponent::add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role) {
162 if (cluster_id == EZB_ZCL_CLUSTER_ID_BASIC) {
163 return;
164 }
165 ezb_af_ep_desc_t ep_desc = ezb_af_device_get_endpoint_desc(this->dev_desc_, endpoint_id);
166 if (ep_desc == NULL) {
167 ESP_LOGE(TAG, "Endpoint %u does not exist, cannot add cluster 0x%04X", endpoint_id, cluster_id);
168 return;
169 }
170 esphome_zb_add_or_update_cluster(cluster_id, ep_desc, role);
171 ESP_LOGD(TAG, "Endpoint %u: Added cluster 0x%04X with role %u", endpoint_id, cluster_id, role);
172}
173
174void ZigbeeComponent::set_basic_cluster(const char *model, const char *manufacturer, uint8_t power_source) {
175 char date_buf[16];
176 time_t time_val = App.get_build_time();
177 struct tm *timeinfo = localtime(&time_val);
178 strftime(date_buf, sizeof(date_buf), "%Y%m%d %H%M%S", timeinfo);
179 this->basic_cluster_data_ = {
180 .model = get_zcl_string(model, 31),
181 .manufacturer = get_zcl_string(manufacturer, 31),
182 .date = get_zcl_string(date_buf, 15),
183 .power_source = power_source,
184 };
185}
186
187void ZigbeeComponent::update_basic_cluster_(ezb_af_ep_desc_t ep_desc) {
188 ezb_zcl_cluster_desc_t cluster_desc =
189 ezb_af_endpoint_get_cluster_desc(ep_desc, EZB_ZCL_CLUSTER_ID_BASIC, EZB_ZCL_CLUSTER_SERVER);
190 if (cluster_desc == NULL) {
191 ezb_zcl_basic_cluster_config_t basic_cluster_cfg = {
192 .zcl_version = EZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE,
193 .power_source = this->basic_cluster_data_.power_source,
194 };
195 cluster_desc = ezb_zcl_basic_create_cluster_desc(&basic_cluster_cfg, EZB_ZCL_CLUSTER_SERVER);
196 }
197 ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID,
198 this->basic_cluster_data_.manufacturer);
199 ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID,
200 this->basic_cluster_data_.model);
201 ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_DATE_CODE_ID, this->basic_cluster_data_.date);
202 ezb_af_endpoint_add_cluster_desc(ep_desc, cluster_desc);
203}
204
206 if (ezb_af_device_desc_register(this->dev_desc_) != EZB_ERR_NONE) {
207 ESP_LOGE(TAG, "Could not register the endpoint list");
208 this->mark_failed();
209 return false;
210 }
211 return true;
212}
213
214static void ezb_task(void *pv_parameters) {
215 if (!global_zigbee->register_device()) {
216 vTaskDelete(NULL);
217 return;
218 }
219 if (esp_zigbee_start(false) != ESP_OK) {
220 ESP_LOGE(TAG, "Could not setup Zigbee");
221 global_zigbee->mark_failed();
222 vTaskDelete(NULL);
223 return; // vTaskDelete(NULL) never returns, but keep intent explicit
224 }
225
226 // Increase priority to 5 to align with openthread or BLE
227 vTaskPrioritySet(NULL, 5);
228
229 esp_zigbee_launch_mainloop();
230
231 esp_zigbee_deinit();
232
233 vTaskDelete(NULL);
234}
235
237 esp_zigbee_platform_config_t platform_config = {
238 .storage_partition_name = "nvs",
239 .radio_config = EZB_DEFAULT_RADIO_CONFIG(),
240 };
241 esp_zigbee_device_config_t device_config = {
242 .device_type = this->device_role_,
243 .install_code_policy = false,
244 };
245#ifdef CONFIG_ZB_ZCZR
246 esp_zigbee_zczr_config_s zb_zczr_cfg = {
247 .max_children = MAX_CHILDREN,
248 };
249 device_config.zczr_config = zb_zczr_cfg;
250#else
251 esp_zigbee_zed_config_s zb_zed_cfg = {
252 .ed_timeout = EZB_NWK_ED_TIMEOUT_64MIN,
253 .keep_alive = ED_KEEP_ALIVE,
254 };
255 device_config.zed_config = zb_zed_cfg;
256#endif
257 esp_zigbee_config_t config = {.device_config = device_config, .platform_config = platform_config};
258 if (esp_zigbee_init(&config) != ESP_OK) {
259 ESP_LOGE(TAG, "Could not initialize Zigbee");
260 this->mark_failed();
261 return;
262 }
263 this->dev_desc_ = ezb_af_create_device_desc();
264}
265
267 global_zigbee = this;
268#ifdef USE_WIFI
269 if (esp_coex_wifi_i154_enable() != ESP_OK) {
270 this->mark_failed();
271 return;
272 }
273#endif
274 ezb_aps_secur_enable_distributed_security(false);
275 ezb_nwk_set_min_join_lqi(32);
276 if (ezb_app_signal_add_handler(ZigbeeComponent::app_signal_handler) != ESP_OK) {
277 ESP_LOGE(TAG, "Could not set application signal handler");
278 this->mark_failed();
279 return;
280 }
281
282 ezb_zcl_core_action_handler_register(zb_action_handler);
283
284 if (ezb_bdb_set_primary_channel_set(EZB_PRIMARY_CHANNEL_MASK) != ESP_OK) {
285 ESP_LOGE(TAG, "Could not setup Zigbee");
286 this->mark_failed();
287 return;
288 }
289
290 uint8_t power_source = static_cast<uint8_t>(this->is_battery_powered() ? EZB_AF_NODE_POWER_SOURCE_RECHARGEABLE_BATTERY
291 : EZB_AF_NODE_POWER_SOURCE_CONSTANT_POWER);
292 ezb_af_node_power_desc_t desc = {
293 .current_power_mode = EZB_AF_NODE_POWER_MODE_SYNC_ON_WHEN_IDLE,
294 .available_power_sources = power_source,
295 .current_power_source = power_source,
296 .current_power_source_level = EZB_AF_NODE_POWER_SOURCE_LEVEL_100_PERCENT,
297 };
298 ezb_af_set_node_power_desc(&desc);
299
300 // Start the Zigbee task with priority 1 to ensure main loop can still run even if Zigbee is busy
301 xTaskCreate(ezb_task, "Zigbee_main", 4096, NULL, 1, NULL);
302 this->disable_loop(); // loop is only needed for processing events, so disable until we join a network
303}
304
306 if (this->joined.exchange(false)) {
307 this->connected_ = true;
308 this->join_cb_.call(this->factory_new);
309 }
310 this->disable_loop();
311}
312
314 if (esp_zigbee_lock_acquire(10 / portTICK_PERIOD_MS)) {
315 ESP_LOGCONFIG(TAG,
316 "Zigbee\n"
317 " Model: %.*s\n"
318 " Router: %s\n"
319 " Device is joined to the network: %s\n"
320 " Current channel: %d\n"
321 " Short addr: 0x%04X\n"
322 " Short pan id: 0x%04X",
323 this->basic_cluster_data_.model[0],
324 reinterpret_cast<const char *>(this->basic_cluster_data_.model + 1),
325 YESNO(this->device_role_ == EZB_NWK_DEVICE_TYPE_ROUTER), YESNO(ezb_bdb_dev_joined()),
326 ezb_nwk_get_current_channel(), ezb_nwk_get_short_address(), ezb_nwk_get_panid());
327 esp_zigbee_lock_release();
328 } else {
329 ESP_LOGCONFIG(TAG,
330 "Zigbee\n"
331 " Model: %.*s\n"
332 " Router: %s\n",
333 this->basic_cluster_data_.model[0],
334 reinterpret_cast<const char *>(this->basic_cluster_data_.model + 1),
335 YESNO(this->device_role_ == EZB_NWK_DEVICE_TYPE_ROUTER));
336 }
337}
338} // namespace esphome::zigbee
339
340#endif
341#endif
BedjetMode mode
BedJet operating mode.
uint8_t status
Definition bl0942.h:8
time_t get_build_time()
Get the build time as a Unix timestamp.
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void disable_loop()
Disable this component's loop.
CallbackManager< void(bool)> join_cb_
void create_default_cluster(uint8_t endpoint_id, uint16_t device_id)
struct esphome::zigbee::ZigbeeComponent::@200 basic_cluster_data_
void update_basic_cluster_(ezb_af_ep_desc_t ep_desc)
void add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role)
static void esp_zigbee_alarm_bdb_commissioning(ezb_bdb_comm_mode_mask_t mode)
static bool app_signal_handler(const ezb_app_signal_t *app_signal)
std::atomic< bool > factory_new
ezb_nwk_device_type_t device_role_
void set_basic_cluster(const char *model, const char *manufacturer, uint8_t power_source)
ezb_af_device_desc_t dev_desc_
const LogString * message
Definition component.cpp:35
const char *const TAG
Definition spi.cpp:7
uint8_t * get_zcl_string(const char *str, uint8_t max_size, bool use_max_size)
Application App
Global storage of Application pointer - only one Application can exist.
struct tm * localtime(const time_t *timer)
Definition posix_tz.cpp:501
ezb_err_t esphome_zb_add_or_update_cluster(uint16_t cluster_id, ezb_af_ep_desc_t ep_desc, uint8_t role_mask)