ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
zigbee_attribute_esp32.h
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <limits>
5#include <type_traits>
6
10
11#ifdef USE_ESP32
12#ifdef USE_ZIGBEE
13
14#include "esp_zigbee.h"
15#include "zigbee_esp32.h"
16
17#ifdef USE_SENSOR
19#endif
20#ifdef USE_BINARY_SENSOR
22#endif
23#ifdef USE_SWITCH
25#endif
26#ifdef USE_NUMBER
28#endif
29
30namespace esphome::zigbee {
31
38
39class ZigbeeAttribute final : public Component {
40 public:
41 ZigbeeAttribute(ZigbeeComponent *parent, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
42 uint8_t attr_type, float scale, uint8_t max_size)
43 : zb_(parent),
44 endpoint_id_(endpoint_id),
45 cluster_id_(cluster_id),
46 role_(role),
47 attr_id_(attr_id),
49 max_size_(max_size),
50 scale_(scale) {}
51 void loop() override;
52 template<typename T> void add_attr(T value);
53 template<typename T> void set_attr(const T &value);
54 uint8_t attr_type() { return attr_type_; }
56 void report();
57
58 template<typename F> void add_on_value_callback(F &&callback) { on_value_callback_.add(std::forward<F>(callback)); }
59 void on_value(ezb_zcl_attribute_t attribute) {
60 if (attribute.data.type == this->attr_type() && attribute.data.value) {
61 this->on_value_callback_.call(attribute);
62 }
63 }
64
65#ifdef USE_SENSOR
66 template<typename T> void connect(sensor::Sensor *sensor);
67 template<typename T, typename F> void connect(sensor::Sensor *sensor, F &&f);
68#endif
69#ifdef USE_BINARY_SENSOR
70 template<typename T> void connect(binary_sensor::BinarySensor *sensor);
71#endif
72#ifdef USE_SWITCH
73 template<typename T> void connect(switch_::Switch *device);
74#endif
75#ifdef USE_NUMBER
76 template<typename T> void connect(number::Number *device);
77#endif
78 bool report_enabled = false;
79
80 protected:
81 void set_attr_();
82 void report_(bool has_lock);
84 uint8_t endpoint_id_;
85 uint16_t cluster_id_;
86 uint8_t role_;
87 uint16_t attr_id_;
88 uint8_t attr_type_;
89 uint8_t max_size_;
90 float scale_;
91 void *value_p_{nullptr};
93 bool report_requested_{false};
94 bool force_report_{false};
95 LazyCallbackManager<void(ezb_zcl_attribute_t attribute)> on_value_callback_{};
96 template<typename T> T scale_value_(float value);
97 template<typename T> T invalid_value_();
98};
99
100template<typename T> void ZigbeeAttribute::add_attr(T value) {
101 // Attribute type does never change and add_attr is only called once during startup, so this is safe.
102 // For now we need to support only simple numeric/bool types for (binary) sensors.
103 // For strings and arrays we would need to allocate a buffer of the maximum size.
104 this->value_p_ = (void *) (new T);
105 this->zb_->add_attr(this, this->endpoint_id_, this->cluster_id_, this->role_, this->attr_id_, this->max_size_,
106 std::move(value));
107}
108
109template<typename T> void ZigbeeAttribute::set_attr(const T &value) {
110 *static_cast<T *>(this->value_p_) = value;
111 this->set_attr_requested_ = true;
112 this->enable_loop();
113}
114
115template<typename T> T ZigbeeAttribute::scale_value_(float value) {
116 static_assert(sizeof(T) <= 2 || std::is_floating_point_v<T>);
117 if constexpr (std::is_integral<T>::value) {
118 const float scaled = this->scale_ * value;
119 if (std::isnan(value) || scaled < static_cast<float>(std::numeric_limits<T>::lowest()) ||
120 scaled > static_cast<float>(std::numeric_limits<T>::max())) {
121 return this->invalid_value_<T>(); // 0x8000 / 0xFFFF / 0 for bitmaps
122 }
123 return static_cast<T>(lroundf(scaled));
124 }
125 return static_cast<T>(this->scale_ * value);
126}
127
128template<typename T> T ZigbeeAttribute::invalid_value_() {
129 if constexpr (std::is_integral_v<T>) {
130 if constexpr (std::is_signed_v<T>) {
131 // For signed integer types, NaN is represented by the minimum value
132 return static_cast<T>(std::numeric_limits<T>::min());
133 }
134
135 if (this->attr_type_ >= EZB_ZCL_ATTR_TYPE_UINT8 && this->attr_type_ <= EZB_ZCL_ATTR_TYPE_ENUM16) {
136 // For unsigned integer types and enum, NaN is represented by the maximum value
137 return static_cast<T>(std::numeric_limits<T>::max());
138 }
139
140 // For other integer types, return 0 as a fallback
141 return static_cast<T>(0);
142 }
143
144 return std::numeric_limits<T>::quiet_NaN(); // For floating-point types, return NaN
145}
146
147#ifdef USE_SENSOR
148template<typename T> void ZigbeeAttribute::connect(sensor::Sensor *sensor) {
149 sensor->add_on_state_callback([this](float value) { this->set_attr(this->scale_value_<T>(value)); });
150}
151template<typename T, typename F> void ZigbeeAttribute::connect(sensor::Sensor *sensor, F &&f) {
152 sensor->add_on_state_callback([f = std::forward<F>(f), this](float value) { this->set_attr((T) f(value)); });
153}
154#endif
155#ifdef USE_BINARY_SENSOR
156template<typename T> void ZigbeeAttribute::connect(binary_sensor::BinarySensor *sensor) {
157 sensor->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); });
158}
159#endif
160#ifdef USE_SWITCH
161template<typename T> void ZigbeeAttribute::connect(switch_::Switch *device) {
163 [device](ezb_zcl_attribute_t attribute) { device->control(*(T *) attribute.data.value); });
164 device->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); });
165}
166#endif
167#ifdef USE_NUMBER
168template<typename T> void ZigbeeAttribute::connect(number::Number *device) {
169 // Add min, max and step attributes to the analog output cluster
170 if (this->cluster_id_ == EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT) {
171 this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0045, this->max_size_,
172 device->traits.get_min_value());
173 this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0041, this->max_size_,
174 device->traits.get_max_value());
175 this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x006A, this->max_size_,
176 device->traits.get_step());
177 }
178 this->add_on_value_callback([this, device](ezb_zcl_attribute_t attribute) {
179 auto call = device->make_call();
180 call.set_value(static_cast<float>(*(T *) attribute.data.value) / this->scale_);
181 call.perform();
182 });
183 device->add_on_state_callback([this](float value) { this->set_attr(this->scale_value_<T>(value)); });
184}
185#endif
186
187} // namespace esphome::zigbee
188
189#endif
190#endif
void enable_loop()
Enable this component's loop.
Definition component.h:248
void add_on_state_callback(F &&callback)
Base class for all binary_sensor-type classes.
NumberCall & set_value(float value)
Base-class for all numbers.
Definition number.h:29
NumberCall make_call()
Definition number.h:35
void add_on_state_callback(F &&callback)
Definition number.h:37
NumberTraits traits
Definition number.h:41
Base-class for all sensors.
Definition sensor.h:47
void add_on_state_callback(F &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition sensor.h:121
Base class for all switches.
Definition switch.h:38
void add_on_state_callback(F &&callback)
Set callback for state changes.
Definition switch.h:96
void control(bool target_state)
Control this switch using a boolean state value.
Definition switch.cpp:12
LazyCallbackManager< void(ezb_zcl_attribute_t attribute)> on_value_callback_
void connect(sensor::Sensor *sensor)
void on_value(ezb_zcl_attribute_t attribute)
ZigbeeAttribute(ZigbeeComponent *parent, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, uint8_t attr_type, float scale, uint8_t max_size)
void add_attr(ZigbeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, uint8_t max_size, T value)