#include "zigbee_zephyr.h" #if defined(USE_ZIGBEE) && defined(USE_NRF52) #include "esphome/core/log.h" #include #include #include "esphome/core/hal.h" #include "esphome/core/wake.h" extern "C" { #include #include #include #include #include } namespace esphome::zigbee { static const char *const TAG = "zigbee"; ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) const uint8_t IEEE_ADDR_BUF_SIZE = 17; void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) { zb_zdo_app_signal_hdr_t *sig_hndler = nullptr; zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_hndler); zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); switch (sig) { case ZB_ZDO_SIGNAL_SKIP_STARTUP: ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_SKIP_STARTUP, status: %d", status); break; case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY: ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY, status: %d", status); break; case ZB_ZDO_SIGNAL_LEAVE: ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_LEAVE, status: %d", status); break; case ZB_BDB_SIGNAL_DEVICE_REBOOT: ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status); if (status == RET_OK) { on_join_(false); } break; case ZB_BDB_SIGNAL_STEERING: break; case ZB_COMMON_SIGNAL_CAN_SLEEP: ESP_LOGV(TAG, "ZB_COMMON_SIGNAL_CAN_SLEEP, status: %d", status); break; case ZB_BDB_SIGNAL_DEVICE_FIRST_START: ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_FIRST_START, status: %d", status); break; case ZB_NLME_STATUS_INDICATION: ESP_LOGD(TAG, "ZB_NLME_STATUS_INDICATION, status: %d", status); break; case ZB_BDB_SIGNAL_TC_REJOIN_DONE: ESP_LOGD(TAG, "ZB_BDB_SIGNAL_TC_REJOIN_DONE, status: %d", status); break; default: ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status); break; } auto before = millis(); auto err = zigbee_default_signal_handler(bufid); if (err != RET_OK) { ESP_LOGE(TAG, "Zigbee_default_signal_handler ERROR %u [%s]", err, zb_error_to_string_get(err)); } if (sig == ZB_COMMON_SIGNAL_CAN_SLEEP) { this->sleep_remainder_ += millis() - before; uint32_t seconds = this->sleep_remainder_ / 1000; this->sleep_remainder_ -= seconds * 1000; this->sleep_time_ += seconds; } switch (sig) { case ZB_BDB_SIGNAL_STEERING: ESP_LOGD(TAG, "ZB_BDB_SIGNAL_STEERING, status: %d", status); if (status == RET_OK) { zb_ext_pan_id_t extended_pan_id; char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; int addr_len; zb_get_extended_pan_id(extended_pan_id); addr_len = ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), extended_pan_id); for (int i = 0; i < addr_len; ++i) { if (ieee_addr_buf[i] != '0') { on_join_(true); break; } } } break; } /* All callbacks should either reuse or free passed buffers. * If bufid == 0, the buffer is invalid (not passed). */ if (bufid) { zb_buf_free(bufid); } } void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) { zb_zcl_device_callback_param_t *p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); zb_zcl_device_callback_id_t device_cb_id = p_device_cb_param->device_cb_id; zb_uint16_t cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id; zb_uint16_t attr_id = p_device_cb_param->cb_param.set_attr_value_param.attr_id; auto endpoint = p_device_cb_param->endpoint; ESP_LOGI(TAG, "%s id %hd, cluster_id %d, attr_id %d, endpoint: %d", __func__, device_cb_id, cluster_id, attr_id, endpoint); /* Set default response value. */ p_device_cb_param->status = RET_OK; esphome::wake_loop_threadsafe(); // endpoints are enumerated from 1 if (global_zigbee->callbacks_.size() >= endpoint) { const auto &cb = global_zigbee->callbacks_[endpoint - 1]; if (cb) { cb(bufid); return; } } p_device_cb_param->status = RET_NOT_IMPLEMENTED; } void ZigbeeComponent::on_join_(bool factory_new) { this->defer([this, factory_new]() { ESP_LOGD(TAG, "Joined the network"); this->join_cb_.call(factory_new); }); } #ifdef USE_ZIGBEE_WIPE_ON_BOOT void ZigbeeComponent::erase_flash_(int area) { const struct flash_area *fap; flash_area_open(area, &fap); flash_area_erase(fap, 0, fap->fa_size); flash_area_close(fap); } #endif void ZigbeeComponent::setup() { global_zigbee = this; auto err = settings_subsys_init(); if (err) { ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); return; } #ifdef USE_ZIGBEE_WIPE_ON_BOOT bool wipe = true; #ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC // unique hash to store preferences for this component uint32_t hash = 88498616UL; uint32_t wipe_value = 0; auto wipe_pref = global_preferences->make_preference(hash, true); if (wipe_pref.load(&wipe_value)) { wipe = wipe_value != USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; ESP_LOGD(TAG, "Wipe value in preferences %u, in firmware %u", wipe_value, USE_ZIGBEE_WIPE_ON_BOOT_MAGIC); } #endif if (wipe) { erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM)); erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG)); erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE)); #ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC wipe_value = USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; wipe_pref.save(&wipe_value); #endif } #endif ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); err = settings_load(); if (err) { ESP_LOGE(TAG, "Cannot load settings, err: %d", err); return; } #ifdef CONFIG_ZIGBEE_ROLE_END_DEVICE zigbee_configure_sleepy_behavior(this->sleepy_); #endif zigbee_enable(); } #ifdef ESPHOME_LOG_HAS_CONFIG static const char *role() { switch (zb_get_network_role()) { case ZB_NWK_DEVICE_TYPE_COORDINATOR: return "coordinator"; case ZB_NWK_DEVICE_TYPE_ROUTER: return "router"; case ZB_NWK_DEVICE_TYPE_ED: return "end device"; } return "unknown"; } static const char *get_wipe_on_boot() { #ifdef USE_ZIGBEE_WIPE_ON_BOOT #ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC return "ONCE"; #else return "YES"; #endif #else return "NO"; #endif } #endif void ZigbeeComponent::dump_config() { char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; zb_ieee_addr_t addr; zb_get_long_address(addr); ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), addr); zb_ext_pan_id_t extended_pan_id; char extended_pan_id_buf[IEEE_ADDR_BUF_SIZE] = {0}; zb_get_extended_pan_id(extended_pan_id); ieee_addr_to_str(extended_pan_id_buf, sizeof(extended_pan_id_buf), extended_pan_id); ESP_LOGCONFIG(TAG, "Zigbee\n" " Wipe on boot: %s\n" " Device is joined to the network: %s\n" " Sleep time: %us\n" " RX ON when idle: %s\n" " Current channel: %d\n" " Current page: %d\n" " Sleep threshold: %ums\n" " Role: %s\n" " Long addr: 0x%s\n" " 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()); dump_reporting_(); } static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { ESP_LOGD(TAG, "Force zboss scheduler to wake and send attribute report"); zb_buf_free(bufid); } void ZigbeeComponent::force_report() { this->force_report_ = true; } void ZigbeeComponent::loop() { if (this->force_report_) { this->force_report_ = false; zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0); } } void ZigbeeComponent::factory_reset() { ESP_LOGD(TAG, "Factory reset"); ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0); } static void log_reporting_info(zb_zcl_reporting_info_t *rep_info) { auto now = millis(); ESP_LOGD(TAG, "Reporting: endpoint %d, cluster_id 0x%04X, attr_id 0x%04X, flags 0x%02X, report in %ums", rep_info->ep, rep_info->cluster_id, rep_info->attr_id, rep_info->flags, ZB_ZCL_GET_REPORTING_FLAG(rep_info, ZB_ZCL_REPORT_TIMER_STARTED) ? ZB_TIME_BEACON_INTERVAL_TO_MSEC(rep_info->run_time) - now : 0); ESP_LOGD(TAG, " min_interval %ds, max_interval %ds, def_min_interval %ds, def_max_interval %ds", rep_info->u.send_info.min_interval, rep_info->u.send_info.max_interval, rep_info->u.send_info.def_min_interval, rep_info->u.send_info.def_max_interval); } void ZigbeeComponent::dump_reporting_() { #ifdef ESPHOME_LOG_HAS_VERBOSE for (zb_uint8_t j = 0; j < ZCL_CTX().device_ctx->ep_count; j++) { if (ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info) { zb_zcl_reporting_info_t *rep_info = ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info; for (zb_uint8_t i = 0; i < ZCL_CTX().device_ctx->ep_desc_list[j]->rep_info_count; i++) { log_reporting_info(rep_info); rep_info++; } } } #endif } void ZigbeeComponent::after_reporting_info(zb_zcl_configure_reporting_req_t *config_rep_req, zb_zcl_attr_addr_info_t *attr_addr_info) { #ifdef ESPHOME_LOG_HAS_DEBUG auto *rep_info = zb_zcl_find_reporting_info_manuf(attr_addr_info->src_ep, attr_addr_info->cluster_id, attr_addr_info->cluster_role, config_rep_req->attr_id, attr_addr_info->manuf_code); if (rep_info == nullptr) { ESP_LOGE(TAG, "Failed to resolve reporting info (src_ep=%u cluster_id=0x%04x role=%u attr_id=0x%04x manuf_code=0x%04x)", attr_addr_info->src_ep, attr_addr_info->cluster_id, attr_addr_info->cluster_role, config_rep_req->attr_id, attr_addr_info->manuf_code); return; } log_reporting_info(rep_info); #endif } } // namespace esphome::zigbee extern "C" { void zboss_signal_handler(zb_uint8_t param) { esphome::zigbee::global_zigbee->zboss_signal_handler_esphome(param); } void zb_osif_serial_put_bytes(const zb_uint8_t *buf, zb_short_t len) { (void) buf; (void) len; } void zb_osif_serial_flush() {} void zb_osif_serial_init() {} // NOLINTBEGIN(readability-identifier-naming,bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) extern 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); 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) { zb_ret_t ret = __real_zb_zcl_put_reporting_info_from_req(config_rep_req, attr_addr_info); esphome::zigbee::global_zigbee->after_reporting_info(config_rep_req, attr_addr_info); return ret; } // NOLINTEND(readability-identifier-naming,bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) } #endif