Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for ZigbeeButton #10898

Open
1 task done
mvermand22 opened this issue Jan 24, 2025 · 2 comments
Open
1 task done

Support for ZigbeeButton #10898

mvermand22 opened this issue Jan 24, 2025 · 2 comments
Assignees
Labels
Type: Feature request Feature request for Arduino ESP32

Comments

@mvermand22
Copy link

mvermand22 commented Jan 24, 2025

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.
@mvermand22 mvermand22 added the Type: Feature request Feature request for Arduino ESP32 label Jan 24, 2025
@mvermand22
Copy link
Author

@P-R-O-C-H-Y could you please have a quick look?
I created this ZigbeeButton based on your other Endpoints.
I just don't understand why ZigbeeTempSensor sends measurement data to the Hub and this ZigbeeButton does not.
I think I just must have made a little config mistake somewhere?

@mvermand22
Copy link
Author

mvermand22 commented Jan 24, 2025

I changed the reportButton to the following, and it now works, though I'm not sure this is the optimal way:

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();
*/

  esp_zb_zcl_on_off_cmd_t cmd_req;
  cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
  // cmd_req.dst_address.addr_short = /* Hub's short address */;
  cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = 0;
  cmd_req.zcl_basic_cmd.dst_endpoint = 1;
  cmd_req.zcl_basic_cmd.src_endpoint = _endpoint;
  if (_current_state) {
    cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID;
  } else {
    cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID;
  }
  // Send the command
  esp_zb_zcl_on_off_cmd_req(&cmd_req);
  log_v("Button state report sent");
}

Especially the hardcoded dst adress and endpoint...
And now it is a command that is being sent and not an attribute change.

@P-R-O-C-H-Y P-R-O-C-H-Y self-assigned this Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature request Feature request for Arduino ESP32
Projects
None yet
Development

No branches or pull requests

2 participants