1
0
mirror of https://github.com/esphome/esphome.git synced 2025-06-15 14:56:59 +02:00

Merge 77099599ea61939d06a7b16fbe687fdf0aa65c35 into 07cf6e723bdd93a738bfc033ad9a932c1bf74b09

This commit is contained in:
tomaszduda23 2025-06-14 23:46:04 -05:00 committed by GitHub
commit 9c56a807eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1554 additions and 0 deletions

View File

@ -0,0 +1,309 @@
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_NUMBER,
KEY_ZIGBEE,
zigbee_set_core_data,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID, __version__
from esphome.core import CORE, ID, coroutine_with_priority
from esphome.cpp_generator import (
AssignmentExpression,
MockObj,
VariableDeclarationExpression,
)
from .const import (
CONF_BASIC_ATTRIB_LIST_EXT,
CONF_BASIC_ATTRS_EXT,
CONF_CLUSTER_LIST,
CONF_EP,
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_ZIGBEE_ID,
ESPHOME_ZB_HA_DECLARE_EP,
ZB_ZCL_CLUSTER_ID_BASIC,
ZB_ZCL_CLUSTER_ID_GROUPS,
ZB_ZCL_CLUSTER_ID_IDENTIFY,
ZB_ZCL_CLUSTER_ID_SCENES,
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
),
cv.GenerateID(CONF_EP): cv.declare_id(ESPHOME_ZB_HA_DECLARE_EP),
cv.GenerateID(CONF_CLUSTER_LIST): cv.declare_id(
cg.global_ns.namespace("zb_zcl_cluster_desc_t")
),
},
)
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 validate_number_of_ep(config):
count = len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])
if count > CONF_MAX_EP_NUMBER:
raise cv.Invalid(f"Maximum number of EP is {CONF_MAX_EP_NUMBER}")
if count == 0:
raise cv.Invalid("At least one zigbee device need to be included")
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) or (
str(arg) == "zb_zcl_time_attrs_t_id"
):
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_
class ArrayAssignmentExpression(AssignmentExpression):
__slots__ = ()
def __init__(self, type_, name, rhs):
super().__init__(type_, "", name, rhs)
def __str__(self):
return f"{self.type} {self.name}[] = {self.rhs}"
def zigbee_array(id_, rhs) -> "MockObj":
rhs = cg.safe_exp(rhs)
obj = MockObj(id_, ".")
assignment = ArrayAssignmentExpression(id_.type, id_, rhs)
CORE.add_global(assignment)
CORE.register_variable(id_, obj)
return obj
class ZigbeeClusterDesc(MockObj):
def __init__(self, name: str, attr=None):
self.name = name
self.attr = attr
def __str__(self):
role = (
"ZB_ZCL_CLUSTER_SERVER_ROLE" if self.attr else "ZB_ZCL_CLUSTER_CLIENT_ROLE"
)
attr_count = "0"
attr_desc_list = "NULL"
if self.attr:
attr_count = f"ZB_ZCL_ARRAY_SIZE({self.attr}, zb_zcl_attr_t)"
attr_desc_list = str(self.attr)
return f"ZB_ZCL_CLUSTER_DESC({self.name}, {attr_count}, {attr_desc_list}, {role}, ZB_ZCL_MANUF_CODE_INVALID)"
def zigbee_new_cluster_list(config, attr_list):
rhs = [
ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BASIC, config[CONF_BASIC_ATTRIB_LIST_EXT]),
ZigbeeClusterDesc(
ZB_ZCL_CLUSTER_ID_IDENTIFY, config[CONF_IDENTIFY_ATTRIB_LIST]
),
]
rhs.extend([attr_list[0]])
rhs.extend(
[
ZigbeeClusterDesc(
ZB_ZCL_CLUSTER_ID_GROUPS, config[CONF_GROUPS_ATTRIB_LIST]
),
ZigbeeClusterDesc(
ZB_ZCL_CLUSTER_ID_SCENES, config[CONF_SCENES_ATTRIB_LIST]
),
]
)
if len(attr_list) == 2:
rhs.extend([attr_list[1]])
obj = zigbee_array(config[CONF_CLUSTER_LIST], rhs)
return (obj, rhs)

View File

@ -0,0 +1,105 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.components.zigbee_ctx import (
KEY_EP_NUMBER,
consume_ep_slots,
zigbee_register_ep,
)
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,
ZigbeeClusterDesc,
zigbee_assign,
zigbee_new_attr_list,
zigbee_new_cluster_list,
zigbee_new_variable,
zigbee_set_string,
)
from ..const import (
CONF_BINARY_ATTRS,
CONF_BINARY_INPUT_ATTRIB_LIST,
CONF_ZIGBEE_ID,
ZB_ZCL_CLUSTER_ID_BINARY_INPUT,
BinaryAttrs,
zigbee_ns,
)
AUTO_LOAD = ["zigbee"]
ZigbeeBinarySensor = zigbee_ns.class_(
"ZigbeeBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONFIG_SCHEMA = cv.All(
(
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.Optional(CONF_LAMBDA): cv.returning_lambda,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ZigbeeBaseSchema)
),
consume_ep_slots,
)
@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_id, clusters = zigbee_new_cluster_list(
config, [ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BINARY_INPUT, attr_list)]
)
zigbee_register_ep(config, cluster_id, 2, clusters)
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(config[KEY_EP_NUMBER]))
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,56 @@
#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_);
}
} // namespace zigbee
} // namespace esphome
#endif

View File

@ -0,0 +1,47 @@
#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 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 ZigbeeEntity, public binary_sensor::BinarySensor, public Component {
public:
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
std::function<optional<bool>()> f_{nullptr};
BinaryAttrs *cluster_attributes_{nullptr};
};
} // namespace zigbee
} // namespace esphome
#endif

View File

@ -0,0 +1,70 @@
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
CONF_EP = "ep"
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_TIME_ATTRS = "time_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"
# output
CONF_BINARY_OUTPUT_ATTRIB_LIST = "binary_output_attrib_list"
# time
CONF_TIME_ATTRIB_LIST = "time_attrib_list"
# general
ESPHOME_ZB_HA_DECLARE_EP = cg.global_ns.namespace("ESPHOME_ZB_HA_DECLARE_EP")
CONF_CLUSTER_LIST = "cluster_list"
# clusters
ZB_ZCL_CLUSTER_ID_BASIC = "ZB_ZCL_CLUSTER_ID_BASIC"
ZB_ZCL_CLUSTER_ID_IDENTIFY = "ZB_ZCL_CLUSTER_ID_IDENTIFY"
ZB_ZCL_CLUSTER_ID_GROUPS = "ZB_ZCL_CLUSTER_ID_GROUPS"
ZB_ZCL_CLUSTER_ID_SCENES = "ZB_ZCL_CLUSTER_ID_SCENES"
ZB_ZCL_CLUSTER_ID_BINARY_INPUT = "ZB_ZCL_CLUSTER_ID_BINARY_INPUT"
ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT = "ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT"
ZB_ZCL_CLUSTER_ID_TIME = "ZB_ZCL_CLUSTER_ID_TIME"

View File

@ -0,0 +1,97 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import output, switch
from esphome.components.zigbee_ctx import (
KEY_EP_NUMBER,
consume_ep_slots,
zigbee_register_ep,
)
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,
ZigbeeClusterDesc,
zigbee_assign,
zigbee_new_attr_list,
zigbee_new_cluster_list,
zigbee_new_variable,
zigbee_set_string,
)
from ..const import (
CONF_BINARY_ATTRS,
CONF_BINARY_OUTPUT_ATTRIB_LIST,
CONF_ZIGBEE_ID,
ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT,
BinaryAttrs,
zigbee_ns,
)
AUTO_LOAD = ["zigbee"]
ZigbeeSwitch = zigbee_ns.class_("ZigbeeSwitch", switch.Switch, cg.Component)
CONFIG_SCHEMA = cv.All(
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"
)
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ZigbeeBaseSchema),
consume_ep_slots,
)
@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_id, clusters = zigbee_new_cluster_list(
config, [ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, attr_list)]
)
zigbee_register_ep(config, cluster_id, 2, clusters)
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(config[KEY_EP_NUMBER]))
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,89 @@
#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() {
this->parent_->add_callback(this->ep_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); });
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) {
this->parent_->schedule([this, value]() { 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
} // namespace esphome
#endif

View File

@ -0,0 +1,88 @@
#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 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_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
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 ZigbeeEntity, 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 setup() override;
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
protected:
void write_state(bool state) override;
void zcl_device_cb_(zb_bufid_t bufid);
output::BinaryOutput *output_;
BinaryAttrs *cluster_attributes_ = nullptr;
};
} // namespace zigbee
} // namespace esphome
#endif

View File

@ -0,0 +1,74 @@
import esphome.codegen as cg
from esphome.components import time as time_
from esphome.components.zigbee_ctx import (
KEY_EP_NUMBER,
consume_ep_slots,
zigbee_register_ep,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import coroutine_with_priority
from .. import (
ZigbeeBaseSchema,
ZigbeeClusterDesc,
zigbee_new_attr_list,
zigbee_new_cluster_list,
zigbee_new_variable,
)
from ..const import (
CONF_TIME_ATTRIB_LIST,
CONF_TIME_ATTRS,
CONF_ZIGBEE_ID,
ZB_ZCL_CLUSTER_ID_TIME,
zigbee_ns,
)
AUTO_LOAD = ["zigbee"]
ZigbeeTime = zigbee_ns.class_("ZigbeeTime", time_.RealTimeClock)
CONFIG_SCHEMA = cv.All(
time_.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ZigbeeTime),
cv.GenerateID(CONF_TIME_ATTRS): cv.declare_id(
cg.global_ns.struct("zb_zcl_time_attrs_t")
),
cv.GenerateID(CONF_TIME_ATTRIB_LIST): cv.declare_id(
cg.global_ns.namespace("ZB_ZCL_DECLARE_TIME_ATTR_LIST")
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ZigbeeBaseSchema)
.extend(cv.polling_component_schema("1s")),
consume_ep_slots,
)
@coroutine_with_priority(50.0)
async def to_code(config):
time_attrs = zigbee_new_variable(config[CONF_TIME_ATTRS])
attr_list = zigbee_new_attr_list(
config[CONF_TIME_ATTRIB_LIST],
time_attrs,
)
cluster_id, clusters = zigbee_new_cluster_list(
config,
[
ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_TIME, attr_list),
ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_TIME),
],
)
zigbee_register_ep(config, cluster_id, 0, clusters)
var = cg.new_Pvariable(config[CONF_ID])
await time_.register_time(var, config)
await cg.register_component(var, config)
cg.add(var.set_ep(config[KEY_EP_NUMBER]))
cg.add(var.set_cluster_attributes(time_attrs))
hub = await cg.get_variable(config[CONF_ZIGBEE_ID])
cg.add(var.set_parent(hub))

View File

@ -0,0 +1,87 @@
#include "zigbee_time.h"
#ifdef USE_ZIGBEE
#include "esphome/core/log.h"
namespace esphome {
namespace zigbee {
static const char *const TAG = "zigbee.time";
// This time standard is the number of
// seconds since 0 hrs 0 mins 0 sec on 1st January 2000 UTC (Universal Coordinated Time).
constexpr time_t EPOCH_2000 = 946684800;
ZigbeeTime *global_time = nullptr;
void zb_zcl_time_sync_time_server_found_cb(zb_ret_t status, zb_uint32_t auth_level, zb_uint16_t short_addr,
zb_uint8_t ep, zb_uint32_t nw_time) {
if (status == RET_OK && auth_level >= ZB_ZCL_TIME_HAS_SYNCHRONIZED_BIT) {
global_time->set_epoch_time(nw_time + EPOCH_2000);
} else {
ESP_LOGE(TAG, "status: %d, auth_level: %u, short_addr: %d, ep: %d, nw_time: %u", status, auth_level, short_addr, ep,
nw_time);
}
}
void ZigbeeTime::setup() {
global_time = this;
this->parent_->add_callback(this->ep_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); });
synchronize_epoch_(EPOCH_2000);
parent_->add_join_callback([this]() { zb_zcl_time_server_synchronize(ep_, zb_zcl_time_sync_time_server_found_cb); });
}
void ZigbeeTime::dump_config() {
ESP_LOGCONFIG(TAG, "Zigbee Time");
ESP_LOGCONFIG(TAG, " EP: %d", ep_);
}
void ZigbeeTime::update() {
time_t time = timestamp_now();
cluster_attributes_->time = time - EPOCH_2000;
}
void ZigbeeTime::set_epoch_time(uint32_t epoch) {
this->parent_->schedule([this, epoch]() {
this->synchronize_epoch_(epoch);
has_time_ = true;
});
}
void ZigbeeTime::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_TIME) {
if (attr_id == ZB_ZCL_ATTR_TIME_TIME_ID) {
zb_uint32_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data32;
ESP_LOGI(TAG, "binary output attribute setting to %u", value);
synchronize_epoch_(value + EPOCH_2000);
} else if (attr_id == ZB_ZCL_ATTR_TIME_TIME_STATUS_ID) {
zb_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);
has_time_ = ZB_ZCL_TIME_TIME_STATUS_SYNCHRONIZED_BIT_IS_SET(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
} // namespace esphome
#endif

View File

@ -0,0 +1,39 @@
#pragma once
#include "esphome/components/zigbee/zigbee_component.h"
#ifdef USE_ZIGBEE
#include "esphome/core/component.h"
#include "esphome/components/time/real_time_clock.h"
extern "C" {
#include <zboss_api.h>
#include <zboss_api_addons.h>
}
namespace esphome {
namespace zigbee {
class ZigbeeTime : public time::RealTimeClock, public ZigbeeEntity {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
void update() override;
void set_cluster_attributes(zb_zcl_time_attrs_t &cluster_attributes) {
this->cluster_attributes_ = &cluster_attributes;
}
void set_epoch_time(uint32_t epoch);
protected:
void zcl_device_cb_(zb_bufid_t bufid);
zb_zcl_time_attrs_t *cluster_attributes_{nullptr};
bool has_time_{false};
};
} // namespace zigbee
} // namespace esphome
#endif

View File

@ -0,0 +1,202 @@
#include "zigbee_component.h"
#ifdef USE_ZIGBEE
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
#include <zephyr/storage/flash_map.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) {
on_join();
}
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 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') {
on_join();
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::on_join() {
this->schedule([this]() {
ESP_LOGD(TAG, "joined the network");
this->join_trigger_->trigger();
if (this->join_cb_) {
this->join_cb_();
}
});
}
void _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);
}
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;
}
#if 0
_erase_flash(FIXED_PARTITION_ID(ZBOSS_NVRAM));
_erase_flash(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG));
_erase_flash(FIXED_PARTITION_ID(SETTINGS_STORAGE));
#endif
/* 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);
}
std::function<void()> fn;
mutex_.lock();
if (!to_schedule_.empty()) {
fn = std::move(to_schedule_.front());
to_schedule_.pop_front();
}
mutex_.unlock();
if (fn) {
fn();
}
}
void Zigbee::schedule(std::function<void()> &&f) {
mutex_.lock();
to_schedule_.push_back(std::move(f));
mutex_.unlock();
}
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,99 @@
#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>
}
#include <deque>
#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)
#define ESPHOME_ZB_ZCL_DECLARE_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, 0, 0, in_clust_num, \
out_clust_num, {__VA_ARGS__}}
#define ESPHOME_ZB_HA_DECLARE_EP(ep_name, ep_id, cluster_list, in_cluster_num, out_cluster_num, report_attr_count, \
...) \
ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, __VA_ARGS__); \
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, 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, report_attr_count, \
reporting_info##ep_name, 0, NULL)
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) { this->callbacks_[endpoint] = cb; }
void add_join_callback(std::function<void()> cb) { this->join_cb_ = 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;
void schedule(std::function<void()> &&f);
protected:
static void zcl_device_cb(zb_bufid_t bufid);
void on_join();
std::map<zb_uint8_t, std::function<void(zb_bufid_t bufid)>> callbacks_;
std::function<void()> join_cb_;
Trigger<> *join_trigger_{new Trigger<>()};
bool need_flush_{false};
std::deque<std::function<void()>> to_schedule_;
Mutex mutex_;
};
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(); }
};
class ZigbeeEntity {
public:
void set_parent(Zigbee *parent) { this->parent_ = parent; }
void set_ep(zb_uint8_t ep) { this->ep_ = ep; }
protected:
zb_uint8_t ep_{0};
Zigbee *parent_{nullptr};
};
} // namespace zigbee
} // namespace esphome
#endif

View File

@ -0,0 +1,62 @@
from collections.abc import MutableMapping
from typing import Any
import esphome.codegen as cg
from esphome.components.zephyr import zephyr_add_pm_static
from esphome.components.nrf52.boards import BOOTLOADER_CONFIG, Section
from esphome.components.zephyr.const import KEY_BOOTLOADER, KEY_ZEPHYR
from esphome.core import CORE, coroutine_with_priority
KEY_ZIGBEE = "zigbee"
KEY_EP_NUMBER = "ep_number"
CONF_EP = "ep"
def zigbee_set_core_data(config):
if CORE.data[KEY_ZEPHYR][KEY_BOOTLOADER] in BOOTLOADER_CONFIG:
zephyr_add_pm_static(
[Section("empty_after_zboss_offset", 0xF4000, 0xC000, "flash_primary")]
)
return config
def consume_ep_slots(config: MutableMapping) -> MutableMapping:
data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {})
slots: list[str] = data.setdefault(KEY_EP_NUMBER, [])
slots.extend([""])
config[KEY_EP_NUMBER] = len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])
return config
def zigbee_register_ep(
config,
cluster_id,
report_attr_count: int,
clusters,
):
id_ = config[CONF_EP]
in_cluster_num = 0
out_cluster_num = 0
attrs = []
for c in clusters:
if c.attr:
in_cluster_num += 1
else:
out_cluster_num += 1
attrs.append(c.name)
CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER][config[KEY_EP_NUMBER] - 1] = str(id_)
obj = cg.RawExpression(
f"{id_.type}({id_}, {config[KEY_EP_NUMBER]}, {cluster_id}, {in_cluster_num}, {out_cluster_num}, {report_attr_count}, {', '.join(attrs)})"
)
CORE.add_global(obj)
@coroutine_with_priority(10.0)
async def to_code(config):
cg.add_global(
cg.RawExpression(
f"ZBOSS_DECLARE_DEVICE_CTX_EP_VA(zb_device_ctx, &{', &'.join(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})"
)
)
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

View File

@ -0,0 +1,20 @@
external_components:
- source: github://pr#7049
components: [logger, nrf52, time, zephyr]
refresh: always
esphome:
name: componenttestnrf52
friendly_name: $component_name
nrf52:
board: adafruit_itsybitsy_nrf52840
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -0,0 +1,20 @@
external_components:
- source: github://pr#7049
components: [logger, nrf52, time, zephyr]
refresh: always
esphome:
name: componenttestnrf52
friendly_name: $component_name
nrf52:
board: adafruit_feather_nrf52840
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file