Skip to content

feat(zigbee): Add callback option for default response message #11613

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

Merged
merged 8 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,49 @@

#include "Zigbee.h"

#define USE_GLOBAL_ON_RESPONSE_CALLBACK 1 // Set to 0 to use local callback specified directly for the endpoint.

/* Zigbee temperature + humidity sensor configuration */
#define TEMP_SENSOR_ENDPOINT_NUMBER 10

#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 55 /* Sleep for 55s will + 5s delay for establishing connection => data reported every 1 minute */
#define REPORT_TIMEOUT 1000 /* Timeout for response from coordinator in ms */

uint8_t button = BOOT_PIN;

ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER);

uint8_t dataToSend = 2; // Temperature and humidity values are reported in same endpoint, so 2 values are reported
bool resend = false;

/************************ Callbacks *****************************/
#if USE_GLOBAL_ON_RESPONSE_CALLBACK
void onGlobalResponse(zb_cmd_type_t command, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster) {
Serial.printf("Global response command: %d, status: %s, endpoint: %d, cluster: 0x%04x\r\n", command, esp_zb_zcl_status_to_name(status), endpoint, cluster);
if ((command == ZB_CMD_REPORT_ATTRIBUTE) && (endpoint == TEMP_SENSOR_ENDPOINT_NUMBER)) {
switch (status) {
case ESP_ZB_ZCL_STATUS_SUCCESS: dataToSend--; break;
case ESP_ZB_ZCL_STATUS_FAIL: resend = true; break;
default: break; // add more statuses like ESP_ZB_ZCL_STATUS_INVALID_VALUE, ESP_ZB_ZCL_STATUS_TIMEOUT etc.
}
}
}
#else
void onResponse(zb_cmd_type_t command, esp_zb_zcl_status_t status) {
Serial.printf("Response command: %d, status: %s\r\n", command, esp_zb_zcl_status_to_name(status));
if (command == ZB_CMD_REPORT_ATTRIBUTE) {
switch (status) {
case ESP_ZB_ZCL_STATUS_SUCCESS: dataToSend--; break;
case ESP_ZB_ZCL_STATUS_FAIL: resend = true; break;
default: break; // add more statuses like ESP_ZB_ZCL_STATUS_INVALID_VALUE, ESP_ZB_ZCL_STATUS_TIMEOUT etc.
}
}
}
#endif

/************************ Temp sensor *****************************/
void meausureAndSleep() {
static void meausureAndSleep(void *arg) {
// Measure temperature sensor value
float temperature = temperatureRead();

Expand All @@ -55,13 +86,35 @@ void meausureAndSleep() {
zbTempSensor.setHumidity(humidity);

// Report temperature and humidity values
zbTempSensor.report();
zbTempSensor.report(); // reports temperature and humidity values (if humidity sensor is not added, only temperature is reported)
Serial.printf("Reported temperature: %.2f°C, Humidity: %.2f%%\r\n", temperature, humidity);

// Add small delay to allow the data to be sent before going to sleep
delay(100);
unsigned long startTime = millis();
const unsigned long timeout = REPORT_TIMEOUT;

Serial.printf("Waiting for data report to be confirmed \r\n");
// Wait until data was successfully sent
int tries = 0;
const int maxTries = 3;
while (dataToSend != 0 && tries < maxTries) {
if (resend) {
Serial.println("Resending data on failure!");
resend = false;
dataToSend = 2;
zbTempSensor.report(); // report again
}
if (millis() - startTime >= timeout) {
Serial.println("\nReport timeout! Report Again");
dataToSend = 2;
zbTempSensor.report(); // report again
startTime = millis();
tries++;
}
Serial.printf(".");
delay(50); // 50ms delay to avoid busy-waiting
}

// Put device to deep sleep
// Put device to deep sleep after data was sent successfully or timeout
Serial.println("Going to sleep now");
esp_deep_sleep_start();
}
Expand Down Expand Up @@ -92,6 +145,16 @@ void setup() {
// Add humidity cluster to the temperature sensor device with min, max and tolerance values
zbTempSensor.addHumiditySensor(0, 100, 1);

// Set callback for default response to handle status of reported data, there are 2 options.

#if USE_GLOBAL_ON_RESPONSE_CALLBACK
// Global callback for all endpoints with more params to determine the endpoint and cluster in the callback function.
Zigbee.onGlobalDefaultResponse(onGlobalResponse);
#else
// Callback specified for endpoint
zbTempSensor.onDefaultResponse(onResponse);
#endif

// Add endpoint to Zigbee Core
Zigbee.addEndpoint(&zbTempSensor);

Expand All @@ -117,8 +180,8 @@ void setup() {
Serial.println();
Serial.println("Successfully connected to Zigbee network");

// Delay approx 1s (may be adjusted) to allow establishing proper connection with coordinator, needed for sleepy devices
delay(1000);
// Start Temperature sensor reading task
xTaskCreate(meausureAndSleep, "temp_sensor_update", 2048, NULL, 10, NULL);
}

void loop() {
Expand All @@ -141,7 +204,5 @@ void loop() {
}
}
}

// Call the function to measure temperature and put the device to sleep
meausureAndSleep();
delay(100);
}
2 changes: 2 additions & 0 deletions libraries/Zigbee/keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ zb_power_source_t KEYWORD1
ZigbeeWindowCoveringType KEYWORD1
ZigbeeFanMode KEYWORD1
ZigbeeFanModeSequence KEYWORD1
zb_cmd_type_t KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
Expand Down Expand Up @@ -96,6 +97,7 @@ getTime KEYWORD2
getTimezone KEYWORD2
addOTAClient KEYWORD2
clearBoundDevices KEYWORD2
onDefaultResponse KEYWORD2

# ZigbeeLight + ZigbeeColorDimmableLight
onLightChange KEYWORD2
Expand Down
3 changes: 3 additions & 0 deletions libraries/Zigbee/src/Zigbee.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#pragma once

// Common types and functions
#include "ZigbeeTypes.h"

// Core
#include "ZigbeeCore.h"
#include "ZigbeeEP.h"
Expand Down
7 changes: 7 additions & 0 deletions libraries/Zigbee/src/ZigbeeCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ZigbeeCore::ZigbeeCore() {
_scan_duration = 3; // default scan duration
_rx_on_when_idle = true;
_debug = false;
_global_default_response_cb = nullptr; // Initialize global callback to nullptr
if (!lock) {
lock = xSemaphoreCreateBinary();
if (lock == NULL) {
Expand Down Expand Up @@ -792,6 +793,12 @@ const char *ZigbeeCore::getDeviceTypeString(esp_zb_ha_standard_devices_t deviceI
}
}

void ZigbeeCore::callDefaultResponseCallback(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster) {
if (_global_default_response_cb) {
_global_default_response_cb(resp_to_cmd, status, endpoint, cluster);
}
}

ZigbeeCore Zigbee = ZigbeeCore();

#endif // CONFIG_ZB_ENABLED
12 changes: 12 additions & 0 deletions libraries/Zigbee/src/ZigbeeCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "aps/esp_zigbee_aps.h"
#include <esp32-hal-log.h>
#include <list>
#include "ZigbeeTypes.h"
#include "ZigbeeEP.h"
class ZigbeeEP;

Expand Down Expand Up @@ -103,6 +104,9 @@ class ZigbeeCore {
SemaphoreHandle_t lock;
bool _debug;

// Global default response callback
void (*_global_default_response_cb)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster);

bool zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs);
static void scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor);
const char *getDeviceTypeString(esp_zb_ha_standard_devices_t deviceId);
Expand Down Expand Up @@ -176,6 +180,14 @@ class ZigbeeCore {
return _debug;
}

// Set global default response callback
void onGlobalDefaultResponse(void (*callback)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster)) {
_global_default_response_cb = callback;
}

// Call global default response callback (for internal use)
void callDefaultResponseCallback(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster);

// Friend function declaration to allow access to private members
friend void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct);
friend bool zb_apsde_data_indication_handler(esp_zb_apsde_data_ind_t ind);
Expand Down
12 changes: 11 additions & 1 deletion libraries/Zigbee/src/ZigbeeEP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,17 @@ void ZigbeeEP::removeBoundDevice(zb_device_params_t *device) {
log_w("No matching device found for removal");
}

const char *ZigbeeEP::esp_zb_zcl_status_to_name(esp_zb_zcl_status_t status) {
void ZigbeeEP::zbDefaultResponse(const esp_zb_zcl_cmd_default_resp_message_t *message) {
log_v("Default response received for endpoint %d", _endpoint);
log_v("Status code: %s", esp_zb_zcl_status_to_name(message->status_code));
log_v("Response to command: %d", message->resp_to_cmd);
if (_on_default_response) {
_on_default_response((zb_cmd_type_t)message->resp_to_cmd, message->status_code);
}
}

// Global function implementation
const char *esp_zb_zcl_status_to_name(esp_zb_zcl_status_t status) {
switch (status) {
case ESP_ZB_ZCL_STATUS_SUCCESS: return "Success";
case ESP_ZB_ZCL_STATUS_FAIL: return "Fail";
Expand Down
15 changes: 12 additions & 3 deletions libraries/Zigbee/src/ZigbeeEP.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ typedef enum {
ZB_POWER_SOURCE_BATTERY = 0x03,
} zb_power_source_t;

// Global function for converting ZCL status to name
const char *esp_zb_zcl_status_to_name(esp_zb_zcl_status_t status);

/* Zigbee End Device Class */
class ZigbeeEP {
public:
Expand Down Expand Up @@ -138,6 +141,7 @@ class ZigbeeEP {
virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented
virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {};
virtual void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {};
virtual void zbDefaultResponse(const esp_zb_zcl_cmd_default_resp_message_t *message); //already implemented

virtual void addBoundDevice(zb_device_params_t *device) {
_bound_devices.push_back(device);
Expand All @@ -156,17 +160,21 @@ class ZigbeeEP {
_on_identify = callback;
}

void onDefaultResponse(void (*callback)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status)) {
_on_default_response = callback;
}

// Convert ZCL status to name

private:
char *_read_manufacturer;
char *_read_model;
void (*_on_identify)(uint16_t time);
void (*_on_default_response)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status);
time_t _read_time;
int32_t _read_timezone;

protected:
// Convert ZCL status to name
const char *esp_zb_zcl_status_to_name(esp_zb_zcl_status_t status);

uint8_t _endpoint;
esp_zb_ha_standard_devices_t _device_id;
esp_zb_endpoint_config_t _ep_config;
Expand All @@ -179,6 +187,7 @@ class ZigbeeEP {
zb_power_source_t _power_source;
uint8_t _time_status;

// Friend class declaration to allow access to protected members
friend class ZigbeeCore;
};

Expand Down
10 changes: 10 additions & 0 deletions libraries/Zigbee/src/ZigbeeHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,16 @@ static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_m
"Received default response: from address(0x%x), src_endpoint(%d) to dst_endpoint(%d), cluster(0x%x) with status 0x%x",
message->info.src_address.u.short_addr, message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster, message->status_code
);

// Call global callback if set
Zigbee.callDefaultResponseCallback((zb_cmd_type_t)message->resp_to_cmd, message->status_code, message->info.dst_endpoint, message->info.cluster);

// List through all Zigbee EPs and call the callback function, with the message
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbDefaultResponse(message); //method zbDefaultResponse is implemented in the common EP class
}
}
return ESP_OK;
}

Expand Down
30 changes: 30 additions & 0 deletions libraries/Zigbee/src/ZigbeeTypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "esp_zigbee_core.h"

// Foundation Command Types
typedef enum {
ZB_CMD_READ_ATTRIBUTE = 0x00U, /*!< Read attributes command */
ZB_CMD_READ_ATTRIBUTE_RESPONSE = 0x01U, /*!< Read attributes response command */
ZB_CMD_WRITE_ATTRIBUTE = 0x02U, /*!< Write attributes foundation command */
ZB_CMD_WRITE_ATTRIBUTE_UNDIVIDED = 0x03U, /*!< Write attributes undivided command */
ZB_CMD_WRITE_ATTRIBUTE_RESPONSE = 0x04U, /*!< Write attributes response command */
ZB_CMD_WRITE_ATTRIBUTE_NO_RESPONSE = 0x05U, /*!< Write attributes no response command */
ZB_CMD_CONFIGURE_REPORTING = 0x06U, /*!< Configure reporting command */
ZB_CMD_CONFIGURE_REPORTING_RESPONSE = 0x07U, /*!< Configure reporting response command */
ZB_CMD_READ_REPORTING_CONFIG = 0x08U, /*!< Read reporting config command */
ZB_CMD_READ_REPORTING_CONFIG_RESPONSE = 0x09U, /*!< Read reporting config response command */
ZB_CMD_REPORT_ATTRIBUTE = 0x0aU, /*!< Report attribute command */
ZB_CMD_DEFAULT_RESPONSE = 0x0bU, /*!< Default response command */
ZB_CMD_DISCOVER_ATTRIBUTES = 0x0cU, /*!< Discover attributes command */
ZB_CMD_DISCOVER_ATTRIBUTES_RESPONSE = 0x0dU, /*!< Discover attributes response command */
ZB_CMD_READ_ATTRIBUTE_STRUCTURED = 0x0eU, /*!< Read attributes structured */
ZB_CMD_WRITE_ATTRIBUTE_STRUCTURED = 0x0fU, /*!< Write attributes structured */
ZB_CMD_WRITE_ATTRIBUTE_STRUCTURED_RESPONSE = 0x10U, /*!< Write attributes structured response */
ZB_CMD_DISCOVER_COMMANDS_RECEIVED = 0x11U, /*!< Discover Commands Received command */
ZB_CMD_DISCOVER_COMMANDS_RECEIVED_RESPONSE = 0x12U, /*!< Discover Commands Received response command */
ZB_CMD_DISCOVER_COMMANDS_GENERATED = 0x13U, /*!< Discover Commands Generated command */
ZB_CMD_DISCOVER_COMMANDS_GENERATED_RESPONSE = 0x14U, /*!< Discover Commands Generated response command */
ZB_CMD_DISCOVER_ATTRIBUTES_EXTENDED = 0x15U, /*!< Discover attributes extended command */
ZB_CMD_DISCOVER_ATTRIBUTES_EXTENDED_RESPONSE = 0x16U, /*!< Discover attributes extended response command */
} zb_cmd_type_t;
Loading