Description
Related area
Zigbee
Hardware specification
ESP32-H2, ESP32-C6
Is your feature request related to a problem?
I would like a ZigbeeButton endpoint that allows to get notified of button state change requests from the hub and is also able to report back any change in state that is imposed locally (physical button pressed).
I tried to build it myself, starting from the ZigbeeDimmableLight endpoint implementation, but I can't get the code to report a local change to the Zigbee hub. It simply does not send anything back.
Describe the solution you'd like
I created this so far:
ZigbeeButton.h
/* Class of Zigbee On/Off Button endpoint inherited from common EP class */
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
#include "ZigbeeEP.h"
#include "ha/esp_zigbee_ha_standard.h"
/**
* @brief Zigbee HA standard button device clusters.
* Added here as not supported by ESP Zigbee library.
*
*
*/
typedef struct zigbee_button_cfg_s {
esp_zb_basic_cluster_cfg_t basic_cfg; /*!< Basic cluster configuration, @ref esp_zb_basic_cluster_cfg_s */
esp_zb_identify_cluster_cfg_t identify_cfg; /*!< Identify cluster configuration, @ref esp_zb_identify_cluster_cfg_s */
esp_zb_groups_cluster_cfg_t groups_cfg; /*!< Groups cluster configuration, @ref esp_zb_groups_cluster_cfg_s */
esp_zb_scenes_cluster_cfg_t scenes_cfg; /*!< Scenes cluster configuration, @ref esp_zb_scenes_cluster_cfg_s */
esp_zb_on_off_cluster_cfg_t on_off_cfg; /*!< On off cluster configuration, @ref esp_zb_on_off_cluster_cfg_s */
} zigbee_button_cfg_t;
/**
* @brief Zigbee HA standard button device default config value.
* Added here as not supported by ESP Zigbee library.
*
*/
// clang-format off
#define ZIGBEE_DEFAULT_BUTTON_CONFIG() \
{ \
.basic_cfg = \
{ \
.zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, \
.power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_DEFAULT_VALUE, \
}, \
.identify_cfg = \
{ \
.identify_time = ESP_ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, \
}, \
.groups_cfg = \
{ \
.groups_name_support_id = ESP_ZB_ZCL_GROUPS_NAME_SUPPORT_DEFAULT_VALUE, \
}, \
.scenes_cfg = \
{ \
.scenes_count = ESP_ZB_ZCL_SCENES_SCENE_COUNT_DEFAULT_VALUE, \
.current_scene = ESP_ZB_ZCL_SCENES_CURRENT_SCENE_DEFAULT_VALUE, \
.current_group = ESP_ZB_ZCL_SCENES_CURRENT_GROUP_DEFAULT_VALUE, \
.scene_valid = ESP_ZB_ZCL_SCENES_SCENE_VALID_DEFAULT_VALUE, \
.name_support = ESP_ZB_ZCL_SCENES_NAME_SUPPORT_DEFAULT_VALUE, \
}, \
.on_off_cfg = \
{ \
.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE, \
}, \
}
// clang-format on
class ZigbeeButton : public ZigbeeEP {
public:
ZigbeeButton(uint8_t endpoint);
~ZigbeeButton() {}
void onButtonChange(void (*callback)(bool)) {
_on_button_change = callback;
}
void restoreButton() {
buttonChanged();
}
void setButtonState(bool state);
void setReporting(uint16_t min_interval, uint16_t max_interval, float delta);
void reportButton();
bool getButtonState() {
return _current_state;
}
private:
void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;
void buttonChanged();
// callback function to be called on button change (State)
void (*_on_button_change)(bool);
/**
* @brief Create a standard HA button cluster list.
* Added here as not supported by ESP Zigbee library.
*
* @note This contains basic, identify, groups, scenes, on-off, as server side.
* @param[in] button_cfg Configuration parameters for this cluster lists defined by @ref zigbee_button_cfg_t
*
* @return Pointer to cluster list @ref esp_zb_cluster_list_s
*
*/
esp_zb_cluster_list_t *zigbee_button_clusters_create(zigbee_button_cfg_t *button_cfg);
bool _current_state;
};
#endif // SOC_IEEE802154_SUPPORTED
ZigbeeButton.cpp
#include "ZigbeeButton.h"
#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
#include "esp_zigbee_cluster.h"
ZigbeeButton::ZigbeeButton(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID;
zigbee_button_cfg_t button_cfg = ZIGBEE_DEFAULT_BUTTON_CONFIG();
_cluster_list = zigbee_button_clusters_create(&button_cfg);
_ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID, .app_device_version = 0};
_current_state = false;
}
// set attribute method -> method overridden in child class
void ZigbeeButton::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {
// check the data and call right method
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
if (_current_state != *(bool *)message->attribute.data.value) {
_current_state = *(bool *)message->attribute.data.value;
buttonChanged();
}
return;
} else {
log_w("Received message ignored. Attribute ID: %d not supported for On/Off Button", message->attribute.id);
}
} else {
log_w("Received message ignored. Cluster ID: %d not supported for Button", message->info.cluster);
}
}
void ZigbeeButton::buttonChanged() {
if (_on_button_change) {
_on_button_change(_current_state);
}
}
void ZigbeeButton::setButtonState(bool state) {
_current_state = state;
buttonChanged();
log_v("Updating on/off state to %d", state);
/* Update clusters */
esp_zb_lock_acquire(portMAX_DELAY);
// set on/off state
esp_zb_zcl_set_attribute_val(
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, &_current_state, false
);
esp_zb_lock_release();
}
void ZigbeeButton::reportButton() {
/* Send report attributes command */
esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID;
report_attr_cmd.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI;
report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;
report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint;
esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
esp_zb_lock_release();
log_v("Button state report sent");
}
void ZigbeeButton::setReporting(uint16_t min_interval, uint16_t max_interval, float delta) {
esp_zb_zcl_reporting_info_t reporting_info;
memset(&reporting_info, 0, sizeof(esp_zb_zcl_reporting_info_t));
reporting_info.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV;
reporting_info.ep = _endpoint;
reporting_info.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;
reporting_info.cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE;
reporting_info.attr_id = ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID;
reporting_info.u.send_info.min_interval = min_interval;
reporting_info.u.send_info.max_interval = max_interval;
reporting_info.u.send_info.def_min_interval = min_interval;
reporting_info.u.send_info.def_max_interval = max_interval;
reporting_info.u.send_info.delta.u16 = (uint16_t)(delta * 100); // Convert delta to ZCL uint16_t
reporting_info.dst.profile_id = ESP_ZB_AF_HA_PROFILE_ID;
reporting_info.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC, esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_update_reporting_info(&reporting_info);
esp_zb_lock_release();
}
esp_zb_cluster_list_t *ZigbeeButton::zigbee_button_clusters_create(zigbee_button_cfg_t *button_cfg) {
esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_basic_cluster_create(&button_cfg->basic_cfg);
esp_zb_attribute_list_t *esp_zb_identify_cluster = esp_zb_identify_cluster_create(&button_cfg->identify_cfg);
esp_zb_attribute_list_t *esp_zb_groups_cluster = esp_zb_groups_cluster_create(&button_cfg->groups_cfg);
esp_zb_attribute_list_t *esp_zb_scenes_cluster = esp_zb_scenes_cluster_create(&button_cfg->scenes_cfg);
esp_zb_attribute_list_t *esp_zb_on_off_cluster = esp_zb_on_off_cluster_create(&button_cfg->on_off_cfg);
// ------------------------------ Create cluster list ------------------------------
esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create();
esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(esp_zb_cluster_list, esp_zb_identify_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_groups_cluster(esp_zb_cluster_list, esp_zb_groups_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_scenes_cluster(esp_zb_cluster_list, esp_zb_scenes_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list, esp_zb_on_off_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return esp_zb_cluster_list;
}
#endif // SOC_IEEE802154_SUPPORTED
This is my .ino file:
/** CONFIG
- USB CDC On Boot: "Enabled" <---- !!
- Core Debug Lvele: "None"
- Erase All Flash Before Sketch Upload: "Disabled"
- Flash Frequency: "64MHz"
- Flash Mode: "QIO"
- Flash Size: "4MB (32Mb)""
- JTAG Adapter: "Disabled"
- Partition Scheme: "Zigbee 4MB with spiffs" <---- !!
- Upload Speed: "921600"
- Zigbee Mode: "Zigbee ED (end device)" <---- !!
**/
#ifndef ZIGBEE_MODE_ED
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
#include "ZigbeeButton.h"
/* Zigbee switch configuration */
#define BUTTON_ENDPOINT_NUMBER 5
uint8_t button = BOOT_PIN;
ZigbeeButton zbButton = ZigbeeButton(BUTTON_ENDPOINT_NUMBER);
void setButton(bool state) {
if (state) {
Serial.printf("Button on\n");
} else {
Serial.printf("Button off\n");
}
}
/********************* Arduino functions **************************/
void setup() {
Serial.begin(115200);
Serial.println("Boot...");
// Init button switch
pinMode(button, INPUT_PULLUP);
zbButton.onButtonChange(setButton);
Zigbee.addEndpoint(&zbButton);
Serial.println("Starting Zigbee...");
// When all EPs are registered, start Zigbee in End Device mode
if (!Zigbee.begin()) {
Serial.println("Zigbee failed to start!");
Serial.println("Rebooting...");
ESP.restart();
} else {
Serial.println("Zigbee started successfully!");
}
Serial.println("Connecting to network...");
while (!Zigbee.connected()) {
Serial.print("-");
delay(100);
}
Serial.println();
Serial.println("Connected to network!");
zbButton.setReporting(0, 10, 1);
}
void loop() {
// Checking button for factory reset
if (digitalRead(button) == LOW) { // Push button pressed
// Key debounce handling
delay(100);
int startTime = millis();
int pressed = 0;
while (digitalRead(button) == LOW) {
delay(50);
}
Serial.println("Reporting button state...");
zbButton.setButtonState(false);
delay(500);
zbButton.reportButton();
delay(2000);
zbButton.setButtonState(true);
delay(500);
zbButton.reportButton();
}
delay(100);
}
It compiles and the ESP32-H2 module gets notified from messages sent from the hub (handled in "setButton"), but when I call zbButton.setButtonState(true); and zbButton.reportButton(); then nothing is sent back to the hub.
Any idea what I am doing wrong?
I also added a ZigbeeTempSensor and that nicely reports back the temperature to the hub
Describe alternatives you've considered
No response
Additional context
No response
I have checked existing list of Feature requests and the Contribution Guide
- I confirm I have checked existing list of Feature requests and Contribution Guide.