zigbee binary_sensor and switch

This commit is contained in:
Tomasz Duda 2024-08-22 16:16:09 +02:00
parent ab620acd4f
commit bdf2d21637
15 changed files with 1269 additions and 0 deletions

View file

@ -0,0 +1,263 @@
from datetime import datetime
import random
from esphome import automation
import esphome.codegen as cg
from esphome.components.zephyr import zephyr_add_prj_conf
from esphome.components.zigbee_ctx import KEY_EP, KEY_ZIGBEE, zigbee_set_core_data
import esphome.config_validation as cv
from esphome.const import CONF_BINARY_SENSOR, CONF_ID, CONF_PLATFORM, __version__
from esphome.core import CORE, ID, coroutine_with_priority
from esphome.cpp_generator import (
AssignmentExpression,
MockObj,
VariableDeclarationExpression,
)
import esphome.final_validate as fv
from .const import (
CONF_BASIC_ATTRIB_LIST_EXT,
CONF_BASIC_ATTRS_EXT,
CONF_GROUPS_ATTRIB_LIST,
CONF_GROUPS_ATTRS,
CONF_IDENTIFY_ATTRIB_LIST,
CONF_IDENTIFY_ATTRS,
CONF_MAX_EP_NUMBER,
CONF_SCENES_ATTRIB_LIST,
CONF_SCENES_ATTRS,
CONF_SWITCH,
CONF_ZIGBEE_ID,
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT,
ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST,
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST,
ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST,
Zigbee,
zb_char_t_ptr,
zb_zcl_basic_attrs_ext_t,
zb_zcl_groups_attrs_t,
zb_zcl_identify_attrs_t,
zb_zcl_scenes_attrs_t,
zigbee_ns,
)
AUTO_LOAD = ["zigbee_ctx"]
CONF_ON_JOIN = "on_join"
ZigbeeBaseSchema = cv.Schema(
{
cv.GenerateID(CONF_ZIGBEE_ID): cv.use_id(Zigbee),
cv.GenerateID(CONF_BASIC_ATTRIB_LIST_EXT): cv.use_id(
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT
),
cv.GenerateID(CONF_IDENTIFY_ATTRIB_LIST): cv.use_id(
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST
),
cv.GenerateID(CONF_GROUPS_ATTRIB_LIST): cv.use_id(
ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST
),
cv.GenerateID(CONF_SCENES_ATTRIB_LIST): cv.use_id(
ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST
),
},
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(Zigbee),
cv.GenerateID(CONF_BASIC_ATTRS_EXT): cv.declare_id(
zb_zcl_basic_attrs_ext_t
),
cv.GenerateID(CONF_BASIC_ATTRIB_LIST_EXT): cv.declare_id(
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT
),
cv.GenerateID(CONF_IDENTIFY_ATTRS): cv.declare_id(zb_zcl_identify_attrs_t),
cv.GenerateID(CONF_IDENTIFY_ATTRIB_LIST): cv.declare_id(
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST
),
cv.GenerateID(CONF_GROUPS_ATTRS): cv.declare_id(zb_zcl_groups_attrs_t),
cv.GenerateID(CONF_GROUPS_ATTRIB_LIST): cv.declare_id(
ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST
),
cv.GenerateID(CONF_SCENES_ATTRS): cv.declare_id(zb_zcl_scenes_attrs_t),
cv.GenerateID(CONF_SCENES_ATTRIB_LIST): cv.declare_id(
ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST
),
cv.Optional(CONF_ON_JOIN): automation.validate_automation(single=True),
}
).extend(cv.COMPONENT_SCHEMA),
zigbee_set_core_data,
)
def count_ep_by_type(fconf, type):
count = 0
if type in fconf:
for entity in fconf[type]:
if CONF_PLATFORM in entity and entity[CONF_PLATFORM] == KEY_ZIGBEE:
count += 1
return count
def validate_number_of_ep(config):
count = 0
fconf = fv.full_config.get()
count += count_ep_by_type(fconf, CONF_SWITCH)
count += count_ep_by_type(fconf, CONF_BINARY_SENSOR)
if count > 8:
raise cv.Invalid(f"Maximum number of EP is {CONF_MAX_EP_NUMBER}")
FINAL_VALIDATE_SCHEMA = cv.All(
validate_number_of_ep,
)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_ZIGBEE")
# 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)
basic_attrs_ext = zigbee_new_variable(config[CONF_BASIC_ATTRS_EXT])
zigbee_new_attr_list(
config[CONF_BASIC_ATTRIB_LIST_EXT],
zigbee_assign(
basic_attrs_ext.zcl_version, cg.global_ns.namespace("ZB_ZCL_VERSION")
),
zigbee_assign(basic_attrs_ext.app_version, 0),
zigbee_assign(basic_attrs_ext.stack_version, 0),
zigbee_assign(basic_attrs_ext.hw_version, 0),
zigbee_set_string(basic_attrs_ext.mf_name, "esphome"),
zigbee_set_string(basic_attrs_ext.model_id, "v1"),
zigbee_set_string(
basic_attrs_ext.date_code, datetime.now().strftime("%d/%m/%y %H:%M")
),
zigbee_assign(
basic_attrs_ext.power_source,
cg.global_ns.namespace("ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE"),
),
zigbee_set_string(basic_attrs_ext.location_id, ""),
zigbee_assign(
basic_attrs_ext.ph_env,
cg.global_ns.namespace("ZB_ZCL_BASIC_ENV_UNSPECIFIED"),
),
zigbee_set_string(basic_attrs_ext.sw_ver, __version__),
)
identify_attrs = zigbee_new_variable(config[CONF_IDENTIFY_ATTRS])
zigbee_new_attr_list(
config[CONF_IDENTIFY_ATTRIB_LIST],
zigbee_assign(
identify_attrs.identify_time,
cg.global_ns.namespace("ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE"),
),
)
groups_attrs = zigbee_new_variable(config[CONF_GROUPS_ATTRS])
zigbee_new_attr_list(
config[CONF_GROUPS_ATTRIB_LIST],
zigbee_assign(groups_attrs.name_support, 0),
)
scenes_attrs = zigbee_new_variable(config[CONF_SCENES_ATTRS])
zigbee_new_attr_list(
config[CONF_SCENES_ATTRIB_LIST],
zigbee_assign(scenes_attrs.scene_count, 0),
zigbee_assign(scenes_attrs.current_scene, 0),
zigbee_assign(scenes_attrs.current_group, 0),
zigbee_assign(scenes_attrs.scene_valid, 0),
zigbee_assign(scenes_attrs.name_support, 0),
)
# the rest
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)
FactoryResetAction = zigbee_ns.class_("FactoryResetAction", automation.Action)
@automation.register_action("zigbee.factory_reset", FactoryResetAction, cv.Schema({}))
async def zigbee_factory_reset_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg)
def zigbee_new_variable(id_: ID, type_: "MockObj" = None) -> "MockObj":
assert isinstance(id_, ID)
obj = MockObj(id_, ".")
if type_ is not None:
id_.type = type_
decl = VariableDeclarationExpression(id_.type, "", id_)
CORE.add_global(decl)
CORE.register_variable(id_, obj)
return obj
def zigbee_assign(target, expression):
cg.add(AssignmentExpression("", "", target, expression))
return target
def zigbee_set_string(target, value: str):
cg.add(
cg.RawExpression(
f"ZB_ZCL_SET_STRING_VAL({target}, {cg.safe_exp(value)}, ZB_ZCL_STRING_CONST_SIZE({cg.safe_exp(value)}))"
)
)
return ID(str(target), True, zb_char_t_ptr)
def zigbee_new_attr_list(id_: ID, *args):
assert isinstance(id_, ID)
list = []
for arg in args:
if str(zb_char_t_ptr) == str(arg.type):
list.append(f"{arg}")
else:
list.append(f"&{arg}")
obj = cg.RawExpression(f'{id_.type}({id_}, {", ".join(list)})')
CORE.add_global(obj)
CORE.register_variable(id_, obj)
return id_
def zigbee_new_cluster_list(id_: ID, *args):
assert isinstance(id_, ID)
list = []
for arg in args:
list.append(f"{arg}")
obj = cg.RawExpression(f'{id_.type}({id_}, {", ".join(list)})')
CORE.add_global(obj)
return id_
def zigbee_register_ep(id_: ID, cluster):
assert isinstance(id_, ID)
ep = len(CORE.data[KEY_ZIGBEE][KEY_EP]) + 1
CORE.data[KEY_ZIGBEE][KEY_EP] += [str(id_)]
obj = cg.RawExpression(f"{id_.type}({id_}, {ep}, {cluster})")
CORE.add_global(obj)
return ep

View file

@ -0,0 +1,115 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_STATE
from esphome.core import coroutine_with_priority
from .. import (
ZigbeeBaseSchema,
zigbee_assign,
zigbee_new_attr_list,
zigbee_new_cluster_list,
zigbee_new_variable,
zigbee_register_ep,
zigbee_set_string,
)
from ..const import (
CONF_BASIC_ATTRIB_LIST_EXT,
CONF_BINARY_ATTRS,
CONF_BINARY_INPUT_ATTRIB_LIST,
CONF_BINARY_INPUT_CLUSTER_LIST,
CONF_BINARY_INPUT_EP,
CONF_GROUPS_ATTRIB_LIST,
CONF_IDENTIFY_ATTRIB_LIST,
CONF_SCENES_ATTRIB_LIST,
CONF_ZIGBEE_ID,
BinaryAttrs,
esphome_zb_ha_declare_binary_input_ep,
zigbee_ns,
)
AUTO_LOAD = ["zigbee"]
ZigbeeBinarySensor = zigbee_ns.class_(
"ZigbeeBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONFIG_SCHEMA = (
binary_sensor.binary_sensor_schema(ZigbeeBinarySensor)
.extend(
{
cv.GenerateID(CONF_BINARY_ATTRS): cv.declare_id(BinaryAttrs),
cv.GenerateID(CONF_BINARY_INPUT_ATTRIB_LIST): cv.declare_id(
cg.global_ns.namespace(
"ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST"
)
),
cv.GenerateID(CONF_BINARY_INPUT_CLUSTER_LIST): cv.declare_id(
cg.global_ns.namespace(
"ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_CLUSTER_LIST"
)
),
cv.GenerateID(CONF_BINARY_INPUT_EP): cv.declare_id(
esphome_zb_ha_declare_binary_input_ep
),
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ZigbeeBaseSchema)
)
@coroutine_with_priority(50.0)
async def to_code(config):
binary_attrs = zigbee_new_variable(config[CONF_BINARY_ATTRS])
attr_list = zigbee_new_attr_list(
config[CONF_BINARY_INPUT_ATTRIB_LIST],
zigbee_assign(binary_attrs.out_of_service, 0),
zigbee_assign(binary_attrs.present_value, 0),
zigbee_assign(binary_attrs.status_flags, 0),
zigbee_set_string(binary_attrs.description, config[CONF_NAME]),
)
cluster = zigbee_new_cluster_list(
config[CONF_BINARY_INPUT_CLUSTER_LIST],
attr_list,
config[CONF_BASIC_ATTRIB_LIST_EXT],
config[CONF_IDENTIFY_ATTRIB_LIST],
config[CONF_GROUPS_ATTRIB_LIST],
config[CONF_SCENES_ATTRIB_LIST],
)
ep = zigbee_register_ep(config[CONF_BINARY_INPUT_EP], cluster)
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_LAMBDA], [], return_type=cg.optional.template(bool)
)
cg.add(var.set_template(template_))
cg.add(var.set_ep(ep))
cg.add(var.set_cluster_attributes(binary_attrs))
hub = await cg.get_variable(config[CONF_ZIGBEE_ID])
cg.add(var.set_parent(hub))
@automation.register_action(
"binary_sensor.zigbee.publish",
binary_sensor.BinarySensorPublishAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_STATE): cv.templatable(cv.boolean),
}
),
)
async def binary_sensor_template_publish_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_STATE], args, bool)
cg.add(var.set_state(template_))
return var

View file

@ -0,0 +1,58 @@
#include "zigbee_binary_sensor.h"
#ifdef USE_ZIGBEE
#include "esphome/core/log.h"
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
#include <zb_nrf_platform.h>
#include <zigbee/zigbee_app_utils.h>
#include <zb_error_to_string.h>
}
namespace esphome {
namespace zigbee {
static const char *const TAG = "zigbee.binary_sensor";
void ZigbeeBinarySensor::setup() {
add_on_state_callback([this](bool state) {
if (state) {
cluster_attributes_->present_value = 1;
} else {
cluster_attributes_->present_value = 0;
}
ESP_LOGD(TAG, "set attribute ep: %d, present_value %d", ep_, cluster_attributes_->present_value);
ZB_ZCL_SET_ATTRIBUTE(ep_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, &cluster_attributes_->present_value, ZB_FALSE);
this->parent_->flush();
});
if (!this->publish_initial_state_)
return;
if (this->f_ != nullptr) {
this->publish_initial_state(this->f_().value_or(false));
} else {
this->publish_initial_state(false);
}
}
void ZigbeeBinarySensor::loop() {
if (this->f_ == nullptr)
return;
auto s = this->f_();
if (s.has_value()) {
this->publish_state(*s);
}
}
void ZigbeeBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Zigbee Binary Sensor", this);
ESP_LOGCONFIG(TAG, " EP: %d", ep_);
}
void ZigbeeBinarySensor::set_parent(Zigbee *parent) { this->parent_ = parent; }
} // namespace zigbee
} // namespace esphome
#endif

View file

@ -0,0 +1,96 @@
#pragma once
#include "esphome/components/zigbee/zigbee_component.h"
#ifdef USE_ZIGBEE
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
}
#define ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_CLUSTER_LIST(cluster_list_name, binary_attr_list, basic_attr_list, \
identify_attr_list, groups_attr_list, scenes_attr_list) \
zb_zcl_cluster_desc_t cluster_list_name[] = { \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_ARRAY_SIZE(identify_attr_list, zb_zcl_attr_t), \
(identify_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_ARRAY_SIZE(basic_attr_list, zb_zcl_attr_t), \
(basic_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_ARRAY_SIZE(binary_attr_list, zb_zcl_attr_t), \
(binary_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_GROUPS, ZB_ZCL_ARRAY_SIZE(groups_attr_list, zb_zcl_attr_t), \
(groups_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_SCENES, ZB_ZCL_ARRAY_SIZE(scenes_attr_list, zb_zcl_attr_t), \
(scenes_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID)}
#define ESPHOME_ZB_HA_DEVICE_VER_SIMPLE_SENSOR 0
#define ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num) \
ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \
ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \
simple_desc_##ep_name = {ep_id, \
ZB_AF_HA_PROFILE_ID, \
ZB_HA_CUSTOM_ATTR_DEVICE_ID, \
ESPHOME_ZB_HA_DEVICE_VER_SIMPLE_SENSOR, \
0, \
in_clust_num, \
out_clust_num, \
{ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, \
ZB_ZCL_CLUSTER_ID_SCENES, ZB_ZCL_CLUSTER_ID_GROUPS}}
#define ESPHOME_ZB_HA_BINARY_INPUT_REPORT_ATTR_COUNT ZB_ZCL_BINARY_INPUT_REPORT_ATTR_COUNT
#define ESPHOME_ZB_HA_BINARY_INPUT_IN_CLUSTER_NUM 5 // server roles in ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_CLUSTER_LIST
#define ESPHOME_ZB_HA_BINARY_INPUT_OUT_CLUSTER_NUM 0 // client roles in ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_CLUSTER_LIST
#define ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_EP(ep_name, ep_id, cluster_list) \
ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_SIMPLE_DESC(ep_name, ep_id, ESPHOME_ZB_HA_BINARY_INPUT_IN_CLUSTER_NUM, \
ESPHOME_ZB_HA_BINARY_INPUT_OUT_CLUSTER_NUM); \
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, ESPHOME_ZB_HA_BINARY_INPUT_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, \
ESPHOME_ZB_HA_BINARY_INPUT_REPORT_ATTR_COUNT, reporting_info##ep_name, 0, NULL)
// it cannot have ESPHOME prefix since it is used outside
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID(data_ptr) \
{ \
ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \
(ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \
}
#define ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \
description) \
ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BINARY_INPUT) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, (present_value)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_STATUS_FLAG_ID, (status_flag)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, (description)) \
ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST
namespace esphome {
namespace zigbee {
class ZigbeeBinarySensor : public Component, public binary_sensor::BinarySensor {
public:
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
void setup() override;
void loop() override;
void dump_config() override;
void set_parent(Zigbee *parent);
void set_ep(zb_uint8_t ep) { this->ep_ = ep; }
void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; }
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
std::function<optional<bool>()> f_{nullptr};
zb_uint8_t ep_{0};
Zigbee *parent_{nullptr};
BinaryAttrs *cluster_attributes_{nullptr};
};
} // namespace zigbee
} // namespace esphome
#endif

View file

@ -0,0 +1,61 @@
import esphome.codegen as cg
zigbee_ns = cg.esphome_ns.namespace("zigbee")
Zigbee = zigbee_ns.class_("Zigbee", cg.Component)
zb_char_t_ptr = cg.global_ns.namespace("zb_char_t *")
CONF_ZIGBEE_ID = "zigbee_id"
CONF_SWITCH = "switch"
CONF_MAX_EP_NUMBER = 8
zb_zcl_basic_attrs_ext_t = cg.global_ns.namespace("zb_zcl_basic_attrs_ext_t")
zb_zcl_identify_attrs_t = cg.global_ns.namespace("zb_zcl_identify_attrs_t")
zb_zcl_groups_attrs_t = cg.global_ns.namespace("zb_zcl_groups_attrs_t")
zb_zcl_scenes_attrs_t = cg.global_ns.namespace("zb_zcl_scenes_attrs_t")
CONF_BASIC_ATTRS_EXT = "basic_attrs_ext"
CONF_IDENTIFY_ATTRS = "identify_attrs"
CONF_GROUPS_ATTRS = "groups_attrs"
CONF_SCENES_ATTRS = "scenes_attrs"
CONF_BINARY_ATTRS = "binary_attrs"
CONF_BASIC_ATTRIB_LIST_EXT = "basic_attrib_list_ext"
CONF_IDENTIFY_ATTRIB_LIST = "identify_attrib_list"
CONF_GROUPS_ATTRIB_LIST = "groups_attrib_list"
CONF_SCENES_ATTRIB_LIST = "scenes_attrib_list"
# it has to be class to make use_id work
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT = cg.global_ns.class_(
"ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT"
)
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST = cg.global_ns.class_(
"ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST"
)
ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST = cg.global_ns.class_(
"ZB_ZCL_DECLARE_GROUPS_ATTRIB_LIST"
)
ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST = cg.global_ns.class_(
"ZB_ZCL_DECLARE_SCENES_ATTRIB_LIST"
)
# input/output
BinaryAttrs = zigbee_ns.struct("BinaryAttrs")
# input
CONF_BINARY_INPUT_ATTRIB_LIST = "binary_input_attrib_list"
CONF_BINARY_INPUT_CLUSTER_LIST = "binary_input_cluster_list"
CONF_BINARY_INPUT_EP = "binary_input_ep"
esphome_zb_ha_declare_binary_input_ep = cg.global_ns.namespace(
"ESPHOME_ZB_HA_DECLARE_BINARY_INPUT_EP"
)
# output
CONF_BINARY_OUTPUT_ATTRIB_LIST = "binary_output_attrib_list"
CONF_BINARY_OUTPUT_CLUSTER_LIST = "binary_output_cluster_list"
CONF_BINARY_OUTPUT_EP = "binary_output_ep"
esphome_zb_ha_declare_binary_output_ep = cg.global_ns.namespace(
"ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_EP"
)

View file

@ -0,0 +1,110 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import output, switch
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME, CONF_OUTPUT, CONF_STATE
from esphome.core import coroutine_with_priority
from .. import (
ZigbeeBaseSchema,
zigbee_assign,
zigbee_new_attr_list,
zigbee_new_cluster_list,
zigbee_new_variable,
zigbee_register_ep,
zigbee_set_string,
)
from ..const import (
CONF_BASIC_ATTRIB_LIST_EXT,
CONF_BINARY_ATTRS,
CONF_BINARY_OUTPUT_ATTRIB_LIST,
CONF_BINARY_OUTPUT_CLUSTER_LIST,
CONF_BINARY_OUTPUT_EP,
CONF_GROUPS_ATTRIB_LIST,
CONF_IDENTIFY_ATTRIB_LIST,
CONF_SCENES_ATTRIB_LIST,
CONF_ZIGBEE_ID,
BinaryAttrs,
esphome_zb_ha_declare_binary_output_ep,
zigbee_ns,
)
AUTO_LOAD = ["zigbee"]
ZigbeeSwitch = zigbee_ns.class_("ZigbeeSwitch", switch.Switch, cg.Component)
CONFIG_SCHEMA = (
switch.switch_schema(ZigbeeSwitch)
.extend(
{
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.GenerateID(CONF_BINARY_ATTRS): cv.declare_id(BinaryAttrs),
cv.GenerateID(CONF_BINARY_OUTPUT_ATTRIB_LIST): cv.declare_id(
cg.global_ns.namespace(
"ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_ATTRIB_LIST"
)
),
cv.GenerateID(CONF_BINARY_OUTPUT_CLUSTER_LIST): cv.declare_id(
cg.global_ns.namespace(
"ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_CLUSTER_LIST"
)
),
cv.GenerateID(CONF_BINARY_OUTPUT_EP): cv.declare_id(
esphome_zb_ha_declare_binary_output_ep
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ZigbeeBaseSchema)
)
@coroutine_with_priority(50.0)
async def to_code(config):
binary_attrs = zigbee_new_variable(config[CONF_BINARY_ATTRS])
attr_list = zigbee_new_attr_list(
config[CONF_BINARY_OUTPUT_ATTRIB_LIST],
zigbee_assign(binary_attrs.out_of_service, 0),
zigbee_assign(binary_attrs.present_value, 0),
zigbee_assign(binary_attrs.status_flags, 0),
zigbee_set_string(binary_attrs.description, config[CONF_NAME]),
)
cluster = zigbee_new_cluster_list(
config[CONF_BINARY_OUTPUT_CLUSTER_LIST],
attr_list,
config[CONF_BASIC_ATTRIB_LIST_EXT],
config[CONF_IDENTIFY_ATTRIB_LIST],
config[CONF_GROUPS_ATTRIB_LIST],
config[CONF_SCENES_ATTRIB_LIST],
)
ep = zigbee_register_ep(config[CONF_BINARY_OUTPUT_EP], cluster)
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_ep(ep))
cg.add(var.set_cluster_attributes(binary_attrs))
hub = await cg.get_variable(config[CONF_ZIGBEE_ID])
cg.add(var.set_parent(hub))
@automation.register_action(
"switch.zigbee.publish",
switch.SwitchPublishAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(switch.Switch),
cv.Required(CONF_STATE): cv.templatable(cv.boolean),
}
),
)
async def switch_template_publish_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_STATE], args, bool)
cg.add(var.set_state(template_))
return var

View file

@ -0,0 +1,47 @@
#include "esphome/core/defines.h"
#ifdef USE_ZIGBEE
extern "C" {
#include "zboss_api.h"
#include "zcl/zb_zcl_common.h"
}
#include "zigbee_switch.h"
static zb_ret_t check_value_binary_output_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value);
void zb_zcl_binary_output_init_server(void) {
zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
check_value_binary_output_server, (zb_zcl_cluster_write_attr_hook_t) NULL,
(zb_zcl_cluster_handler_t) NULL);
}
void zb_zcl_binary_output_init_client(void) {
zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_CLIENT_ROLE,
(zb_zcl_cluster_check_value_t) NULL, (zb_zcl_cluster_write_attr_hook_t) NULL,
(zb_zcl_cluster_handler_t) NULL);
}
#define ZB_ZCL_BINARY_OUTPUT_STATUS_FLAG_MAX_VALUE 0x0F
static zb_ret_t check_value_binary_output_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value) {
zb_ret_t ret = RET_OK;
ZVUNUSED(endpoint);
switch (attr_id) {
case ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID:
case ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID:
ret = ZB_ZCL_CHECK_BOOL_VALUE(*value) ? RET_OK : RET_ERROR;
break;
case ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID:
if (*value > ZB_ZCL_BINARY_OUTPUT_STATUS_FLAG_MAX_VALUE) {
ret = RET_ERROR;
}
break;
default:
break;
}
return ret;
}
#endif

View file

@ -0,0 +1,93 @@
#include "zigbee_switch.h"
#ifdef USE_ZIGBEE
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
#include <zb_nrf_platform.h>
#include <zigbee/zigbee_app_utils.h>
#include <zb_error_to_string.h>
}
namespace esphome {
namespace zigbee {
static const char *const TAG = "zigbee_on_off.switch";
void ZigbeeSwitch::dump_config() {
LOG_SWITCH("", "Zigbee Switch", this);
ESP_LOGCONFIG(TAG, " EP: %d", ep_);
}
void ZigbeeSwitch::setup() {
add_on_state_callback([this](bool state) {
if (state) {
cluster_attributes_->present_value = 1;
} else {
cluster_attributes_->present_value = 0;
}
ESP_LOGD(TAG, "set attribute ep: %d, present_value %d", ep_, cluster_attributes_->present_value);
ZB_ZCL_SET_ATTRIBUTE(ep_, ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, &cluster_attributes_->present_value, ZB_FALSE);
this->parent_->flush();
});
bool initial_state = this->get_initial_state_with_restore_mode().value_or(false);
if (initial_state) {
this->turn_on();
} else {
this->turn_off();
}
}
void ZigbeeSwitch::write_state(bool state) {
if (state) {
this->output_->turn_on();
} else {
this->output_->turn_off();
}
this->publish_state(state);
}
void ZigbeeSwitch::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_BINARY_OUTPUT) {
uint8_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data8;
ESP_LOGI(TAG, "binary output attribute setting to %hd", value);
if (attr_id == ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_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);
}
void ZigbeeSwitch::set_parent(Zigbee *parent) {
this->parent_ = parent;
this->parent_->add_callback(this->ep_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); });
}
} // namespace zigbee
} // namespace esphome
#endif

View file

@ -0,0 +1,135 @@
#pragma once
#include "esphome/components/zigbee/zigbee_component.h"
#ifdef USE_ZIGBEE
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/output/binary_output.h"
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
}
#define ESPHOME_ZB_HA_DEVICE_VER_SIMPLE_OUTPUT 0 // TODO what to set here?
#define ZB_ZCL_BINARY_OUTPUT_CLUSTER_REVISION_DEFAULT ((zb_uint16_t) 0x0001u)
// NOLINTNEXTLINE(readability-identifier-naming)
enum zb_zcl_binary_output_attr_e {
ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID = 0x001C,
ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID = 0x0051,
ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID = 0x0055,
ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID = 0x006F,
};
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID(data_ptr) \
{ \
ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \
(void *) (data_ptr) \
}
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID(data_ptr) \
{ \
ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \
ZB_ZCL_ATTR_ACCESS_READ_WRITE | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \
(void *) (data_ptr) \
}
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID(data_ptr) \
{ \
ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID, ZB_ZCL_ATTR_TYPE_8BITMAP, \
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \
(void *) (data_ptr) \
}
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID(data_ptr) \
{ \
ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \
(ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \
}
#define ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num) \
ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \
ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \
simple_desc_##ep_name = {ep_id, \
ZB_AF_HA_PROFILE_ID, \
ZB_HA_CUSTOM_ATTR_DEVICE_ID, \
ESPHOME_ZB_HA_DEVICE_VER_SIMPLE_OUTPUT, \
0, \
in_clust_num, \
out_clust_num, \
{ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, \
ZB_ZCL_CLUSTER_ID_SCENES, ZB_ZCL_CLUSTER_ID_GROUPS}}
#define ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \
description) \
ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BINARY_OUTPUT) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID, (out_of_service)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, (present_value)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID, (status_flag)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, (description)) \
ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST
#define ESPHOME_ZB_HA_BINARY_OUTPUT_REPORT_ATTR_COUNT 2
#define ESPHOME_ZB_HA_BINARY_OUTPUT_IN_CLUSTER_NUM \
5 // server roles in ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_CLUSTER_LIST
#define ESPHOME_ZB_HA_BINARY_OUTPUT_OUT_CLUSTER_NUM \
0 // client roles in ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_CLUSTER_LIST
#define ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_EP(ep_name, ep_id, cluster_list) \
ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_SIMPLE_DESC(ep_name, ep_id, ESPHOME_ZB_HA_BINARY_OUTPUT_IN_CLUSTER_NUM, \
ESPHOME_ZB_HA_BINARY_OUTPUT_OUT_CLUSTER_NUM); \
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, ESPHOME_ZB_HA_BINARY_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, \
ESPHOME_ZB_HA_BINARY_OUTPUT_REPORT_ATTR_COUNT, reporting_info##ep_name, 0, NULL)
#define ESPHOME_ZB_HA_DECLARE_BINARY_OUTPUT_CLUSTER_LIST(cluster_list_name, binary_attr_list, basic_attr_list, \
identify_attr_list, groups_attr_list, scenes_attr_list) \
zb_zcl_cluster_desc_t cluster_list_name[] = { \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_IDENTIFY, ZB_ZCL_ARRAY_SIZE(identify_attr_list, zb_zcl_attr_t), \
(identify_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_BASIC, ZB_ZCL_ARRAY_SIZE(basic_attr_list, zb_zcl_attr_t), \
(basic_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_ARRAY_SIZE(binary_attr_list, zb_zcl_attr_t), \
(binary_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_GROUPS, ZB_ZCL_ARRAY_SIZE(groups_attr_list, zb_zcl_attr_t), \
(groups_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_SCENES, ZB_ZCL_ARRAY_SIZE(scenes_attr_list, zb_zcl_attr_t), \
(scenes_attr_list), ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_MANUF_CODE_INVALID)}
void zb_zcl_binary_output_init_server();
void zb_zcl_binary_output_init_client();
#define ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT_SERVER_ROLE_INIT zb_zcl_binary_output_init_server
#define ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT_CLIENT_ROLE_INIT zb_zcl_binary_output_init_client
namespace esphome {
namespace zigbee {
class ZigbeeSwitch : public switch_::Switch, public Component {
public:
void set_output(output::BinaryOutput *output) { this->output_ = output; }
void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; }
void set_ep(zb_uint8_t ep) { this->ep_ = ep; }
void setup() override;
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
void set_parent(Zigbee *parent);
protected:
void write_state(bool state) override;
void zcl_device_cb_(zb_bufid_t bufid);
output::BinaryOutput *output_;
BinaryAttrs *cluster_attributes_ = nullptr;
zb_uint8_t ep_{0};
Zigbee *parent_{nullptr};
};
} // namespace zigbee
} // namespace esphome
#endif

View file

@ -0,0 +1,161 @@
#include "zigbee_component.h"
#ifdef USE_ZIGBEE
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
#include <zb_nrf_platform.h>
#include <zigbee/zigbee_app_utils.h>
#include <zb_error_to_string.h>
}
namespace esphome {
namespace zigbee {
static const char *const TAG = "zigbee";
Zigbee *global_zigbee = nullptr;
#define IEEE_ADDR_BUF_SIZE 17
void Zigbee::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);
if (status == RET_OK) {
// this is from wrong thread
this->join_trigger_->trigger();
}
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;
default:
ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status);
break;
}
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));
}
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') {
// called from wrong thread
this->join_trigger_->trigger();
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 Zigbee::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();
}
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 Zigbee::flush() { need_flush_ = true; }
void Zigbee::loop() {
if (need_flush_) {
need_flush_ = false;
zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0);
}
}
void Zigbee::factory_reset() {
ESP_LOGD(TAG, "factory reset");
ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0);
}
} // namespace zigbee
} // namespace esphome
extern "C" void zboss_signal_handler(zb_bufid_t bufid) {
esphome::zigbee::global_zigbee->zboss_signal_handler_esphome(bufid);
}
#endif

View file

@ -0,0 +1,65 @@
#pragma once
#include <map>
#include "esphome/core/defines.h"
#ifdef USE_ZIGBEE
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
}
#define ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clusters_count, out_clusters_count) \
typedef ZB_PACKED_PRE struct zb_af_simple_desc_##ep_name##_##out_clusters_count##_##out_clusters_count##_s { \
zb_uint8_t endpoint; /* Endpoint */ \
zb_uint16_t app_profile_id; /* Application profile identifier */ \
zb_uint16_t app_device_id; /* Application device identifier */ \
zb_bitfield_t app_device_version : 4; /* Application device version */ \
zb_bitfield_t reserved : 4; /* Reserved */ \
zb_uint8_t app_input_cluster_count; /* Application input cluster count */ \
zb_uint8_t app_output_cluster_count; /* Application output cluster count */ \
/* Application input and output cluster list */ \
zb_uint16_t app_cluster_list[(in_clusters_count) + (out_clusters_count)]; \
} ZB_PACKED_STRUCT zb_af_simple_desc_##ep_name##_##in_clusters_count##_##out_clusters_count##_t
#define ESPHOME_CAT7(a, b, c, d, e, f, g) a##b##c##d##e##f##g
#define ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_num, out_num) \
ESPHOME_CAT7(zb_af_simple_desc_, ep_name, _, in_num, _, out_num, _t)
namespace esphome {
namespace zigbee {
struct BinaryAttrs {
zb_bool_t out_of_service;
zb_bool_t present_value;
zb_uint8_t status_flags;
zb_uchar_t description[32]; // TODO it could be in progmem, max is ZB_ZCL_MAX_STRING_SIZE
};
class Zigbee : public Component {
public:
void setup() override;
void add_callback(zb_uint8_t endpoint, std::function<void(zb_bufid_t bufid)> cb) { callbacks_[endpoint] = cb; }
void zboss_signal_handler_esphome(zb_bufid_t bufid);
void factory_reset();
Trigger<> *get_join_trigger() const { return this->join_trigger_; };
void flush();
void loop() override;
protected:
static void zcl_device_cb(zb_bufid_t bufid);
std::map<zb_uint8_t, std::function<void(zb_bufid_t bufid)>> callbacks_;
Trigger<> *join_trigger_{new Trigger<>()};
bool need_flush_{false};
};
extern Zigbee *global_zigbee; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class FactoryResetAction : public Action<Ts...> {
public:
void play(Ts... x) override { global_zigbee->factory_reset(); }
};
} // namespace zigbee
} // namespace esphome
#endif

View file

@ -0,0 +1,22 @@
import esphome.codegen as cg
from esphome.core import CORE, coroutine_with_priority
KEY_ZIGBEE = "zigbee"
KEY_EP = "ep"
def zigbee_set_core_data(config):
CORE.data[KEY_ZIGBEE] = {}
CORE.data[KEY_ZIGBEE][KEY_EP] = []
return config
@coroutine_with_priority(10.0)
async def to_code(config):
if len(CORE.data[KEY_ZIGBEE][KEY_EP]) > 0:
cg.add_global(
cg.RawExpression(
f"ZBOSS_DECLARE_DEVICE_CTX_EP_VA(zb_device_ctx, &{', &'.join(CORE.data[KEY_ZIGBEE][KEY_EP])})"
)
)
cg.add(cg.RawExpression("ZB_AF_REGISTER_DEVICE_CTX(&zb_device_ctx)"))

View file

@ -0,0 +1,41 @@
---
switch:
- platform: zigbee
output: output_template
id: zigbee_switch_1
- platform: zigbee
output: output_template
id: zigbee_switch_2
output:
- platform: template
id: output_template
type: binary
write_action:
- binary_sensor.zigbee.publish:
id: zigbee_binary_sensor_1
state: ON
- binary_sensor.zigbee.publish:
id: zigbee_binary_sensor_1
state: OFF
- platform: template
id: output_factory
type: binary
write_action:
- zigbee.factory_reset
binary_sensor:
- platform: zigbee
id: zigbee_binary_sensor_1
- platform: zigbee
id: zigbee_binary_sensor_2
lambda: return true;
zigbee:
on_join:
- switch.zigbee.publish:
id: zigbee_switch_1
state: OFF
- switch.zigbee.publish:
id: zigbee_switch_1
state: ON

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml