ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
zigbee_zephyr.cpp
Go to the documentation of this file.
1#include "zigbee_zephyr.h"
2#if defined(USE_ZIGBEE) && defined(USE_NRF52)
3#include "esphome/core/log.h"
4#include <zephyr/settings/settings.h>
5#include <zephyr/storage/flash_map.h>
6#include "esphome/core/hal.h"
7
8extern "C" {
9#include <zboss_api.h>
10#include <zboss_api_addons.h>
11#include <zb_nrf_platform.h>
12#include <zigbee/zigbee_app_utils.h>
13#include <zb_error_to_string.h>
14}
15
16namespace esphome::zigbee {
17
18ESPHOME_LOG_TAG(TAG, "zigbee");
19
20ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
21
22const uint8_t IEEE_ADDR_BUF_SIZE = 17;
23
25 zb_zdo_app_signal_hdr_t *sig_hndler = nullptr;
26 zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_hndler);
27 zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid);
28
29 switch (sig) {
30 case ZB_ZDO_SIGNAL_SKIP_STARTUP:
31 ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_SKIP_STARTUP, status: %d", status);
32 if (status == RET_OK) {
33 on_start_();
34 }
35 break;
36 case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY:
37 ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY, status: %d", status);
38 break;
39 case ZB_ZDO_SIGNAL_LEAVE:
40 ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_LEAVE, status: %d", status);
41 break;
42 case ZB_BDB_SIGNAL_DEVICE_REBOOT:
43 ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status);
44 if (status == RET_OK) {
45 on_join_(false);
46 }
47 break;
48 case ZB_BDB_SIGNAL_STEERING:
49 break;
50 case ZB_COMMON_SIGNAL_CAN_SLEEP:
51 ESP_LOGV(TAG, "ZB_COMMON_SIGNAL_CAN_SLEEP, status: %d", status);
52 break;
53 case ZB_BDB_SIGNAL_DEVICE_FIRST_START:
54 ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_FIRST_START, status: %d", status);
55 break;
56 case ZB_NLME_STATUS_INDICATION:
57 ESP_LOGD(TAG, "ZB_NLME_STATUS_INDICATION, status: %d", status);
58 break;
59 case ZB_BDB_SIGNAL_TC_REJOIN_DONE:
60 ESP_LOGD(TAG, "ZB_BDB_SIGNAL_TC_REJOIN_DONE, status: %d", status);
61 break;
62 default:
63 ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status);
64 break;
65 }
66
67 auto before = millis();
68 auto err = zigbee_default_signal_handler(bufid);
69 if (err != RET_OK) {
70 ESP_LOGE(TAG, "Zigbee_default_signal_handler ERROR %u [%s]", err, zb_error_to_string_get(err));
71 }
72
73 if (sig == ZB_COMMON_SIGNAL_CAN_SLEEP) {
74 this->sleep_remainder_ += millis() - before;
75 uint32_t seconds = this->sleep_remainder_ / 1000;
76 this->sleep_remainder_ -= seconds * 1000;
77 this->sleep_time_ += seconds;
78 }
79
80 switch (sig) {
81 case ZB_BDB_SIGNAL_STEERING:
82 ESP_LOGD(TAG, "ZB_BDB_SIGNAL_STEERING, status: %d", status);
83 if (status == RET_OK) {
84 zb_ext_pan_id_t extended_pan_id;
85 char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0};
86 int addr_len;
87
88 zb_get_extended_pan_id(extended_pan_id);
89 addr_len = ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), extended_pan_id);
90
91 for (int i = 0; i < addr_len; ++i) {
92 if (ieee_addr_buf[i] != '0') {
93 on_join_(true);
94 break;
95 }
96 }
97 }
98 break;
99 }
100
101 /* All callbacks should either reuse or free passed buffers.
102 * If bufid == 0, the buffer is invalid (not passed).
103 */
104 if (bufid) {
105 zb_buf_free(bufid);
106 }
107}
108
109void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) {
110 zb_zcl_device_callback_param_t *p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);
111 zb_zcl_device_callback_id_t device_cb_id = p_device_cb_param->device_cb_id;
112 zb_uint16_t cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id;
113 zb_uint16_t attr_id = p_device_cb_param->cb_param.set_attr_value_param.attr_id;
114 auto endpoint = p_device_cb_param->endpoint;
115
116 ESP_LOGI(TAG, "%s id %hd, cluster_id %d, attr_id %d, endpoint: %d", __func__, device_cb_id, cluster_id, attr_id,
117 endpoint);
118
119 /* Set default response value. */
120 p_device_cb_param->status = RET_OK;
121
122 // endpoints are enumerated from 1
123 if (global_zigbee->callbacks_.size() >= endpoint) {
124 const auto &cb = global_zigbee->callbacks_[endpoint - 1];
125 if (cb) {
126 cb(bufid);
127 return;
128 }
129 }
130 p_device_cb_param->status = RET_NOT_IMPLEMENTED;
131}
132
133void ZigbeeComponent::on_join_(bool factory_new) {
134 this->defer([this, factory_new]() {
135 ESP_LOGD(TAG, "Joined the network");
136 this->join_cb_.call(factory_new);
137 });
138}
139
141 this->defer([this]() {
142 ESP_LOGD(TAG, "Started zigbee stack");
143 this->start_cb_.call();
144 });
145}
146
147#ifdef USE_ZIGBEE_WIPE_ON_BOOT
149 const struct flash_area *fap;
150 flash_area_open(area, &fap);
151 flash_area_erase(fap, 0, fap->fa_size);
152 flash_area_close(fap);
153}
154#endif
155
157 global_zigbee = this;
158 auto err = settings_subsys_init();
159 if (err) {
160 ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err);
161 return;
162 }
163
164#ifdef USE_ZIGBEE_WIPE_ON_BOOT
165 bool wipe = true;
166#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
167 // unique hash to store preferences for this component
168 uint32_t hash = 88498616UL;
169 uint32_t wipe_value = 0;
170 auto wipe_pref = global_preferences->make_preference<uint32_t>(hash, true);
171 if (wipe_pref.load(&wipe_value)) {
172 wipe = wipe_value != USE_ZIGBEE_WIPE_ON_BOOT_MAGIC;
173 ESP_LOGD(TAG, "Wipe value in preferences %u, in firmware %u", wipe_value, USE_ZIGBEE_WIPE_ON_BOOT_MAGIC);
174 }
175#endif
176 if (wipe) {
177 erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM));
178 erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG));
179 erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE));
180#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
181 wipe_value = USE_ZIGBEE_WIPE_ON_BOOT_MAGIC;
182 wipe_pref.save(&wipe_value);
183#endif
184 }
185#endif
186
187 ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb);
188 err = settings_load();
189 if (err) {
190 ESP_LOGE(TAG, "Cannot load settings, err: %d", err);
191 return;
192 }
193#ifdef CONFIG_ZIGBEE_ROLE_END_DEVICE
194 zigbee_configure_sleepy_behavior(this->sleepy_);
195#endif
196 zigbee_enable();
197 this->disable_loop();
198}
199
200#ifdef ESPHOME_LOG_HAS_CONFIG
201static const char *role() {
202 switch (zb_get_network_role()) {
203 case ZB_NWK_DEVICE_TYPE_COORDINATOR:
204 return "coordinator";
205 case ZB_NWK_DEVICE_TYPE_ROUTER:
206 return "router";
207 case ZB_NWK_DEVICE_TYPE_ED:
208 return "end device";
209 }
210 return "unknown";
211}
212
213static const char *get_wipe_on_boot() {
214#ifdef USE_ZIGBEE_WIPE_ON_BOOT
215#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
216 return "ONCE";
217#else
218 return "YES";
219#endif
220#else
221 return "NO";
222#endif
223}
224#endif
225
227 char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0};
228 zb_ieee_addr_t addr;
229 zb_get_long_address(addr);
230 ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), addr);
231 zb_ext_pan_id_t extended_pan_id;
232 char extended_pan_id_buf[IEEE_ADDR_BUF_SIZE] = {0};
233 zb_get_extended_pan_id(extended_pan_id);
234 ieee_addr_to_str(extended_pan_id_buf, sizeof(extended_pan_id_buf), extended_pan_id);
235 ESP_LOGCONFIG(TAG,
236 "Zigbee\n"
237 " Wipe on boot: %s\n"
238 " Device is joined to the network: %s\n"
239 " Sleep time: %us\n"
240 " Radio sleep time: %us\n"
241 " RX ON when idle: %s\n"
242 " Current channel: %d\n"
243 " Current page: %d\n"
244 " Sleep threshold: %ums\n"
245 " Role: %s\n"
246 " Long addr: 0x%s\n"
247 " Short addr: 0x%04X\n"
248 " Long pan id: 0x%s\n"
249 " Short pan id: 0x%04X",
250 get_wipe_on_boot(), YESNO(zb_zdo_joined()), this->sleep_time_, this->radio_sleep_time_,
251 YESNO(zb_get_rx_on_when_idle()), zb_get_current_channel(), zb_get_current_page(),
252 zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf,
253 zb_get_pan_id());
255}
256
257static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) {
258 ESP_LOGD(TAG, "Force zboss scheduler to wake and send attribute report");
259 zb_buf_free(bufid);
260}
261
266
268 this->radio_sleep_remainder_ += ms;
269 uint32_t seconds = this->radio_sleep_remainder_ / 1000;
270 this->radio_sleep_remainder_ -= seconds * 1000;
271 this->radio_sleep_time_ += seconds;
272}
273
275 if (this->force_report_) {
276 this->force_report_ = false;
277 zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0);
278 }
279 this->disable_loop();
280}
281
283 ESP_LOGD(TAG, "Factory reset");
284 ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0);
285}
286
287static void log_reporting_info(zb_zcl_reporting_info_t *rep_info) {
288 auto now = millis();
289 ESP_LOGD(TAG, "Reporting: endpoint %d, cluster_id 0x%04X, attr_id 0x%04X, flags 0x%02X, report in %ums", rep_info->ep,
290 rep_info->cluster_id, rep_info->attr_id, rep_info->flags,
291 ZB_ZCL_GET_REPORTING_FLAG(rep_info, ZB_ZCL_REPORT_TIMER_STARTED)
292 ? ZB_TIME_BEACON_INTERVAL_TO_MSEC(rep_info->run_time) - now
293 : 0);
294 ESP_LOGD(TAG, " min_interval %ds, max_interval %ds, def_min_interval %ds, def_max_interval %ds",
295 rep_info->u.send_info.min_interval, rep_info->u.send_info.max_interval,
296 rep_info->u.send_info.def_min_interval, rep_info->u.send_info.def_max_interval);
297}
298
300#ifdef ESPHOME_LOG_HAS_VERBOSE
301 for (zb_uint8_t j = 0; j < ZCL_CTX().device_ctx->ep_count; j++) {
302 if (ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info) {
303 zb_zcl_reporting_info_t *rep_info = ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info;
304 for (zb_uint8_t i = 0; i < ZCL_CTX().device_ctx->ep_desc_list[j]->rep_info_count; i++) {
305 log_reporting_info(rep_info);
306 rep_info++;
307 }
308 }
309 }
310#endif
311}
312
313void ZigbeeComponent::after_reporting_info(zb_zcl_configure_reporting_req_t *config_rep_req,
314 zb_zcl_attr_addr_info_t *attr_addr_info) {
315#ifdef ESPHOME_LOG_HAS_DEBUG
316 auto *rep_info =
317 zb_zcl_find_reporting_info_manuf(attr_addr_info->src_ep, attr_addr_info->cluster_id, attr_addr_info->cluster_role,
318 config_rep_req->attr_id, attr_addr_info->manuf_code);
319 if (rep_info == nullptr) {
320 ESP_LOGE(TAG,
321 "Failed to resolve reporting info (src_ep=%u cluster_id=0x%04x role=%u attr_id=0x%04x manuf_code=0x%04x)",
322 attr_addr_info->src_ep, attr_addr_info->cluster_id, attr_addr_info->cluster_role, config_rep_req->attr_id,
323 attr_addr_info->manuf_code);
324 return;
325 }
326 log_reporting_info(rep_info);
327#endif
328}
329
330} // namespace esphome::zigbee
331
332extern "C" {
333void zboss_signal_handler(zb_uint8_t param) { esphome::zigbee::global_zigbee->zboss_signal_handler_esphome(param); }
334void zb_osif_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len) {
335 (void) buf;
336 (void) len;
337}
340
341// NOLINTBEGIN(readability-identifier-naming,bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
342extern zb_ret_t __real_zb_zcl_put_reporting_info_from_req(zb_zcl_configure_reporting_req_t *config_rep_req,
343 zb_zcl_attr_addr_info_t *attr_addr_info);
344
345zb_ret_t __wrap_zb_zcl_put_reporting_info_from_req(zb_zcl_configure_reporting_req_t *config_rep_req,
346 zb_zcl_attr_addr_info_t *attr_addr_info) {
347 zb_ret_t ret = __real_zb_zcl_put_reporting_info_from_req(config_rep_req, attr_addr_info);
348 esphome::zigbee::global_zigbee->after_reporting_info(config_rep_req, attr_addr_info);
349 return ret;
350}
351
354extern zb_bool_t __real_zb_trans_transmit(zb_uint8_t wait_type, zb_time_t tx_at, zb_uint8_t *tx_buf,
355 zb_uint8_t current_channel);
356
357static uint32_t radio_sleep_start_ms = 0;
358
359static void stop_radio_sleep_timer() {
360 if (radio_sleep_start_ms) {
361 esphome::zigbee::global_zigbee->add_radio_sleep_time_ms(esphome::millis() - radio_sleep_start_ms);
362 }
363 radio_sleep_start_ms = 0;
364}
365
368 radio_sleep_start_ms = esphome::millis();
369}
370
372 stop_radio_sleep_timer();
374}
375
376zb_bool_t __wrap_zb_trans_transmit(zb_uint8_t wait_type, zb_time_t tx_at, zb_uint8_t *tx_buf,
377 zb_uint8_t current_channel) {
378 stop_radio_sleep_timer();
379 return __real_zb_trans_transmit(wait_type, tx_at, tx_buf, current_channel);
380}
381// NOLINTEND(readability-identifier-naming,bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
382}
383#endif
uint8_t status
Definition bl0942.h:8
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void disable_loop()
Disable this component's loop.
void after_reporting_info(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info)
CallbackManager< void(bool)> join_cb_
static void zcl_device_cb(zb_bufid_t bufid)
LazyCallbackManager< void()> start_cb_
void on_join_(bool factory_new)
void zboss_signal_handler_esphome(zb_bufid_t bufid)
void add_radio_sleep_time_ms(uint32_t ms)
uint16_t addr_len
int ret
ESPHOME_LOG_TAG(TAG, "zigbee.time")
const uint8_t IEEE_ADDR_BUF_SIZE
const void size_t len
Definition hal.h:68
ESPPreferences * global_preferences
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24
void __real_zb_trans_enter_receive(void)
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)
zb_ret_t __wrap_zb_zcl_put_reporting_info_from_req(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info)
void zb_osif_serial_init()
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)
void zb_osif_serial_flush()
void __wrap_zb_trans_enter_receive(void)
void __real_zb_trans_enter_sleep(void)
void zboss_signal_handler(zb_uint8_t param)
zb_ret_t __real_zb_zcl_put_reporting_info_from_req(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info)
void zb_osif_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len)
void __wrap_zb_trans_enter_sleep(void)