From dac9455add3b4b0a3823c13112f01a638100ffef Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 5 Jun 2024 21:03:02 +0200 Subject: [PATCH 1/2] add zephyr shell --- .../components/zephyr_mcumgr/ota/__init__.py | 2 +- esphome/components/zephyr_shell/__init__.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 esphome/components/zephyr_shell/__init__.py diff --git a/esphome/components/zephyr_mcumgr/ota/__init__.py b/esphome/components/zephyr_mcumgr/ota/__init__.py index f9a32d80e0aa..1a11bab88922 100644 --- a/esphome/components/zephyr_mcumgr/ota/__init__.py +++ b/esphome/components/zephyr_mcumgr/ota/__init__.py @@ -128,7 +128,7 @@ async def to_code(config): zephyr_add_prj_conf("MCUMGR_GRP_OS_MCUMGR_PARAMS", True) zephyr_add_prj_conf("NCS_SAMPLE_MCUMGR_BT_OTA_DFU_SPEEDUP", True) - if config[CONF_HARDWARE_UART]: + if CONF_HARDWARE_UART in config: cdc_id = UARTS[config[CONF_HARDWARE_UART]][1] if cdc_id >= 0: zephyr_add_cdc_acm(config, cdc_id) diff --git a/esphome/components/zephyr_shell/__init__.py b/esphome/components/zephyr_shell/__init__.py new file mode 100644 index 000000000000..7a9ba19b15e3 --- /dev/null +++ b/esphome/components/zephyr_shell/__init__.py @@ -0,0 +1,31 @@ +import esphome.config_validation as cv +from esphome.components.zephyr import zephyr_add_prj_conf, zephyr_add_overlay + +CONFIG_SCHEMA = cv.Schema({}) + + +async def to_code(config): + zephyr_add_prj_conf("SHELL", True) + # zephyr_ble_server + zephyr_add_prj_conf("BT_SHELL", True) + # ota + zephyr_add_prj_conf("MCUBOOT_SHELL", True) + # i2c + zephyr_add_prj_conf("I2C_SHELL", True) + # zigbee + zephyr_add_prj_conf("ZIGBEE_SHELL", True) + # select uart for shell + zephyr_add_overlay( + """ +/ { + chosen { + zephyr,shell-uart = &cdc_acm_uart1; + }; +}; +&zephyr_udc0 { + cdc_acm_uart1: cdc_acm_uart1 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; +""" + ) From daa6cae7a5c3e86d3b72a9d1d3bf890b18a6c9ad Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 5 Jun 2024 21:09:55 +0200 Subject: [PATCH 2/2] add zigbee switch --- esphome/components/zigbee/__init__.py | 60 +++++++ esphome/components/zigbee/switch/__init__.py | 162 ++++++++++++++++++ .../zigbee/switch/zigbee_on_off.cpp | 112 ++++++++++++ .../components/zigbee/switch/zigbee_on_off.h | 62 +++++++ esphome/components/zigbee/zigbee.cpp | 108 ++++++++++++ esphome/components/zigbee/zigbee.h | 20 +++ tests/test12.2.yaml | 103 ++--------- 7 files changed, 538 insertions(+), 89 deletions(-) create mode 100644 esphome/components/zigbee/__init__.py create mode 100644 esphome/components/zigbee/switch/__init__.py create mode 100644 esphome/components/zigbee/switch/zigbee_on_off.cpp create mode 100644 esphome/components/zigbee/switch/zigbee_on_off.h create mode 100644 esphome/components/zigbee/zigbee.cpp create mode 100644 esphome/components/zigbee/zigbee.h diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py new file mode 100644 index 000000000000..9553b5d36d5a --- /dev/null +++ b/esphome/components/zigbee/__init__.py @@ -0,0 +1,60 @@ +import random +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components.zephyr import zephyr_add_prj_conf +from esphome.const import CONF_ID +from esphome.cpp_generator import MockObj +from esphome.core import CORE + +zigbee_ns = cg.esphome_ns.namespace("zigbee") +Zigbee = zigbee_ns.class_("Zigbee", cg.Component) + +KEY_ZIGBEE = "zigbee" +KEY_EP = "ep" + + +def zigbee_set_core_data(config): + CORE.data[KEY_ZIGBEE] = {} + CORE.data[KEY_ZIGBEE][KEY_EP] = [] + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(Zigbee), + } + ).extend(cv.COMPONENT_SCHEMA), + zigbee_set_core_data, +) + + +async def to_code(config): + cg.add_global( + MockObj( + f"ZBOSS_DECLARE_DEVICE_CTX_EP_VA(zb_device_ctx, &{', &'.join(CORE.data[KEY_ZIGBEE][KEY_EP])})" + ) + ) + cg.add(MockObj("ZB_AF_REGISTER_DEVICE_CTX(&zb_device_ctx)")) + + # zigbee + zephyr_add_prj_conf("ZIGBEE", True) + zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True) + zephyr_add_prj_conf("ZIGBEE_ROLE_END_DEVICE", True) + + zephyr_add_prj_conf("ZIGBEE_CHANNEL_SELECTION_MODE_MULTI", True) + + # TODO zigbee2mqtt do not update configuration of device without this + zephyr_add_prj_conf("IEEE802154_VENDOR_OUI_ENABLE", True) + random_number = random.randint(0x000000, 0xFFFFFF) + zephyr_add_prj_conf("IEEE802154_VENDOR_OUI", random_number) + + # crypto + zephyr_add_prj_conf("CRYPTO", True) + + # networking + zephyr_add_prj_conf("NET_IPV6", False) + zephyr_add_prj_conf("NET_IP_ADDR_CHECK", False) + zephyr_add_prj_conf("NET_UDP", False) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/esphome/components/zigbee/switch/__init__.py b/esphome/components/zigbee/switch/__init__.py new file mode 100644 index 000000000000..22c2c16d39a5 --- /dev/null +++ b/esphome/components/zigbee/switch/__init__.py @@ -0,0 +1,162 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output, switch +from esphome.const import CONF_OUTPUT +from esphome.cpp_generator import MockObj +from esphome.core import CORE + +zigbee_on_off_ns = cg.esphome_ns.namespace("zigbee_on_off") +ZigbeeOnOffSwitch = zigbee_on_off_ns.class_("ZigbeeOnOff", switch.Switch, cg.Component) + +cluster_attributes = zigbee_on_off_ns.class_("cluster_attributes_t") + +ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT = cg.global_ns.class_( + "ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT", cg.Component +) +ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST = cg.global_ns.class_( + "ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST", cg.Component +) +ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST = cg.global_ns.class_( + "ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST", cg.Component +) +ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST = cg.global_ns.class_( + "ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST", cg.Component +) +ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST = cg.global_ns.class_( + "ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST", cg.Component +) +ZB_HA_DECLARE_ON_OFF_OUTPUT_CLUSTER_LIST = cg.global_ns.class_( + "ZB_HA_DECLARE_ON_OFF_OUTPUT_CLUSTER_LIST", cg.Component +) +ZB_EP = cg.global_ns.class_("ZB_EP", cg.Component) + +AUTO_LOAD = ["zigbee"] + +CONF_CLUSTER_ATTRIBUTES = "cluster_attributes" + +CONF_BASIC_ATTRIB_LIST_EXT = "basic_attrib_list_ext" +CONF_IDENTIFY_ATTR_LIST = "identify_attr_list" +CONF_GROUPS_ATTR_LIST = "groups_attr_list" +CONF_SCENES_ATTR_LIST = "scenes_attr_list" +CONF_ON_OFF_ATTR_LIST = "on_off_attr_list" +CONF_ON_OFF_OUTPUT_CLUSTER_LIST = "on_off_output_cluster_list" +CONF_ZB_EP = "zb_ep" + +zigbee_ns = cg.esphome_ns.namespace("zigbee") +Zigbee = zigbee_ns.class_("Zigbee", cg.Component) + +CONF_ZIGBEE_ID = "zigbee_id" + +CONFIG_SCHEMA = ( + switch.switch_schema(ZigbeeOnOffSwitch) + .extend( + { + cv.GenerateID(CONF_ZIGBEE_ID): cv.use_id(Zigbee), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.GenerateID(CONF_CLUSTER_ATTRIBUTES): cv.declare_id(cluster_attributes), + cv.GenerateID(CONF_BASIC_ATTRIB_LIST_EXT): cv.declare_id( + ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT + ), + cv.GenerateID(CONF_IDENTIFY_ATTR_LIST): cv.declare_id( + ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST + ), + cv.GenerateID(CONF_GROUPS_ATTR_LIST): cv.declare_id( + ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST + ), + cv.GenerateID(CONF_SCENES_ATTR_LIST): cv.declare_id( + ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST + ), + cv.GenerateID(CONF_ON_OFF_ATTR_LIST): cv.declare_id( + ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST + ), + cv.GenerateID(CONF_ON_OFF_OUTPUT_CLUSTER_LIST): cv.declare_id( + ZB_HA_DECLARE_ON_OFF_OUTPUT_CLUSTER_LIST + ), + cv.GenerateID(CONF_ZB_EP): cv.declare_id(ZB_EP), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +KEY_ZIGBEE = "zigbee" +KEY_EP = "ep" + + +async def to_code(config): + cg.add_global(MockObj(f"{cluster_attributes} {config[CONF_CLUSTER_ATTRIBUTES]}")) + + # Declare attributes + + # basic cluster attributes data + cg.add_global( + MockObj( + f"ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT({config[CONF_BASIC_ATTRIB_LIST_EXT]}, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.zcl_version, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.app_version, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.stack_version, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.hw_version, {config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.mf_name, {config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.model_id, {config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.date_code, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.power_source, {config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.location_id, &{config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.ph_env, {config[CONF_CLUSTER_ATTRIBUTES]}.basic_attr.sw_ver)" + ) + ) + + # identify cluster attributes data + cg.add_global( + MockObj( + f"ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST({config[CONF_IDENTIFY_ATTR_LIST]}, &{config[CONF_CLUSTER_ATTRIBUTES]}.identify_attr.identify_time)" + ) + ) + + # groups cluster attributes data + cg.add_global( + MockObj( + f"ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST({config[CONF_GROUPS_ATTR_LIST]}, &{config[CONF_CLUSTER_ATTRIBUTES]}.groups_attr.name_support)" + ) + ) + + # scenes cluster attribute data + cg.add_global( + MockObj( + f"ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST({config[CONF_SCENES_ATTR_LIST]}, &{config[CONF_CLUSTER_ATTRIBUTES]}.scenes_attr.scene_count, &{config[CONF_CLUSTER_ATTRIBUTES]}.scenes_attr.current_scene, &{config[CONF_CLUSTER_ATTRIBUTES]}.scenes_attr.current_group, &{config[CONF_CLUSTER_ATTRIBUTES]}.scenes_attr.scene_valid, &{config[CONF_CLUSTER_ATTRIBUTES]}.scenes_attr.name_support)" + ) + ) + + # on/off cluster attributes data + cg.add_global( + MockObj( + f"ZB_ZCL_DECLARE_ON_OFF_ATTRIB_LIST({config[CONF_ON_OFF_ATTR_LIST]}, &{config[CONF_CLUSTER_ATTRIBUTES]}.on_off_attr.on_off)" + ) + ) + + # Declare device + cg.add_global( + MockObj( + f"ZB_HA_DECLARE_ON_OFF_OUTPUT_CLUSTER_LIST({config[CONF_ON_OFF_OUTPUT_CLUSTER_LIST]}, {config[CONF_ON_OFF_ATTR_LIST]}, {config[CONF_BASIC_ATTRIB_LIST_EXT]}, {config[CONF_IDENTIFY_ATTR_LIST]}, {config[CONF_GROUPS_ATTR_LIST]}, {config[CONF_SCENES_ATTR_LIST]})" + ) + ) + + zb_ep = config[CONF_ZB_EP] + end_point = int(str(zb_ep)[str(zb_ep).find("_id") + 4 :] or "1") + 9 + + ep_macro_name = "" + if str(config[CONF_CLUSTER_ATTRIBUTES])[-1].isdigit(): + ep_macro_name = "ESPHOME_" + ep_macro_name += "ZB_HA_DECLARE_ON_OFF_OUTPUT_EP" + + cg.add_global( + MockObj( + f"{ep_macro_name}({config[CONF_ZB_EP]}, {end_point}, {config[CONF_ON_OFF_OUTPUT_CLUSTER_LIST]})" + ) + ) + + CORE.data[KEY_ZIGBEE][KEY_EP] += [str(config[CONF_ZB_EP])] + + var = await switch.new_switch(config) + await cg.register_component(var, config) + + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) + cg.add(var.set_cluster_attributes(MockObj(f"&{config[CONF_CLUSTER_ATTRIBUTES]}"))) + hub = await cg.get_variable(config[CONF_ZIGBEE_ID]) + cg.add( + getattr(hub, "add_callback")( + end_point, + MockObj( + f"std::bind(&zigbee_on_off::ZigbeeOnOff::zcl_device_cb, {var}, std::placeholders::_1)" + ), + ) + ) diff --git a/esphome/components/zigbee/switch/zigbee_on_off.cpp b/esphome/components/zigbee/switch/zigbee_on_off.cpp new file mode 100644 index 000000000000..70a7b777017e --- /dev/null +++ b/esphome/components/zigbee/switch/zigbee_on_off.cpp @@ -0,0 +1,112 @@ +#include "zigbee_on_off.h" +#include "esphome/core/log.h" +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace esphome { +namespace zigbee_on_off { + +#define HA_ESP_LIGHT_ENDPOINT 14 +#define HA_ESP_LIGHT_ENDPOINT2 15 +#define BULB_INIT_BASIC_LOCATION_DESC "TODO" +#define BULB_INIT_BASIC_PH_ENV ZB_ZCL_BASIC_ENV_UNSPECIFIED +#define BULB_INIT_BASIC_MODEL_ID "TODO" +#define BULB_INIT_BASIC_DATE_CODE "20200329" +#define BULB_INIT_BASIC_APP_VERSION 00 +#define BULB_INIT_BASIC_HW_VERSION 00 +#define BULB_INIT_BASIC_STACK_VERSION 00 +#define BULB_INIT_BASIC_MANUF_NAME "esphome" + +static const char *const TAG = "zigbee_on_off.switch"; + +void ZigbeeOnOff::dump_config() { LOG_SWITCH("", "Zigbee Switch", this); } + +void ZigbeeOnOff::setup() { + cluster_attributes_->basic_attr.zcl_version = ZB_ZCL_VERSION; + cluster_attributes_->basic_attr.app_version = BULB_INIT_BASIC_APP_VERSION; + cluster_attributes_->basic_attr.stack_version = BULB_INIT_BASIC_STACK_VERSION; + cluster_attributes_->basic_attr.hw_version = BULB_INIT_BASIC_HW_VERSION; + + ZB_ZCL_SET_STRING_VAL(cluster_attributes_->basic_attr.mf_name, BULB_INIT_BASIC_MANUF_NAME, + ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_MANUF_NAME)); + + ZB_ZCL_SET_STRING_VAL(cluster_attributes_->basic_attr.model_id, BULB_INIT_BASIC_MODEL_ID, + ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_MODEL_ID)); + + ZB_ZCL_SET_STRING_VAL(cluster_attributes_->basic_attr.date_code, BULB_INIT_BASIC_DATE_CODE, + ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_DATE_CODE)); + + cluster_attributes_->basic_attr.power_source = ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE; + + ZB_ZCL_SET_STRING_VAL(cluster_attributes_->basic_attr.location_id, BULB_INIT_BASIC_LOCATION_DESC, + ZB_ZCL_STRING_CONST_SIZE(BULB_INIT_BASIC_LOCATION_DESC)); + + cluster_attributes_->basic_attr.ph_env = BULB_INIT_BASIC_PH_ENV; + + /* identify cluster attributes data */ + cluster_attributes_->identify_attr.identify_time = ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE; + /* groups cluster attributes data */ + cluster_attributes_->groups_attr.name_support = 0; + + bool initial_state = this->get_initial_state_with_restore_mode().value_or(false); + + if (initial_state) { + /* on/off cluster attributes data */ + cluster_attributes_->on_off_attr.on_off = ZB_ZCL_ON_OFF_IS_ON; + this->turn_on(); + } else { + /* on/off cluster attributes data */ + cluster_attributes_->on_off_attr.on_off = ZB_ZCL_ON_OFF_IS_OFF; + this->turn_off(); + } +} + +void ZigbeeOnOff::write_state(bool state) { + if (state) { + this->output_->turn_on(); + } else { + this->output_->turn_off(); + } + this->publish_state(state); +} + +void ZigbeeOnOff::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; + + p_device_cb_param->status = RET_OK; + + switch (device_cb_id) { + /* ZCL set attribute value */ + case ZB_ZCL_SET_ATTR_VALUE_CB_ID: + if (cluster_id == ZB_ZCL_CLUSTER_ID_ON_OFF) { + uint8_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data8; + ESP_LOGI(TAG, "on/off attribute setting to %hd", value); + + if (attr_id == ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) { + write_state((zb_bool_t) value); + } + } else { + /* other clusters attribute handled here */ + ESP_LOGI(TAG, "Unhandled cluster attribute id: %d", cluster_id); + } + break; + default: + p_device_cb_param->status = RET_ERROR; + break; + } + + ESP_LOGD(TAG, "%s status: %hd", __func__, p_device_cb_param->status); +} + +} // namespace zigbee_on_off +} // namespace esphome diff --git a/esphome/components/zigbee/switch/zigbee_on_off.h b/esphome/components/zigbee/switch/zigbee_on_off.h new file mode 100644 index 000000000000..876098b8c5f7 --- /dev/null +++ b/esphome/components/zigbee/switch/zigbee_on_off.h @@ -0,0 +1,62 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/output/binary_output.h" +extern "C" { +#include +#include +} + +#define ESPHOME_ZB_ZCL_DECLARE_ON_OFF_OUTPUT_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num) \ + ZB_AF_SIMPLE_DESC_TYPE(in_clust_num, out_clust_num) \ + simple_desc_##ep_name = {ep_id, \ + ZB_AF_HA_PROFILE_ID, \ + ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, \ + ZB_HA_DEVICE_VER_ON_OFF_OUTPUT, \ + 0, \ + in_clust_num, \ + out_clust_num, \ + {ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_CLUSTER_ID_ON_OFF, \ + ZB_ZCL_CLUSTER_ID_SCENES, ZB_ZCL_CLUSTER_ID_GROUPS}} + +#define ESPHOME_ZB_HA_DECLARE_ON_OFF_OUTPUT_EP(ep_name, ep_id, cluster_list) \ + ESPHOME_ZB_ZCL_DECLARE_ON_OFF_OUTPUT_SIMPLE_DESC(ep_name, ep_id, ZB_HA_ON_OFF_OUTPUT_IN_CLUSTER_NUM, \ + ZB_HA_ON_OFF_OUTPUT_OUT_CLUSTER_NUM); \ + ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, ZB_HA_ON_OFF_OUTPUT_REPORT_ATTR_COUNT); \ + ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \ + ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \ + (zb_af_simple_desc_1_1_t *) &simple_desc_##ep_name, \ + ZB_HA_ON_OFF_OUTPUT_REPORT_ATTR_COUNT, reporting_info##ep_name, 0, NULL) + +namespace esphome { +namespace zigbee_on_off { + +typedef struct { + zb_zcl_basic_attrs_ext_t basic_attr; + zb_zcl_identify_attrs_t identify_attr; + zb_zcl_groups_attrs_t groups_attr; + zb_zcl_scenes_attrs_t scenes_attr; + zb_zcl_on_off_attrs_t on_off_attr; +} cluster_attributes_t; + +class ZigbeeOnOff : public switch_::Switch, public Component { + public: + void set_output(output::BinaryOutput *output) { output_ = output; } + void set_cluster_attributes(cluster_attributes_t *cluster_attributes) { cluster_attributes_ = cluster_attributes; } + + void setup() override; + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } + void dump_config() override; + + void zcl_device_cb(zb_bufid_t bufid); + + protected: + void write_state(bool state) override; + + output::BinaryOutput *output_; + cluster_attributes_t *cluster_attributes_ = nullptr; +}; + +} // namespace zigbee_on_off +} // namespace esphome diff --git a/esphome/components/zigbee/zigbee.cpp b/esphome/components/zigbee/zigbee.cpp new file mode 100644 index 000000000000..dfc1d68664b2 --- /dev/null +++ b/esphome/components/zigbee/zigbee.cpp @@ -0,0 +1,108 @@ +#include "zigbee.h" +#include "esphome/core/log.h" +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace esphome { +namespace zigbee { + +static const char *const TAG = "zigbee"; + +static Zigbee *global_zigbee = nullptr; + +void zboss_signal_handler_esphome(zb_bufid_t bufid) { + zb_zdo_app_signal_hdr_t *sig_hndler = NULL; + 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); + break; + case ZB_BDB_SIGNAL_STEERING: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_STEERING, status: %d", status); + break; + case ZB_COMMON_SIGNAL_CAN_SLEEP: + ESP_LOGV(TAG, "ZB_COMMON_SIGNAL_CAN_SLEEP, status: %d", status); + break; + default: + ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status); + break; + } + + /* No application-specific behavior is required. + * Call default signal handler. + */ + 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)); + } + + /* All callbacks should either reuse or free passed buffers. + * If bufid == 0, the buffer is invalid (not passed). + */ + if (bufid) { + zb_buf_free(bufid); + } +} + +static void 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, "zcl_device_cb %s id %hd, cluster_id %d, attr_id %d, endpoint: %d", __func__, device_cb_id, cluster_id, + attr_id, endpoint); + + auto cb = global_zigbee->callbacks_.find(endpoint); + if (cb != global_zigbee->callbacks_.end()) { + cb->second(bufid); + return; + } + p_device_cb_param->status = RET_ERROR; +} + +void Zigbee::setup() { + global_zigbee = this; + auto err = settings_subsys_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); + return; + } + + /* Register callback for handling ZCL commands. */ + ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); + + /* Settings should be loaded after zcl_scenes_init */ + err = settings_load(); + if (err) { + ESP_LOGE(TAG, "Cannot load settings, err: %d", err); + return; + } + + /* Start Zigbee default thread */ + zigbee_enable(); +} + +} // namespace zigbee +} // namespace esphome + +extern "C" void zboss_signal_handler(zb_bufid_t bufid) { esphome::zigbee::zboss_signal_handler_esphome(bufid); } diff --git a/esphome/components/zigbee/zigbee.h b/esphome/components/zigbee/zigbee.h new file mode 100644 index 000000000000..3217cf4994b6 --- /dev/null +++ b/esphome/components/zigbee/zigbee.h @@ -0,0 +1,20 @@ +#pragma once +#include + +#include "esphome/core/component.h" +extern "C" { +#include +} + +namespace esphome { +namespace zigbee { + +class Zigbee : public Component { + public: + void setup() override; + void add_callback(zb_uint8_t endpoint, std::function cb) { callbacks_[endpoint] = cb; } + std::map> callbacks_; +}; + +} // namespace zigbee +} // namespace esphome diff --git a/tests/test12.2.yaml b/tests/test12.2.yaml index 2eae0f9107ed..7d6610a6fcd4 100644 --- a/tests/test12.2.yaml +++ b/tests/test12.2.yaml @@ -17,6 +17,19 @@ logger: switch: NONE switch: + - platform: zigbee + output: gpio_15 + id: zigbee1 + - platform: zigbee + output: gpio_15 + id: zigbee2 + +# interval: +# - interval: 500ms +# then: +# - switch.toggle: gpio_15 + +output: - platform: gpio pin: number: 15 @@ -24,14 +37,6 @@ switch: mode: output: true id: gpio_15 - restore_mode: RESTORE_DEFAULT_OFF - -interval: - - interval: 500ms - then: - - switch.toggle: gpio_15 - -output: - platform: gpio pin: number: 14 @@ -50,88 +55,8 @@ output: dfu_mode: reset_output: rest_gpio -ota: - - platform: zephyr_mcumgr - hardware_uart: cdc1 - on_begin: - then: - - logger.log: "OTA start" - on_progress: - then: - - logger.log: - format: "OTA progress %0.1f%%" - args: ["x"] - on_end: - then: - - logger.log: "OTA end" - on_error: - then: - - logger.log: - format: "OTA update error %d" - args: ["x"] - on_state_change: - then: - - if: - condition: - lambda: return state == ota::OTA_STARTED; - then: - - logger.log: "OTA start" - -zephyr_ble_server: - -zephyr_ble_nus: - log: true - zephyr_debug: debug: -sensor: - - platform: uptime - name: Uptime Sensor - - platform: adc - pin: VDDHDIV5 - name: "VDDH Voltage" - filters: - - multiply: 5 - - platform: adc - pin: VDD - name: "VDD Voltage" - - platform: adc - pin: AIN0 - name: "AIN0 Voltage" - - platform: ads1115 - multiplexer: 'A0_GND' - gain: 6.144 - name: "ADS1115 Channel A0-GND" - update_interval: 1s - - platform: ads1115 - multiplexer: 'A1_GND' - gain: 6.144 - name: "ADS1115 Channel A1-GND" - update_interval: 1s - - platform: ads1115 - multiplexer: 'A2_GND' - gain: 6.144 - name: "ADS1115 Channel A2-GND" - update_interval: 1s - - platform: ads1115 - multiplexer: 'A3_GND' - gain: 6.144 - name: "ADS1115 Channel A3-GND" - update_interval: 1s - -deep_sleep: - -text_sensor: - - platform: debug - reset_reason: - name: "Reset Reason" - -i2c: - sda: P1.04 - scl: P1.06 - scan: false - -ads1115: - - address: 0x48 +zephyr_shell: