mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 08:50:22 +00:00
[zigbee] Add cluster: device_class to expose ESP32 sensors via measurement clusters instead of Analog/Binary Input (#17388)
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>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
Jonathan Swoboda
parent
23afdc1d9b
commit
cd0a0f3fca
@@ -18,6 +18,7 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_CLUSTER,
|
||||
CONF_ENDPOINT,
|
||||
CONF_MAX_EP_NUMBER,
|
||||
CONF_ON_JOIN,
|
||||
@@ -74,6 +75,11 @@ BASE_SCHEMA = cv.Schema(
|
||||
_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"),
|
||||
@@ -196,21 +202,27 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
|
||||
async def setup_binary_sensor(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_binary_sensor
|
||||
|
||||
await zephyr_setup_binary_sensor(entity, config)
|
||||
if not config.get(CONF_ZIGBEE_ID):
|
||||
return
|
||||
from .zigbee_zephyr import add_binary_sensor
|
||||
else:
|
||||
from .zigbee_esp32 import add_component as add_binary_sensor
|
||||
CORE.add_job(add_binary_sensor, entity, config)
|
||||
|
||||
|
||||
async def setup_sensor(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_sensor
|
||||
|
||||
await zephyr_setup_sensor(entity, config)
|
||||
if not config.get(CONF_ZIGBEE_ID):
|
||||
return
|
||||
from .zigbee_zephyr import add_sensor
|
||||
else:
|
||||
from .zigbee_esp32 import add_component as add_sensor
|
||||
CORE.add_job(add_sensor, entity, config)
|
||||
|
||||
|
||||
async def setup_switch(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
|
||||
@@ -64,6 +64,7 @@ REPORT = {
|
||||
"default": report.ZIGBEE_REPORT_DEFAULT,
|
||||
}
|
||||
|
||||
CONF_CLUSTER = "cluster"
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_MAX_EP_NUMBER = 239
|
||||
CONF_ON_JOIN = "on_join"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
ALLOWED_UNITS = "allowed_units"
|
||||
DEVICE_TYPE = "device_type"
|
||||
ROLE = "role"
|
||||
CONF_CLUSTERS = "clusters"
|
||||
CONF_ATTRIBUTES = "attributes"
|
||||
CONF_CLUSTER = "cluster"
|
||||
SCALE = "scale"
|
||||
CONF_ATTRIBUTE_ID = "attribute_id"
|
||||
KEY_ZIGBEE_ATTRIBUTES = "zigbee_attributes"
|
||||
CONNECT = "connect"
|
||||
SCALE = "scale"
|
||||
KEY_ZIGBEE_EP = "zigbee_ep"
|
||||
KEY_ZIGBEE_EP_NO_NUM = "zigbee_ep_no_num"
|
||||
KEY_ZIGBEE_FIRST_EP_CL = "zigbee_first_ep_cl"
|
||||
@@ -14,6 +16,11 @@ KEY_ZIGBEE_FIRST_EP_CL = "zigbee_first_ep_cl"
|
||||
DEVICE_ID = {
|
||||
"RANGE_EXTENDER": cg.RawExpression("EZB_ZHA_RANGE_EXTENDER_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"),
|
||||
"TEMPERATURE_SENSOR": cg.RawExpression("EZB_ZHA_TEMPERATURE_SENSOR_DEVICE_ID"),
|
||||
"PRESSURE_SENSOR": cg.RawExpression("EZB_ZHA_PRESSURE_SENSOR_DEVICE_ID"),
|
||||
"FLOW_SENSOR": cg.RawExpression("EZB_ZHA_FLOW_SENSOR_DEVICE_ID"),
|
||||
"CUSTOM_ATTR": 0xFFF2,
|
||||
}
|
||||
cluster_id = cg.esphome_ns.enum("ezb_zcl_cluster_id_e")
|
||||
@@ -22,6 +29,14 @@ CLUSTER_ID = {
|
||||
"TIME": cluster_id.EZB_ZCL_CLUSTER_ID_TIME,
|
||||
"BINARY_INPUT": cluster_id.EZB_ZCL_CLUSTER_ID_BINARY_INPUT,
|
||||
"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,
|
||||
"PRESSURE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT,
|
||||
"FLOW_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT,
|
||||
"REL_HUMIDITY_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
|
||||
"OCCUPANCY_SENSING": cluster_id.EZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING,
|
||||
"CARBON_DIOXIDE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT,
|
||||
"PM2_5_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT,
|
||||
}
|
||||
CLUSTER_ROLE = {
|
||||
"SERVER": cg.RawExpression("EZB_ZCL_CLUSTER_SERVER"),
|
||||
@@ -31,6 +46,12 @@ attr_type = cg.esphome_ns.enum("ezb_zcl_attr_type_e")
|
||||
ATTR_TYPE = {
|
||||
"BOOL": attr_type.EZB_ZCL_ATTR_TYPE_BOOL,
|
||||
"MAP8": attr_type.EZB_ZCL_ATTR_TYPE_MAP8,
|
||||
"UINT8": attr_type.EZB_ZCL_ATTR_TYPE_UINT8,
|
||||
"UINT16": attr_type.EZB_ZCL_ATTR_TYPE_UINT16,
|
||||
"INT8": attr_type.EZB_ZCL_ATTR_TYPE_INT8,
|
||||
"INT16": attr_type.EZB_ZCL_ATTR_TYPE_INT16,
|
||||
"ENUM8": attr_type.EZB_ZCL_ATTR_TYPE_ENUM8,
|
||||
"ENUM16": attr_type.EZB_ZCL_ATTR_TYPE_ENUM16,
|
||||
"STRING": attr_type.EZB_ZCL_ATTR_TYPE_STRING,
|
||||
"SINGLE": attr_type.EZB_ZCL_ATTR_TYPE_SINGLE,
|
||||
"DOUBLE": attr_type.EZB_ZCL_ATTR_TYPE_DOUBLE,
|
||||
|
||||
@@ -23,7 +23,8 @@ void ZigbeeAttribute::set_attr_() {
|
||||
}
|
||||
// Check for error
|
||||
if (state != EZB_ZCL_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Setting attribute failed, ZCL status: %u", static_cast<unsigned>(state));
|
||||
ESP_LOGE(TAG, "Setting attribute 0x%04X on cluster 0x%04X failed, ZCL status: %u", this->attr_id_,
|
||||
this->cluster_id_, static_cast<unsigned>(state));
|
||||
}
|
||||
esp_zigbee_lock_release();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
@@ -47,6 +49,7 @@ class ZigbeeAttribute final : public Component {
|
||||
void set_report(ZigbeeReportT report);
|
||||
#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);
|
||||
@@ -68,6 +71,8 @@ class ZigbeeAttribute final : public Component {
|
||||
bool set_attr_requested_{false};
|
||||
bool report_requested_{false};
|
||||
bool force_report_{false};
|
||||
template<typename T> T scale_value_(float value);
|
||||
template<typename T> T invalid_value_();
|
||||
};
|
||||
|
||||
template<typename T> void ZigbeeAttribute::add_attr(T value) {
|
||||
@@ -85,9 +90,44 @@ template<typename T> void ZigbeeAttribute::set_attr(const T &value) {
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
template<typename T> T ZigbeeAttribute::scale_value_(float value) {
|
||||
static_assert(sizeof(T) <= 2 || std::is_floating_point_v<T>);
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
const float scaled = this->scale_ * value;
|
||||
if (std::isnan(value) || scaled < static_cast<float>(std::numeric_limits<T>::lowest()) ||
|
||||
scaled > static_cast<float>(std::numeric_limits<T>::max())) {
|
||||
return this->invalid_value_<T>(); // 0x8000 / 0xFFFF / 0 for bitmaps
|
||||
}
|
||||
return static_cast<T>(lroundf(scaled));
|
||||
}
|
||||
return static_cast<T>(this->scale_ * value);
|
||||
}
|
||||
|
||||
template<typename T> T ZigbeeAttribute::invalid_value_() {
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
if constexpr (std::is_signed_v<T>) {
|
||||
// For signed integer types, NaN is represented by the minimum value
|
||||
return static_cast<T>(std::numeric_limits<T>::min());
|
||||
}
|
||||
|
||||
if (this->attr_type_ >= EZB_ZCL_ATTR_TYPE_UINT8 && this->attr_type_ <= EZB_ZCL_ATTR_TYPE_ENUM16) {
|
||||
// For unsigned integer types and enum, NaN is represented by the maximum value
|
||||
return static_cast<T>(std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// For other integer types, return 0 as a fallback
|
||||
return static_cast<T>(0);
|
||||
}
|
||||
|
||||
return std::numeric_limits<T>::quiet_NaN(); // For floating-point types, return NaN
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
template<typename T> void ZigbeeAttribute::connect(sensor::Sensor *sensor) {
|
||||
sensor->add_on_state_callback([this](float value) { this->set_attr((T) (this->scale_ * value)); });
|
||||
sensor->add_on_state_callback([this](float value) { this->set_attr(this->scale_value_<T>(value)); });
|
||||
}
|
||||
template<typename T, typename F> void ZigbeeAttribute::connect(sensor::Sensor *sensor, F &&f) {
|
||||
sensor->add_on_state_callback([f = std::forward<F>(f), this](float value) { this->set_attr((T) f(value)); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DEVICE, CONF_ID, CONF_TYPE
|
||||
from esphome.core import CORE
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_TYPE,
|
||||
CONF_VALUE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
DEVICE_CLASS_PM25,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_CUBIC_METER_PER_HOUR,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_LITRE_PER_HOUR,
|
||||
UNIT_LUX,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PASCAL,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
from esphome.core import CORE, Lambda
|
||||
|
||||
from .const import (
|
||||
CONF_MAX_EP_NUMBER,
|
||||
@@ -12,70 +35,264 @@ from .const import (
|
||||
REPORT,
|
||||
)
|
||||
from .const_esp32 import (
|
||||
ALLOWED_UNITS,
|
||||
CONF_ATTRIBUTE_ID,
|
||||
CONF_ATTRIBUTES,
|
||||
CONF_CLUSTERS,
|
||||
CONNECT,
|
||||
DEVICE_TYPE,
|
||||
KEY_ZIGBEE_EP,
|
||||
KEY_ZIGBEE_EP_NO_NUM,
|
||||
KEY_ZIGBEE_FIRST_EP_CL,
|
||||
ROLE,
|
||||
SCALE,
|
||||
)
|
||||
|
||||
# endpoint configs:
|
||||
ep_configs: dict[str, dict[str, Any]] = {
|
||||
"binary_input": {
|
||||
DEVICE_TYPE: "SIMPLE_SENSOR",
|
||||
ANALOG_INPUT_EP = {
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "ANALOG_INPUT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x55,
|
||||
CONF_TYPE: "SINGLE",
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
BINARY_INPUT_EP = {
|
||||
DEVICE_TYPE: "SIMPLE_SENSOR",
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "BINARY_INPUT",
|
||||
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 = {
|
||||
ALLOWED_UNITS: [UNIT_HECTOPASCAL, UNIT_PASCAL],
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "BINARY_INPUT",
|
||||
CONF_ID: "PRESSURE_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x55,
|
||||
CONF_TYPE: "BOOL",
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "INT16",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONF_DEVICE: None,
|
||||
CONNECT: True,
|
||||
SCALE: {
|
||||
UNIT_HECTOPASCAL: 1,
|
||||
UNIT_PASCAL: 0.01,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
if device_type:
|
||||
ep[DEVICE_TYPE] = (
|
||||
"PRESSURE_SENSOR" # Sensor that measures pressure of liquids like water
|
||||
)
|
||||
return ep
|
||||
|
||||
|
||||
SENSOR_EP_CONFIGS: dict[str, dict[str, Any]] = {
|
||||
DEVICE_CLASS_TEMPERATURE: {
|
||||
ALLOWED_UNITS: [UNIT_CELSIUS],
|
||||
DEVICE_TYPE: "TEMPERATURE_SENSOR",
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "TEMPERATURE_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x51,
|
||||
CONF_TYPE: "BOOL",
|
||||
},
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x6F,
|
||||
CONF_TYPE: "MAP8",
|
||||
},
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x1C,
|
||||
CONF_TYPE: "STRING",
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "INT16",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
SCALE: 100,
|
||||
CONNECT: True,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"analog_input": {
|
||||
DEVICE_CLASS_HUMIDITY: {
|
||||
ALLOWED_UNITS: [UNIT_PERCENT],
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "ANALOG_INPUT",
|
||||
CONF_ID: "REL_HUMIDITY_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x55,
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "UINT16",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
SCALE: 100,
|
||||
CONNECT: True,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE: _pressure_ep(),
|
||||
DEVICE_CLASS_PRESSURE: _pressure_ep(device_type=True),
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE: {
|
||||
ALLOWED_UNITS: [UNIT_LITRE_PER_HOUR, UNIT_CUBIC_METER_PER_HOUR],
|
||||
DEVICE_TYPE: "FLOW_SENSOR",
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "FLOW_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "UINT16",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONNECT: True,
|
||||
SCALE: {
|
||||
UNIT_LITRE_PER_HOUR: 0.01,
|
||||
UNIT_CUBIC_METER_PER_HOUR: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
DEVICE_CLASS_ILLUMINANCE: {
|
||||
ALLOWED_UNITS: [UNIT_LUX],
|
||||
DEVICE_TYPE: "LIGHT_SENSOR",
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "ILLUMINANCE_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "UINT16",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONF_LAMBDA: cv.lambda_(
|
||||
Lambda(
|
||||
"if (x < 0.0f || std::isnan(x)) return 0xFFFF;" # NaN
|
||||
" if (x < 1.0f) return 0;" # too small to measure
|
||||
" const float v = log10(x)*10000 + 1;"
|
||||
" return v > 65534.0f ? 0xFFFE : (uint16_t) lroundf(v);" # clamp to 0xFFFE if too large
|
||||
)
|
||||
),
|
||||
CONNECT: True,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
DEVICE_CLASS_PM25: {
|
||||
ALLOWED_UNITS: [UNIT_MICROGRAMS_PER_CUBIC_METER],
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "PM2_5_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "SINGLE",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONF_DEVICE: None,
|
||||
CONNECT: True,
|
||||
},
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x51,
|
||||
CONF_TYPE: "BOOL",
|
||||
CONF_ATTRIBUTE_ID: 0x2,
|
||||
CONF_TYPE: "SINGLE",
|
||||
CONF_VALUE: 9999, # overwrite default 1.0
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
DEVICE_CLASS_CARBON_DIOXIDE: {
|
||||
ALLOWED_UNITS: [UNIT_PARTS_PER_MILLION],
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "CARBON_DIOXIDE_MEASUREMENT",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x6F,
|
||||
CONF_ATTRIBUTE_ID: 0x0000,
|
||||
CONF_TYPE: "SINGLE",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONNECT: True,
|
||||
SCALE: 0.000001,
|
||||
},
|
||||
{CONF_ATTRIBUTE_ID: 0x0001, CONF_TYPE: "SINGLE", CONF_VALUE: 0.0},
|
||||
{CONF_ATTRIBUTE_ID: 0x0002, CONF_TYPE: "SINGLE", CONF_VALUE: 0.1},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
BINARY_SENSOR_EP_CONFIGS: dict[str, dict[str, Any]] = {
|
||||
DEVICE_CLASS_OCCUPANCY: {
|
||||
DEVICE_TYPE: "OCCUPANCY_SENSOR",
|
||||
CONF_CLUSTERS: [
|
||||
{
|
||||
CONF_ID: "OCCUPANCY_SENSING",
|
||||
ROLE: "SERVER",
|
||||
CONF_ATTRIBUTES: [
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x0,
|
||||
CONF_TYPE: "MAP8",
|
||||
CONF_REPORT: cv.enum(REPORT, lower=True)("default"),
|
||||
CONNECT: True,
|
||||
},
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x1C,
|
||||
CONF_TYPE: "STRING",
|
||||
CONF_ATTRIBUTE_ID: 0x1,
|
||||
CONF_TYPE: "ENUM8",
|
||||
CONF_VALUE: 0, # hardcode PIR for now as ultrasonic or physical contact is unlikely
|
||||
},
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x2,
|
||||
CONF_TYPE: "MAP8",
|
||||
CONF_VALUE: 0b00000001, # hardcode PIR for now as ultrasonic or physical contact is unlikely
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -181,13 +181,8 @@ static void zb_action_handler(ezb_zcl_core_action_callback_id_t callback_id, voi
|
||||
}
|
||||
|
||||
void ZigbeeComponent::create_default_cluster(uint8_t endpoint_id, uint16_t device_id) {
|
||||
ezb_af_ep_config_t config = {
|
||||
.ep_id = endpoint_id,
|
||||
.app_profile_id = EZB_AF_HA_PROFILE_ID,
|
||||
.app_device_id = device_id,
|
||||
.app_device_version = 0,
|
||||
};
|
||||
ezb_af_ep_desc_t ep_desc = ezb_af_create_endpoint_desc(&config);
|
||||
ezb_af_ep_desc_t ep_desc =
|
||||
esphome_zb_zha_default_ep_desc_create(endpoint_id, device_id, this->basic_cluster_data_.power_source);
|
||||
if (ezb_af_device_add_endpoint_desc(this->dev_desc_, ep_desc) != EZB_ERR_NONE) {
|
||||
ESP_LOGE(TAG, "Could not create endpoint %u", endpoint_id);
|
||||
}
|
||||
@@ -232,13 +227,13 @@ void ZigbeeComponent::update_basic_cluster_(ezb_af_ep_desc_t ep_desc) {
|
||||
.power_source = this->basic_cluster_data_.power_source,
|
||||
};
|
||||
cluster_desc = ezb_zcl_basic_create_cluster_desc(&basic_cluster_cfg, EZB_ZCL_CLUSTER_SERVER);
|
||||
ezb_af_endpoint_add_cluster_desc(ep_desc, cluster_desc);
|
||||
}
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID,
|
||||
this->basic_cluster_data_.manufacturer);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID,
|
||||
this->basic_cluster_data_.model);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_DATE_CODE_ID, this->basic_cluster_data_.date);
|
||||
ezb_af_endpoint_add_cluster_desc(ep_desc, cluster_desc);
|
||||
}
|
||||
|
||||
bool ZigbeeComponent::register_device() {
|
||||
|
||||
@@ -17,9 +17,9 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACCURACY_DECIMALS,
|
||||
CONF_AP,
|
||||
CONF_DEVICE,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_MAX_LENGTH,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
@@ -38,6 +38,7 @@ from .const import (
|
||||
ANALOG_INPUT_APPTYPE,
|
||||
BACNET_UNIT_NO_UNITS,
|
||||
BACNET_UNITS,
|
||||
CONF_CLUSTER,
|
||||
CONF_ENDPOINT,
|
||||
CONF_POWER_SOURCE,
|
||||
CONF_REPORT,
|
||||
@@ -48,19 +49,29 @@ from .const import (
|
||||
ZigbeeAttribute,
|
||||
)
|
||||
from .const_esp32 import (
|
||||
ALLOWED_UNITS,
|
||||
ATTR_TYPE,
|
||||
CLUSTER_ID,
|
||||
CLUSTER_ROLE,
|
||||
CONF_ATTRIBUTE_ID,
|
||||
CONF_ATTRIBUTES,
|
||||
CONF_CLUSTERS,
|
||||
CONNECT,
|
||||
DEVICE_ID,
|
||||
DEVICE_TYPE,
|
||||
KEY_ZIGBEE_ATTRIBUTES,
|
||||
KEY_ZIGBEE_EP,
|
||||
ROLE,
|
||||
SCALE,
|
||||
)
|
||||
from .zigbee_ep_esp32 import add_ep, create_ep, ep_configs
|
||||
from .zigbee_ep_esp32 import (
|
||||
ANALOG_INPUT_EP,
|
||||
BINARY_INPUT_EP,
|
||||
BINARY_SENSOR_EP_CONFIGS,
|
||||
SENSOR_EP_CONFIGS,
|
||||
add_ep,
|
||||
create_ep,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -79,8 +90,11 @@ def get_c_type(attr_type: str) -> Any | None:
|
||||
if "STRING" in attr_type:
|
||||
return cg.std_string
|
||||
test = re.match(r"^(DATA|UINT|MAP|ENUM)(\d{1,2})$", attr_type)
|
||||
if test and test.group(2):
|
||||
if test:
|
||||
return getattr(cg, "uint" + get_c_size(test.group(2), [8, 16, 32, 64]))
|
||||
test = re.match(r"^INT(\d{1,2})$", attr_type)
|
||||
if test:
|
||||
return getattr(cg, "int" + get_c_size(test.group(1), [8, 16, 32, 64]))
|
||||
return None
|
||||
|
||||
|
||||
@@ -92,8 +106,11 @@ def get_cv_by_type(attr_type: str) -> Any | None:
|
||||
if "STRING" in attr_type:
|
||||
return cv.string
|
||||
test = re.match(r"^(DATA|UINT|MAP|ENUM)(\d{1,2})$", attr_type)
|
||||
if test and test.group(2):
|
||||
if test:
|
||||
return cv.positive_int
|
||||
test = re.match(r"^INT(\d{1,2})$", attr_type)
|
||||
if test:
|
||||
return cv.int_
|
||||
raise cv.Invalid(f"Zigbee: type {attr_type} not supported or implemented")
|
||||
|
||||
|
||||
@@ -104,6 +121,14 @@ def get_default_by_type(attr_type: str) -> str | bool | int | float:
|
||||
return False
|
||||
if attr_type in ["SINGLE", "DOUBLE"]:
|
||||
return float("nan")
|
||||
test = re.match(r"^(UINT|ENUM)(\d{1,2})$", attr_type)
|
||||
if test:
|
||||
# ZCL "invalid value" sentinel for unsigned ints is the maximum (0xFFFF for UINT16)
|
||||
return 2 ** (int(test.group(2))) - 1
|
||||
test = re.match(r"^INT(\d{1,2})$", attr_type)
|
||||
if test:
|
||||
# ZCL "invalid value" sentinel for signed ints is the minimum (0x8000 for INT16)
|
||||
return -(1 << (int(test.group(1)) - 1))
|
||||
return 0
|
||||
|
||||
|
||||
@@ -167,59 +192,84 @@ def setup_attributes(config: ConfigType, clusters: list[dict[str, Any]]) -> None
|
||||
) # or use unidecode
|
||||
attr[CONF_VALUE] = str(name)
|
||||
attr[CONF_MAX_LENGTH] = len(str(name))
|
||||
if CONF_DEVICE in attr: # connect device
|
||||
attr[CONF_DEVICE] = config[CONF_ID]
|
||||
if attr.get(CONNECT): # connect device
|
||||
if CONF_REPORT in config:
|
||||
attr[CONF_REPORT] = config[CONF_REPORT]
|
||||
attr[CONF_ID] = cv.declare_id(ZigbeeAttribute)(None)
|
||||
if "zb_attr_ids" not in config:
|
||||
config["zb_attr_ids"] = []
|
||||
config["zb_attr_ids"].append(attr[CONF_ID])
|
||||
attr_list = config.setdefault(KEY_ZIGBEE_ATTRIBUTES, [])
|
||||
attr_list.append(attr)
|
||||
else:
|
||||
attr[CONF_ID] = None
|
||||
validate_attributes(attr)
|
||||
|
||||
|
||||
def validate_sensor_esp32(config: ConfigType) -> ConfigType:
|
||||
ep = copy.deepcopy(ep_configs["analog_input"])
|
||||
# get application type from device class and meas unit
|
||||
# if none get BACNET unit from meas unit
|
||||
dev_class = config.get(CONF_DEVICE_CLASS)
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
apptype = ANALOG_INPUT_APPTYPE.get((dev_class, unit))
|
||||
bacunit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS)
|
||||
accuracy = config.get(CONF_ACCURACY_DECIMALS)
|
||||
if apptype is not None:
|
||||
if config[CONF_CLUSTER] == "device_class":
|
||||
if dev_class not in SENSOR_EP_CONFIGS:
|
||||
raise cv.Invalid(
|
||||
"'cluster: device_class' requires a supported 'device_class'. "
|
||||
f"Supported: {', '.join(SENSOR_EP_CONFIGS)}. Use 'cluster: generic' otherwise."
|
||||
)
|
||||
ep = copy.deepcopy(SENSOR_EP_CONFIGS[dev_class])
|
||||
if unit not in ep[ALLOWED_UNITS]:
|
||||
raise cv.Invalid(
|
||||
f"Device class '{dev_class}' requires one of units {', '.join(ep[ALLOWED_UNITS])}."
|
||||
)
|
||||
for attr in ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES]:
|
||||
if isinstance(attr.get(SCALE), dict):
|
||||
attr[SCALE] = attr[SCALE][unit]
|
||||
if isinstance(attr.get(CONF_LAMBDA), dict):
|
||||
attr[CONF_LAMBDA] = attr[CONF_LAMBDA][unit]
|
||||
else:
|
||||
ep = copy.deepcopy(ANALOG_INPUT_EP)
|
||||
apptype = ANALOG_INPUT_APPTYPE.get((dev_class, unit))
|
||||
bacunit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS)
|
||||
accuracy = config.get(CONF_ACCURACY_DECIMALS)
|
||||
if apptype is not None:
|
||||
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x100,
|
||||
CONF_VALUE: (apptype << 16) | 0xFFFF,
|
||||
CONF_TYPE: "UINT32",
|
||||
},
|
||||
)
|
||||
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x100,
|
||||
CONF_VALUE: (apptype << 16) | 0xFFFF,
|
||||
CONF_TYPE: "UINT32",
|
||||
},
|
||||
)
|
||||
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x75,
|
||||
CONF_VALUE: bacunit,
|
||||
CONF_TYPE: "ENUM16",
|
||||
},
|
||||
)
|
||||
if accuracy is not None:
|
||||
# Analog Input Resolution (0x006A): smallest reportable change
|
||||
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x6A,
|
||||
CONF_VALUE: 10**-accuracy,
|
||||
CONF_TYPE: "SINGLE",
|
||||
CONF_ATTRIBUTE_ID: 0x75,
|
||||
CONF_VALUE: bacunit,
|
||||
CONF_TYPE: "ENUM16",
|
||||
},
|
||||
)
|
||||
if accuracy is not None:
|
||||
# Analog Input Resolution (0x006A): smallest reportable change
|
||||
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
|
||||
{
|
||||
CONF_ATTRIBUTE_ID: 0x6A,
|
||||
CONF_VALUE: 10**-accuracy,
|
||||
CONF_TYPE: "SINGLE",
|
||||
},
|
||||
)
|
||||
setup_attributes(config, ep[CONF_CLUSTERS])
|
||||
add_ep(ep, config.get(CONF_ENDPOINT), config.get(CONF_USE_DEVICE_TYPE))
|
||||
return config
|
||||
|
||||
|
||||
def validate_binary_sensor_esp32(config: ConfigType) -> ConfigType:
|
||||
ep = copy.deepcopy(ep_configs["binary_input"])
|
||||
dev_class = config.get(CONF_DEVICE_CLASS)
|
||||
if config[CONF_CLUSTER] == "device_class":
|
||||
if dev_class in BINARY_SENSOR_EP_CONFIGS:
|
||||
ep = copy.deepcopy(BINARY_SENSOR_EP_CONFIGS[dev_class])
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
"'cluster: device_class' requires a supported 'device_class'. "
|
||||
f"Supported: {', '.join(BINARY_SENSOR_EP_CONFIGS)}. Use 'cluster: generic' otherwise."
|
||||
)
|
||||
else:
|
||||
ep = copy.deepcopy(BINARY_INPUT_EP)
|
||||
setup_attributes(config, ep[CONF_CLUSTERS])
|
||||
add_ep(ep, config.get(CONF_ENDPOINT), config.get(CONF_USE_DEVICE_TYPE))
|
||||
return config
|
||||
@@ -274,15 +324,11 @@ async def attributes_to_code(
|
||||
)
|
||||
await cg.register_component(attr_var, attr)
|
||||
|
||||
cg.add(attr_var.add_attr(attr[CONF_VALUE]))
|
||||
template_arg = cg.TemplateArguments(get_c_type(attr[CONF_TYPE]))
|
||||
cg.add(attr_var.add_attr(template_arg, attr[CONF_VALUE]))
|
||||
if CONF_REPORT in attr:
|
||||
cg.add(attr_var.set_report(attr[CONF_REPORT]))
|
||||
|
||||
if CONF_DEVICE in attr:
|
||||
device = await cg.get_variable(attr[CONF_DEVICE])
|
||||
template_arg = cg.TemplateArguments(get_c_type(attr[CONF_TYPE]))
|
||||
cg.add(attr_var.connect(template_arg, device))
|
||||
|
||||
|
||||
async def esp32_to_code(config: ConfigType) -> "MockObj":
|
||||
add_idf_component(
|
||||
@@ -332,3 +378,19 @@ async def esp32_to_code(config: ConfigType) -> "MockObj":
|
||||
)
|
||||
await attributes_to_code(var, ep_num, cl)
|
||||
return var
|
||||
|
||||
|
||||
async def add_component(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
attrs = config.get(KEY_ZIGBEE_ATTRIBUTES, [])
|
||||
for attr in attrs:
|
||||
zb_attr = await cg.get_variable(attr[CONF_ID])
|
||||
template_arg = cg.TemplateArguments(get_c_type(attr[CONF_TYPE]))
|
||||
if attr.get(CONF_LAMBDA) is not None:
|
||||
lambda_ = await cg.process_lambda(
|
||||
attr[CONF_LAMBDA],
|
||||
[(cg.float_, "x")],
|
||||
return_type=get_c_type(attr[CONF_TYPE]),
|
||||
)
|
||||
cg.add(zb_attr.connect(template_arg, entity, lambda_))
|
||||
else:
|
||||
cg.add(zb_attr.connect(template_arg, entity))
|
||||
|
||||
@@ -24,6 +24,36 @@ ezb_err_t esphome_zb_add_or_update_cluster(uint16_t cluster_id, ezb_af_ep_desc_t
|
||||
return ezb_af_endpoint_add_cluster_desc(ep_desc, cluster_desc);
|
||||
}
|
||||
|
||||
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_LIGHT_SENSOR_DEVICE_ID: {
|
||||
ezb_zha_light_sensor_config_t config = EZB_ZHA_LIGHT_SENSOR_CONFIG();
|
||||
config.basic_cfg.power_source = power_source;
|
||||
ep_desc = ezb_zha_create_light_sensor(ep_id, &config);
|
||||
break;
|
||||
}
|
||||
case EZB_ZHA_TEMPERATURE_SENSOR_DEVICE_ID: {
|
||||
ezb_zha_temperature_sensor_config_t config = EZB_ZHA_TEMPERATURE_SENSOR_CONFIG();
|
||||
config.basic_cfg.power_source = power_source;
|
||||
ep_desc = ezb_zha_create_temperature_sensor(ep_id, &config);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// For other device IDs no macro exists, so we create an empty endpoint descriptor
|
||||
ezb_af_ep_config_t config = {
|
||||
.ep_id = ep_id,
|
||||
.app_profile_id = EZB_AF_HA_PROFILE_ID,
|
||||
.app_device_id = device_id,
|
||||
.app_device_version = 0,
|
||||
};
|
||||
ep_desc = ezb_af_create_endpoint_desc(&config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ep_desc;
|
||||
}
|
||||
|
||||
ezb_zcl_cluster_desc_t esphome_zb_default_cluster_dscr_create(uint16_t cluster_id, uint8_t role_mask) {
|
||||
switch (cluster_id) {
|
||||
case EZB_ZCL_CLUSTER_ID_BASIC:
|
||||
@@ -36,6 +66,22 @@ ezb_zcl_cluster_desc_t esphome_zb_default_cluster_dscr_create(uint16_t cluster_i
|
||||
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_ILLUMINANCE_MEASUREMENT:
|
||||
return ezb_zcl_illuminance_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT:
|
||||
return ezb_zcl_temperature_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT:
|
||||
return ezb_zcl_pressure_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT:
|
||||
return ezb_zcl_flow_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT:
|
||||
return ezb_zcl_rel_humidity_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING:
|
||||
return ezb_zcl_occupancy_sensing_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT:
|
||||
return ezb_zcl_carbon_dioxide_measurement_create_cluster_desc(NULL, role_mask);
|
||||
case EZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT:
|
||||
return ezb_zcl_pm2_5_measurement_create_cluster_desc(NULL, role_mask);
|
||||
default: {
|
||||
ezb_zcl_custom_cluster_config_t config = {0};
|
||||
config.cluster_id = cluster_id;
|
||||
@@ -57,6 +103,22 @@ ezb_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, ezb_zcl_cluster_desc_
|
||||
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_ILLUMINANCE_MEASUREMENT:
|
||||
return ezb_zcl_illuminance_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT:
|
||||
return ezb_zcl_temperature_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT:
|
||||
return ezb_zcl_pressure_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT:
|
||||
return ezb_zcl_flow_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT:
|
||||
return ezb_zcl_rel_humidity_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING:
|
||||
return ezb_zcl_occupancy_sensing_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT:
|
||||
return ezb_zcl_carbon_dioxide_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
case EZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT:
|
||||
return ezb_zcl_pm2_5_measurement_cluster_desc_add_attr(cluster_desc, attr_id, value_p);
|
||||
default:
|
||||
return EZB_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ extern "C" {
|
||||
|
||||
#include "esp_zigbee.h"
|
||||
|
||||
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_err_t esphome_zb_cluster_add_or_update_attr(uint16_t cluster_id, ezb_zcl_cluster_desc_t cluster_desc,
|
||||
uint16_t attr_id, void *value_p);
|
||||
ezb_err_t esphome_zb_add_or_update_cluster(uint16_t cluster_id, ezb_af_ep_desc_t ep_desc, uint8_t role_mask);
|
||||
|
||||
@@ -314,14 +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_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
CORE.add_job(_add_binary_sensor, entity, config)
|
||||
|
||||
|
||||
async def zephyr_setup_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
CORE.add_job(_add_sensor, entity, config)
|
||||
|
||||
|
||||
async def zephyr_setup_switch(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
CORE.add_job(_add_switch, entity, config)
|
||||
|
||||
@@ -404,7 +396,7 @@ async def _add_zigbee_ep(
|
||||
cg.add(var.set_parent(hub))
|
||||
|
||||
|
||||
async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
async def add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
await _add_zigbee_ep(
|
||||
entity,
|
||||
config,
|
||||
@@ -416,7 +408,7 @@ async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def _add_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
async def add_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
# Get BACnet engineering unit from unit_of_measurement
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT, "")
|
||||
bacnet_unit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS)
|
||||
|
||||
@@ -3,13 +3,19 @@ packages:
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Garage Door Open 10"
|
||||
name: "Occupancy Sensor"
|
||||
report: "default"
|
||||
use_device_type: false
|
||||
device_class: occupancy
|
||||
cluster: device_class
|
||||
endpoint: 3
|
||||
- platform: template
|
||||
name: "Garage Door Open 12"
|
||||
report: "force"
|
||||
endpoint: 1
|
||||
- platform: template
|
||||
name: "Garage Door Open 13"
|
||||
endpoint: 2
|
||||
use_device_type: false
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
@@ -19,6 +25,25 @@ sensor:
|
||||
unit_of_measurement: "°C"
|
||||
endpoint: 1
|
||||
use_device_type: true
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "Light Sensor"
|
||||
lambda: return 100.0;
|
||||
device_class: illuminance
|
||||
unit_of_measurement: "lx"
|
||||
endpoint: 2
|
||||
use_device_type: true
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "pressure_sensor"
|
||||
unit_of_measurement: Pa
|
||||
device_class: pressure
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "flow_sensor"
|
||||
unit_of_measurement: L/h
|
||||
device_class: volume_flow_rate
|
||||
cluster: device_class
|
||||
|
||||
zigbee:
|
||||
model: zigbee_test
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
packages:
|
||||
zigbee: !include common_esp32.yaml
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "humidity_sensor"
|
||||
unit_of_measurement: "%"
|
||||
device_class: humidity
|
||||
cluster: device_class
|
||||
endpoint: 2
|
||||
use_device_type: false
|
||||
- platform: template
|
||||
name: "pm25_sensor"
|
||||
unit_of_measurement: 'µg/m³'
|
||||
device_class: pm25
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "co2_sensor"
|
||||
unit_of_measurement: ppm
|
||||
device_class: carbon_dioxide
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "pressure_sensor2"
|
||||
unit_of_measurement: hPa
|
||||
device_class: pressure
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "flow_sensor2"
|
||||
unit_of_measurement: m³/h
|
||||
device_class: volume_flow_rate
|
||||
cluster: device_class
|
||||
- platform: template
|
||||
name: "atmospheric_pressure_sensor"
|
||||
unit_of_measurement: hPa
|
||||
device_class: atmospheric_pressure
|
||||
cluster: device_class
|
||||
Reference in New Issue
Block a user