[zigbee] Add zigbee support for number on esp32 (#20243)

This commit is contained in:
luar123
2026-10-07 08:21:17 -04:00
committed by GitHub
parent d234a4d51e
commit e4d8913680
8 changed files with 105 additions and 6 deletions
+12 -3
View File
@@ -42,6 +42,7 @@ from .const_zephyr import (
from .zigbee_esp32 import (
final_validate_esp32,
validate_binary_sensor_esp32,
validate_number_esp32,
validate_sensor_esp32,
validate_switch_esp32,
zigbee_require_vfs_select,
@@ -120,7 +121,9 @@ SENSOR_SCHEMA = (
SWITCH_SCHEMA = (
cv.Schema({}).extend(_get_base_schema(["generic", "on_off"])).extend(zephyr_switch)
)
NUMBER_SCHEMA = cv.Schema({}).extend(zephyr_number)
NUMBER_SCHEMA = (
cv.Schema({}).extend(_get_base_schema(["generic"])).extend(zephyr_number)
)
def _validate_router_sleepy(config: ConfigType) -> ConfigType:
@@ -269,12 +272,18 @@ async def setup_number(
max_value: float,
step: float,
) -> 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:
if not config.get(CONF_ZIGBEE_ID):
return
from .zigbee_zephyr import zephyr_setup_number
await zephyr_setup_number(entity, config, min_value, max_value, step)
else:
from .zigbee_esp32 import add_component as add_number
CORE.add_job(add_number, entity, config)
def consume_endpoint(config: ConfigType) -> ConfigType:
@@ -320,7 +329,7 @@ def validate_number(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_number_esp32(config)
return consume_endpoint(config)
+2 -1
View File
@@ -145,7 +145,8 @@ class AnalogInputType(IntEnum):
TIME_SECONDS = 0x0E
ANALOG_INPUT_APPTYPE = {
# Same for input and output
ANALOG_APPTYPE = {
(DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS): AnalogInputType.TEMP_DEGREES_C,
(DEVICE_CLASS_HUMIDITY, UNIT_PERCENT): AnalogInputType.RELATIVE_HUMIDITY_PERCENT,
(DEVICE_CLASS_PRESSURE, UNIT_PASCAL): AnalogInputType.PRESSURE_PASCAL,
+1
View File
@@ -33,6 +33,7 @@ CLUSTER_ID = {
"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,
"ANALOG_OUTPUT": cluster_id.EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT,
"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,
@@ -23,6 +23,9 @@
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
namespace esphome::zigbee {
@@ -67,6 +70,9 @@ class ZigbeeAttribute final : public Component {
#endif
#ifdef USE_SWITCH
template<typename T> void connect(switch_::Switch *device);
#endif
#ifdef USE_NUMBER
template<typename T> void connect(number::Number *device);
#endif
bool report_enabled = false;
@@ -157,6 +163,25 @@ template<typename T> void ZigbeeAttribute::connect(switch_::Switch *device) {
device->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); });
}
#endif
#ifdef USE_NUMBER
template<typename T> void ZigbeeAttribute::connect(number::Number *device) {
// Add min, max and step attributes to the analog output cluster
if (this->cluster_id_ == EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT) {
this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0045, this->max_size_,
device->traits.get_min_value());
this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0041, this->max_size_,
device->traits.get_max_value());
this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x006A, this->max_size_,
device->traits.get_step());
}
this->add_on_value_callback([this, device](ezb_zcl_attribute_t attribute) {
auto call = device->make_call();
call.set_value(static_cast<float>(*(T *) attribute.data.value) / this->scale_);
call.perform();
});
device->add_on_state_callback([this](float value) { this->set_attr(this->scale_value_<T>(value)); });
}
#endif
} // namespace esphome::zigbee
@@ -78,6 +78,35 @@ ANALOG_INPUT_EP = {
],
}
ANALOG_OUTPUT_EP = {
CONF_CLUSTERS: [
{
CONF_ID: "ANALOG_OUTPUT",
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: [
+31 -2
View File
@@ -36,7 +36,7 @@ import esphome.final_validate as fv
from esphome.types import ConfigType
from .const import (
ANALOG_INPUT_APPTYPE,
ANALOG_APPTYPE,
BACNET_UNIT_NO_UNITS,
BACNET_UNITS,
CONF_CLUSTER,
@@ -67,6 +67,7 @@ from .const_esp32 import (
)
from .zigbee_ep_esp32 import (
ANALOG_INPUT_EP,
ANALOG_OUTPUT_EP,
BINARY_INPUT_EP,
BINARY_OUTPUT_EP,
BINARY_SENSOR_EP_CONFIGS,
@@ -229,7 +230,7 @@ def validate_sensor_esp32(config: ConfigType) -> ConfigType:
attr[CONF_LAMBDA] = attr[CONF_LAMBDA][unit]
else:
ep = copy.deepcopy(ANALOG_INPUT_EP)
apptype = ANALOG_INPUT_APPTYPE.get((dev_class, unit))
apptype = ANALOG_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:
@@ -261,6 +262,34 @@ def validate_sensor_esp32(config: ConfigType) -> ConfigType:
return config
def validate_number_esp32(config: ConfigType) -> ConfigType:
# 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)
ep = copy.deepcopy(ANALOG_OUTPUT_EP)
apptype = ANALOG_APPTYPE.get((dev_class, unit))
bacunit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS)
if apptype is not None:
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
{
CONF_ATTRIBUTE_ID: 0x100,
CONF_VALUE: (apptype << 16) | 0x0100FFFF,
CONF_TYPE: "UINT32",
},
)
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
{
CONF_ATTRIBUTE_ID: 0x75,
CONF_VALUE: bacunit,
CONF_TYPE: "ENUM16",
},
)
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:
dev_class = config.get(CONF_DEVICE_CLASS)
if config[CONF_CLUSTER] == "device_class":
@@ -72,6 +72,8 @@ ezb_zcl_cluster_desc_t esphome_zb_default_cluster_dscr_create(uint16_t cluster_i
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_ANALOG_OUTPUT:
return ezb_zcl_analog_output_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:
@@ -113,6 +115,8 @@ ezb_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, ezb_zcl_cluster_desc_
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_ANALOG_OUTPUT:
return ezb_zcl_analog_output_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:
+1
View File
@@ -45,6 +45,7 @@ number:
max_value: 100
step: 1
unit_of_measurement: "°C"
device_class: temperature
time:
- platform: zigbee