[zigbee] Implement switch support for esp32 (#17579)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
luar123
2026-09-30 22:45:50 +00:00
committed by GitHub
co-authored by Copilot Autofix powered by AI Jonathan Swoboda pre-commit-ci-lite[bot]
parent cb47dfcf0e
commit 568c1ae494
11 changed files with 349 additions and 41 deletions
+60 -32
View File
@@ -43,6 +43,7 @@ from .zigbee_esp32 import (
final_validate_esp32,
validate_binary_sensor_esp32,
validate_sensor_esp32,
validate_switch_esp32,
zigbee_require_vfs_select,
)
from .zigbee_zephyr import (
@@ -67,34 +68,58 @@ def _check_report_deprecation(value: str) -> str:
return value
BASE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_REPORT): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
_check_report_deprecation,
cv.enum(REPORT, lower=True),
),
cv.OnlyWith(CONF_CLUSTER, ["esp32", "zigbee"], default="generic"): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.one_of(*["generic", "device_class"], lower=True),
),
cv.Optional(CONF_ENDPOINT): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.int_range(1, CONF_MAX_EP_NUMBER),
),
cv.Optional(CONF_USE_DEVICE_TYPE): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.boolean,
),
}
def _get_base_schema(cluster_options: list[str] | None = None) -> cv.Schema:
schema = cv.Schema(
{
cv.Optional(CONF_REPORT): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
_check_report_deprecation,
cv.enum(REPORT, lower=True),
),
cv.Optional(CONF_ENDPOINT): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.int_range(1, CONF_MAX_EP_NUMBER),
),
cv.Optional(CONF_USE_DEVICE_TYPE): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.boolean,
),
}
)
if cluster_options:
schema = cv.Schema(
{
cv.OnlyWith(
CONF_CLUSTER, ["esp32", "zigbee"], default=cluster_options[0]
): cv.All(
cv.requires_component("zigbee"),
cv.requires_component("esp32"),
cv.one_of(*cluster_options, lower=True),
),
}
).extend(schema)
return schema
# set BASE_SCHEMA for CI and backwards compatibility
BASE_SCHEMA = _get_base_schema()
BINARY_SENSOR_SCHEMA = (
cv.Schema({})
.extend(_get_base_schema(["generic", "device_class"]))
.extend(zephyr_binary_sensor)
)
SENSOR_SCHEMA = (
cv.Schema({})
.extend(_get_base_schema(["generic", "device_class"]))
.extend(zephyr_sensor)
)
SWITCH_SCHEMA = (
cv.Schema({}).extend(_get_base_schema(["generic", "on_off"])).extend(zephyr_switch)
)
BINARY_SENSOR_SCHEMA = cv.Schema({}).extend(BASE_SCHEMA).extend(zephyr_binary_sensor)
SENSOR_SCHEMA = cv.Schema({}).extend(BASE_SCHEMA).extend(zephyr_sensor)
SWITCH_SCHEMA = cv.Schema({}).extend(zephyr_switch)
NUMBER_SCHEMA = cv.Schema({}).extend(zephyr_number)
@@ -226,12 +251,15 @@ async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None:
async def setup_switch(entity: cg.MockObj, config: ConfigType) -> None:
if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL):
if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL):
return
if CORE.using_zephyr:
from .zigbee_zephyr import zephyr_setup_switch
await zephyr_setup_switch(entity, config)
if not config.get(CONF_ZIGBEE_ID):
return
from .zigbee_zephyr import add_switch
else:
from .zigbee_esp32 import add_component as add_switch
CORE.add_job(add_switch, entity, config)
async def setup_number(
@@ -284,7 +312,7 @@ def validate_switch(config: ConfigType) -> ConfigType:
if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL):
return config
if CORE.is_esp32:
return config
return validate_switch_esp32(config)
return consume_endpoint(config)
+4
View File
@@ -14,7 +14,9 @@ KEY_ZIGBEE_EP_NO_NUM = "zigbee_ep_no_num"
KEY_ZIGBEE_FIRST_EP_CL = "zigbee_first_ep_cl"
DEVICE_ID = {
"ON_OFF_OUTPUT": cg.RawExpression("EZB_ZHA_ON_OFF_OUTPUT_DEVICE_ID"),
"RANGE_EXTENDER": cg.RawExpression("EZB_ZHA_RANGE_EXTENDER_DEVICE_ID"),
"MAINS_POWER_OUTLET": cg.RawExpression("EZB_ZHA_MAINS_POWER_OUTLET_DEVICE_ID"),
"SIMPLE_SENSOR": cg.RawExpression("EZB_ZHA_SIMPLE_SENSOR_DEVICE_ID"),
"LIGHT_SENSOR": cg.RawExpression("EZB_ZHA_LIGHT_SENSOR_DEVICE_ID"),
"OCCUPANCY_SENSOR": cg.RawExpression("EZB_ZHA_OCCUPANCY_SENSOR_DEVICE_ID"),
@@ -26,8 +28,10 @@ DEVICE_ID = {
cluster_id = cg.esphome_ns.enum("ezb_zcl_cluster_id_e")
CLUSTER_ID = {
"BASIC": cluster_id.EZB_ZCL_CLUSTER_ID_BASIC,
"ON_OFF": cluster_id.EZB_ZCL_CLUSTER_ID_ON_OFF,
"TIME": cluster_id.EZB_ZCL_CLUSTER_ID_TIME,
"BINARY_INPUT": cluster_id.EZB_ZCL_CLUSTER_ID_BINARY_INPUT,
"BINARY_OUTPUT": cluster_id.EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT,
"ANALOG_INPUT": cluster_id.EZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
"ILLUMINANCE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT,
"TEMPERATURE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT,
@@ -20,6 +20,9 @@
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
namespace esphome::zigbee {
@@ -47,12 +50,23 @@ class ZigbeeAttribute final : public Component {
template<typename T> void set_attr(const T &value);
uint8_t attr_type() { return attr_type_; }
void set_report(ZigbeeReportT report);
template<typename F> void add_on_value_callback(F &&callback) { on_value_callback_.add(std::forward<F>(callback)); }
void on_value(ezb_zcl_attribute_t attribute) {
if (attribute.data.type == this->attr_type() && attribute.data.value) {
this->on_value_callback_.call(attribute);
}
}
#ifdef USE_SENSOR
template<typename T> void connect(sensor::Sensor *sensor);
template<typename T, typename F> void connect(sensor::Sensor *sensor, F &&f);
#endif
#ifdef USE_BINARY_SENSOR
template<typename T> void connect(binary_sensor::BinarySensor *sensor);
#endif
#ifdef USE_SWITCH
template<typename T> void connect(switch_::Switch *device);
#endif
bool report_enabled = false;
@@ -71,6 +85,7 @@ class ZigbeeAttribute final : public Component {
bool set_attr_requested_{false};
bool report_requested_{false};
bool force_report_{false};
LazyCallbackManager<void(ezb_zcl_attribute_t attribute)> on_value_callback_{};
template<typename T> T scale_value_(float value);
template<typename T> T invalid_value_();
};
@@ -135,6 +150,13 @@ template<typename T> void ZigbeeAttribute::connect(binary_sensor::BinarySensor *
sensor->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); });
}
#endif
#ifdef USE_SWITCH
template<typename T> void ZigbeeAttribute::connect(switch_::Switch *device) {
this->add_on_value_callback(
[device](ezb_zcl_attribute_t attribute) { device->control(*(T *) attribute.data.value); });
device->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); });
}
#endif
} // namespace esphome::zigbee
@@ -108,6 +108,35 @@ BINARY_INPUT_EP = {
],
}
BINARY_OUTPUT_EP = {
CONF_CLUSTERS: [
{
CONF_ID: "BINARY_OUTPUT",
ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x55,
CONF_TYPE: "BOOL",
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
CONNECT: True,
},
{
CONF_ATTRIBUTE_ID: 0x51,
CONF_TYPE: "BOOL",
},
{
CONF_ATTRIBUTE_ID: 0x6F,
CONF_TYPE: "MAP8",
},
{
CONF_ATTRIBUTE_ID: 0x1C,
CONF_TYPE: "STRING",
},
],
},
],
}
def _pressure_ep(device_type: bool = False) -> dict[str, Any]:
ep = {
@@ -300,6 +329,26 @@ BINARY_SENSOR_EP_CONFIGS: dict[str, dict[str, Any]] = {
},
}
SWITCH_EP_CONFIGS: dict[str, dict[str, Any]] = {
"on_off": {
DEVICE_TYPE: "ON_OFF_OUTPUT",
CONF_CLUSTERS: [
{
CONF_ID: "ON_OFF",
ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_TYPE: "BOOL",
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
CONNECT: True,
},
],
},
],
},
}
def _get_next_ep_num(eps: list[int]) -> int:
try:
@@ -154,6 +154,31 @@ bool ZigbeeComponent::app_signal_handler(const ezb_app_signal_t *app_signal) {
return true;
}
void load_zb_event(ZBEvent *event, ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute) {
event->load_set_attr_value_event(info, attribute);
}
template<typename... Args> void enqueue_zb_event(Args... args) {
// Allocate an event from the pool
ZBEvent *event = global_zigbee->zb_event_pool_.allocate();
if (event == nullptr) {
// No events available - queue is full or we're out of memory
global_zigbee->zb_events_.increment_dropped_count();
return;
}
// Load new event data (replaces previous event)
load_zb_event(event, args...);
// Push the event to the queue
global_zigbee->zb_events_.push(event);
// Push always succeeds because we're the only producer and the pool ensures we never exceed queue size
global_zigbee->enable_loop_soon_any_context();
}
// Explicit template instantiations for the friend function
template void enqueue_zb_event(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute);
static void zb_attribute_handler(ezb_zcl_set_attr_value_message_t *message) {
ESP_RETURN_ON_FALSE(message, , TAG, "Empty message");
ESP_RETURN_ON_FALSE(message->info.status == EZB_ZCL_STATUS_SUCCESS, , TAG, "Received message: error status(%d)",
@@ -161,6 +186,7 @@ static void zb_attribute_handler(ezb_zcl_set_attr_value_message_t *message) {
ESP_LOGD(TAG, "ZCL SetAttributeValue message for endpoint(%d) cluster(0x%04x) %s with status(0x%02x)",
message->info.dst_ep, message->info.cluster_id,
message->info.cluster_role == EZB_ZCL_CLUSTER_SERVER ? "server" : "client", message->info.status);
enqueue_zb_event(message->info, message->in.attribute);
}
static void zb_action_handler(ezb_zcl_core_action_callback_id_t callback_id, void *message) {
@@ -180,6 +206,13 @@ static void zb_action_handler(ezb_zcl_core_action_callback_id_t callback_id, voi
}
}
void ZigbeeComponent::handle_attribute_(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute) {
auto it = this->attributes_.find({info.dst_ep, info.cluster_id, info.cluster_role, attribute.id});
if (it != this->attributes_.end()) {
it->second->on_value(attribute);
}
}
void ZigbeeComponent::create_default_cluster(uint8_t endpoint_id, uint16_t device_id) {
ezb_af_ep_desc_t ep_desc =
esphome_zb_zha_default_ep_desc_create(endpoint_id, device_id, this->basic_cluster_data_.power_source);
@@ -349,6 +382,30 @@ void ZigbeeComponent::setup() {
}
void ZigbeeComponent::loop() {
// Process all pending events
ZBEvent *event = this->zb_events_.pop();
while (event != nullptr) {
// Handle the event
switch (event->callback_id_) {
case EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID:
this->handle_attribute_(event->event_.set_attr.info, event->event_.set_attr.attribute);
break;
default:
ESP_LOGW(TAG, "Received event with unhandled callback id: 0x%x", static_cast<unsigned>(event->callback_id_));
break;
}
// Free the event back to the pool
this->zb_event_pool_.release(event);
// Get the next event
event = this->zb_events_.pop();
}
// Log dropped events periodically
uint16_t dropped = this->zb_events_.get_and_reset_dropped_count();
if (dropped > 0) {
ESP_LOGW(TAG, "Dropped %u Zigbee events due to buffer overflow", dropped);
}
if (!this->start_reported_ && this->started_) {
this->start_cb_.call();
this->start_reported_ = true;
+14 -4
View File
@@ -12,11 +12,10 @@
#include "ezbee/zha.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/lock_free_queue.h"
#include "esphome/core/event_pool.h"
#include "zigbee_helpers_esp32.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#include "zigbee_event_esp32.h"
namespace esphome::zigbee {
@@ -24,6 +23,7 @@ namespace esphome::zigbee {
static const uint16_t ED_KEEP_ALIVE = 3000; /* 3000 millisecond */
static const uint8_t MAX_CHILDREN = 10;
static const uint32_t EZB_PRIMARY_CHANNEL_MASK = 0x07FFF800U; /* channels 11-26 */
static constexpr uint8_t MAX_ZB_QUEUE_SIZE = 32;
#define EZB_DEFAULT_RADIO_CONFIG() \
{ .radio_mode = ESP_ZIGBEE_RADIO_MODE_NATIVE, }
@@ -88,6 +88,7 @@ class ZigbeeComponent final : public Component {
template<typename T>
void add_attr_(ZigbeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
T *value_p);
void handle_attribute_(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute);
// attributes_ will be used during operation in zigbee callbacks to update the attribute values and trigger
// automations
// key tuple could be replaced by single 64 (48) bit int with bit fields for endpoint, cluster, role and attr_id
@@ -95,6 +96,15 @@ class ZigbeeComponent final : public Component {
std::vector<AttrValue> attr_values_;
ezb_af_device_desc_t dev_desc_ = ezb_af_create_device_desc();
CallbackManager<void(bool)> join_cb_{};
template<typename... Args> friend void enqueue_zb_event(Args... args);
esphome::LockFreeQueue<ZBEvent, MAX_ZB_QUEUE_SIZE> zb_events_;
// Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
// buffer that holds N-1 elements (one slot distinguishes full from empty).
// This guarantees allocate() returns nullptr before push() can fail, which:
// 1. Prevents leaking a pool slot (the Nth allocate succeeds but push fails)
// 2. Avoids needing release() on the producer path after a failed push(),
// preserving the SPSC contract on the pool's internal free list
esphome::EventPool<ZBEvent, MAX_ZB_QUEUE_SIZE - 1> zb_event_pool_;
LazyCallbackManager<void()> start_cb_{};
bool start_reported_{false};
std::atomic<bool> started_ = false;
+20
View File
@@ -27,6 +27,7 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE,
CONF_WIFI,
DEVICE_CLASS_OUTLET,
)
from esphome.core import CORE
from esphome.coroutine import CoroPriority, coroutine_with_priority
@@ -67,8 +68,10 @@ from .const_esp32 import (
from .zigbee_ep_esp32 import (
ANALOG_INPUT_EP,
BINARY_INPUT_EP,
BINARY_OUTPUT_EP,
BINARY_SENSOR_EP_CONFIGS,
SENSOR_EP_CONFIGS,
SWITCH_EP_CONFIGS,
add_ep,
create_ep,
)
@@ -275,6 +278,23 @@ def validate_binary_sensor_esp32(config: ConfigType) -> ConfigType:
return config
def validate_switch_esp32(config: ConfigType) -> ConfigType:
if config[CONF_CLUSTER] == "on_off":
ep = copy.deepcopy(SWITCH_EP_CONFIGS["on_off"])
if config.get(CONF_DEVICE_CLASS) == DEVICE_CLASS_OUTLET:
ep[DEVICE_TYPE] = "MAINS_POWER_OUTLET"
else:
if config.get(CONF_DEVICE_CLASS) == DEVICE_CLASS_OUTLET:
_LOGGER.warning(
"'device_class: outlet' has no effect with 'cluster: generic', "
"use 'cluster: on_off' to expose a MAINS_POWER_OUTLET device type"
)
ep = copy.deepcopy(BINARY_OUTPUT_EP)
setup_attributes(config, ep[CONF_CLUSTERS])
add_ep(ep, config.get(CONF_ENDPOINT), config.get(CONF_USE_DEVICE_TYPE))
return config
def zigbee_require_vfs_select(config: ConfigType) -> ConfigType:
"""Register VFS select requirement during config validation."""
# Zigbee uses esp_vfs_eventfd which requires VFS select support
@@ -0,0 +1,97 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ESP32
#ifdef USE_ZIGBEE
#include <cstddef> // for offsetof
#include <cstring> // for memcpy
#include "esp_zigbee.h"
#include "ezbee/zha.h"
namespace esphome::zigbee {
class ZBEvent {
public:
// IMPORTANT: We MUST copy all values because the pointer from ESP-IDF
// is only valid during the callback execution. Since ZB events are processed
// asynchronously in the main loop, we store our own copy inline to ensure
// the data remains valid until the event is processed.
ZBEvent(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute)
: event_{}, callback_id_(EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID) {
this->init_set_attr_value_data_(info, attribute);
}
// Destructor to clean up heap allocations
~ZBEvent() { this->release(); }
// Default constructor for pre-allocation in pool
ZBEvent() : event_{}, callback_id_(EZB_ZCL_CORE_CB_ID_END) {}
// Invoked on return to EventPool - clean up any heap-allocated data
void release() {
// Free any allocated memory within the event
switch (this->callback_id_) {
case EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID:
if (!this->event_.set_attr.is_inline && this->event_.set_attr.data.heap_data != nullptr) {
delete[] this->event_.set_attr.data.heap_data;
this->event_.set_attr.data.heap_data = nullptr;
}
break;
default:
break;
}
}
// Load new event data for reuse (replaces previous event data)
// Note: release() is NOT called here because EventPool::release() already
// calls event->release() before returning to the free list. Every event
// from allocate() is already in a clean state.
void load_set_attr_value_event(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute) {
this->callback_id_ = EZB_ZCL_CORE_SET_ATTR_VALUE_CB_ID;
this->init_set_attr_value_data_(info, attribute);
}
// Disable copy to prevent double-delete
ZBEvent(const ZBEvent &) = delete;
ZBEvent &operator=(const ZBEvent &) = delete;
union {
// NOLINTNEXTLINE(readability-identifier-naming)
struct set_attr_event {
ezb_zcl_message_info_t info;
ezb_zcl_attribute_t attribute;
union {
uint8_t *heap_data;
uint8_t inline_data[4]; // For small data types (<= 32 bit)
} data;
bool is_inline;
} set_attr;
} event_;
ezb_zcl_core_action_callback_id_t callback_id_;
private:
void init_set_attr_value_data_(ezb_zcl_message_info_t info, ezb_zcl_attribute_t attribute) {
this->event_.set_attr.info = info;
this->event_.set_attr.attribute = attribute;
// get attribute.data.value with correct type
if (attribute.data.value != nullptr) {
// Copy the attribute value to avoid dangling pointer issues
size_t value_size = ezb_zcl_get_attr_value_size(attribute.data.type, attribute.data.value);
if (value_size > 4) {
this->event_.set_attr.data.heap_data = new uint8_t[value_size];
memcpy(this->event_.set_attr.data.heap_data, attribute.data.value, value_size);
this->event_.set_attr.attribute.data.value = this->event_.set_attr.data.heap_data;
this->event_.set_attr.is_inline = false;
} else {
memcpy(this->event_.set_attr.data.inline_data, attribute.data.value, value_size);
this->event_.set_attr.attribute.data.value = this->event_.set_attr.data.inline_data;
this->event_.set_attr.is_inline = true;
}
}
}
};
} // namespace esphome::zigbee
#endif // USE_ZIGBEE
#endif // USE_ESP32
@@ -27,6 +27,12 @@ ezb_err_t esphome_zb_add_or_update_cluster(uint16_t cluster_id, ezb_af_ep_desc_t
ezb_af_ep_desc_t esphome_zb_zha_default_ep_desc_create(uint8_t ep_id, uint16_t device_id, uint8_t power_source) {
ezb_af_ep_desc_t ep_desc;
switch (device_id) {
case EZB_ZHA_MAINS_POWER_OUTLET_DEVICE_ID: {
ezb_zha_mains_power_outlet_config_t config = EZB_ZHA_MAINS_POWER_OUTLET_CONFIG();
config.basic_cfg.power_source = power_source;
ep_desc = ezb_zha_create_mains_power_outlet(ep_id, &config);
break;
}
case EZB_ZHA_LIGHT_SENSOR_DEVICE_ID: {
ezb_zha_light_sensor_config_t config = EZB_ZHA_LIGHT_SENSOR_CONFIG();
config.basic_cfg.power_source = power_source;
@@ -60,12 +66,16 @@ ezb_zcl_cluster_desc_t esphome_zb_default_cluster_dscr_create(uint16_t cluster_i
return ezb_zcl_basic_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_IDENTIFY:
return ezb_zcl_identify_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_ON_OFF:
return ezb_zcl_on_off_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_TIME:
return ezb_zcl_time_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_ANALOG_INPUT:
return ezb_zcl_analog_input_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_BINARY_INPUT:
return ezb_zcl_binary_input_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT:
return ezb_zcl_binary_output_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT:
return ezb_zcl_illuminance_measurement_create_cluster_desc(NULL, role_mask);
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT:
@@ -97,12 +107,16 @@ ezb_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, ezb_zcl_cluster_desc_
return ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_IDENTIFY:
return ezb_zcl_identify_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_ON_OFF:
return ezb_zcl_on_off_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_TIME:
return ezb_zcl_time_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_ANALOG_INPUT:
return ezb_zcl_analog_input_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_BINARY_INPUT:
return ezb_zcl_binary_input_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT:
return ezb_zcl_binary_output_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT:
return ezb_zcl_illuminance_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT:
+1 -5
View File
@@ -314,10 +314,6 @@ async def _ctx_to_code(config: ConfigType) -> None:
cg.add(cg.RawExpression("ZB_AF_REGISTER_DEVICE_CTX(&zb_device_ctx)"))
async def zephyr_setup_switch(entity: cg.MockObj, config: ConfigType) -> None:
CORE.add_job(_add_switch, entity, config)
async def zephyr_setup_number(
entity: cg.MockObj,
config: ConfigType,
@@ -425,7 +421,7 @@ async def add_sensor(entity: cg.MockObj, config: ConfigType) -> None:
)
async def _add_switch(entity: cg.MockObj, config: ConfigType) -> None:
async def add_switch(entity: cg.MockObj, config: ConfigType) -> None:
await _add_zigbee_ep(
entity,
config,
+11
View File
@@ -45,6 +45,17 @@ sensor:
device_class: volume_flow_rate
cluster: device_class
switch:
- platform: template
name: "Template Switch 1"
optimistic: true
cluster: on_off
- platform: template
name: "Template Switch 2"
optimistic: true
cluster: on_off
device_class: outlet
zigbee:
model: zigbee_test
router: false