feat(zigbee): Add Time cluster support + fix of duplicate indentify cluster (#10863)

* feat(zigbee): Add Time cluster support

* fix(zigbee): Remove duplicate of identify cluster

* feat(zigbee): Remove unused variables in addTimeCluster

* feat(zigbee): Update examples with optional Time cluster

* ci(pre-commit): Apply automatic fixes

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Jan Procházka 2025-01-30 18:11:40 +01:00 committed by GitHub
parent 2fecc482b7
commit 732a7cb4ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 181 additions and 8 deletions

View file

@ -36,6 +36,11 @@
#define TEMP_SENSOR_ENDPOINT_NUMBER 10
uint8_t button = BOOT_PIN;
// Optional Time cluster variables
struct tm timeinfo;
struct tm *localTime;
int32_t timezone;
ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER);
/************************ Temp sensor *****************************/
@ -66,6 +71,9 @@ void setup() {
// Optional: Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C)
zbTempSensor.setTolerance(1);
// Optional: Time cluster configuration (default params, as this device will revieve time from coordinator)
zbTempSensor.addTimeCluster();
// Add endpoint to Zigbee Core
Zigbee.addEndpoint(&zbTempSensor);
@ -85,6 +93,19 @@ void setup() {
}
Serial.println();
// Optional: If time cluster is added, time can be read from the coordinator
timeinfo = zbTempSensor.getTime();
timezone = zbTempSensor.getTimezone();
Serial.println("UTC time:");
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
time_t local = mktime(&timeinfo) + timezone;
localTime = localtime(&local);
Serial.println("Local time with timezone:");
Serial.println(localTime, "%A, %B %d %Y %H:%M:%S");
// Start Temperature sensor reading task
xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL);

View file

@ -45,6 +45,8 @@ float sensor_max_temp;
float sensor_min_temp;
float sensor_tolerance;
struct tm timeinfo = {}; // Time structure for Time cluster
/****************** Temperature sensor handling *******************/
void recieveSensorTemp(float temperature) {
Serial.printf("Temperature sensor value: %.2f°C\n", temperature);
@ -71,6 +73,19 @@ void setup() {
//Optional: set Zigbee device name and model
zbThermostat.setManufacturerAndModel("Espressif", "ZigbeeThermostat");
//Optional Time cluster configuration
//example time January 13, 2025 13:30:30 CET
timeinfo.tm_year = 2025 - 1900; // = 2025
timeinfo.tm_mon = 0; // January
timeinfo.tm_mday = 13; // 13th
timeinfo.tm_hour = 12; // 12 hours - 1 hour (CET)
timeinfo.tm_min = 30; // 30 minutes
timeinfo.tm_sec = 30; // 30 seconds
timeinfo.tm_isdst = -1;
// Set time and gmt offset (timezone in seconds -> CET = +3600 seconds)
zbThermostat.addTimeCluster(timeinfo, 3600);
//Add endpoint to Zigbee Core
Zigbee.addEndpoint(&zbThermostat);

View file

@ -238,4 +238,129 @@ void ZigbeeEP::zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message) {
}
}
void ZigbeeEP::addTimeCluster(tm time, int32_t gmt_offset) {
time_t utc_time = 0;
// Check if time is set
if (time.tm_year > 0) {
// Convert time to UTC
utc_time = mktime(&time);
}
// Create time cluster server attributes
esp_zb_attribute_list_t *time_cluster_server = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME);
esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID, (void *)&gmt_offset);
esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ID, (void *)&utc_time);
// Create time cluster client attributes
esp_zb_attribute_list_t *time_cluster_client = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME);
// Add time clusters to cluster list
esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_server, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_client, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);
}
void ZigbeeEP::setTime(tm time) {
time_t utc_time = mktime(&time);
esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_TIME, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TIME_TIME_ID, &utc_time, false);
esp_zb_lock_release();
}
void ZigbeeEP::setTimezone(int32_t gmt_offset) {
esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_TIME, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID, &gmt_offset, false);
esp_zb_lock_release();
}
tm ZigbeeEP::getTime(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) {
/* Read peer time */
esp_zb_zcl_read_attr_cmd_t read_req;
if (short_addr >= 0) {
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr;
} else {
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t));
}
uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ID};
read_req.attr_number = ZB_ARRAY_LENTH(attributes);
read_req.attr_field = attributes;
read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME;
read_req.zcl_basic_cmd.dst_endpoint = endpoint;
read_req.zcl_basic_cmd.src_endpoint = _endpoint;
// clear read time
_read_time = 0;
log_v("Reading time from endpoint %d", endpoint);
esp_zb_zcl_read_attr_cmd_req(&read_req);
//Wait for response or timeout
if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) {
log_e("Error while reading time");
return tm();
}
struct tm *timeinfo = localtime(&_read_time);
if (timeinfo) {
return *timeinfo;
} else {
log_e("Error while converting time");
return tm();
}
}
int32_t ZigbeeEP::getTimezone(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) {
/* Read peer timezone */
esp_zb_zcl_read_attr_cmd_t read_req;
if (short_addr >= 0) {
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr;
} else {
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t));
}
uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID};
read_req.attr_number = ZB_ARRAY_LENTH(attributes);
read_req.attr_field = attributes;
read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME;
read_req.zcl_basic_cmd.dst_endpoint = endpoint;
read_req.zcl_basic_cmd.src_endpoint = _endpoint;
// clear read timezone
_read_timezone = 0;
log_v("Reading timezone from endpoint %d", endpoint);
esp_zb_zcl_read_attr_cmd_req(&read_req);
//Wait for response or timeout
if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) {
log_e("Error while reading timezone");
}
return _read_timezone;
}
void ZigbeeEP::zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute) {
/* Time cluster attributes */
if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME) {
log_v("Time attribute received");
log_v("Time: %lld", *(uint32_t *)attribute->data.value);
_read_time = *(uint32_t *)attribute->data.value;
xSemaphoreGive(lock);
} else if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S32) {
log_v("Timezone attribute received");
log_v("Timezone: %d", *(int32_t *)attribute->data.value);
_read_timezone = *(int32_t *)attribute->data.value;
xSemaphoreGive(lock);
}
}
#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED

View file

@ -82,16 +82,27 @@ public:
_allow_multiple_binding = bind;
}
// Manufacturer name and model implemented
// Set Manufacturer name and model
void setManufacturerAndModel(const char *name, const char *model);
void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255);
void setBatteryPercentage(uint8_t percentage);
void reportBatteryPercentage();
// Methods to read manufacturer and model name from selected endpoint and short address
char *readManufacturer(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr);
char *readModel(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr);
// Set Power source and battery percentage for battery powered devices
void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255);
void setBatteryPercentage(uint8_t percentage);
void reportBatteryPercentage();
// Set time
void addTimeCluster(tm time = {0}, int32_t gmt_offset = 0); // gmt offset in seconds
void setTime(tm time);
void setTimezone(int32_t gmt_offset);
// Get time from Coordinator or specific endpoint (blocking until response)
struct tm getTime(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0});
int32_t getTimezone(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0}); // gmt offset in seconds
bool epAllowMultipleBinding() {
return _allow_multiple_binding;
}
@ -104,6 +115,7 @@ public:
virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) {};
virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented
virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message);
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) {};
@ -120,6 +132,8 @@ private:
char *_read_manufacturer;
char *_read_model;
void (*_on_identify)(uint16_t time);
time_t _read_time;
int32_t _read_timezone;
protected:
uint8_t _endpoint;

View file

@ -105,6 +105,8 @@ static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_re
if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BASIC) {
(*it)->zbReadBasicCluster(&variable->attribute); //method zbReadBasicCluster implemented in the common EP class
} else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_TIME) {
(*it)->zbReadTimeCluster(&variable->attribute); //method zbReadTimeCluster implemented in the common EP class
} else {
(*it)->zbAttributeRead(message->info.cluster, &variable->attribute); //method zbAttributeRead must be implemented in specific EP class
}

View file

@ -11,7 +11,6 @@ esp_zb_cluster_list_t *zigbee_carbon_dioxide_sensor_clusters_create(zigbee_carbo
esp_zb_cluster_list_add_carbon_dioxide_measurement_cluster(
cluster_list, esp_zb_carbon_dioxide_measurement_cluster_create(carbon_dioxide_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE
);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}

View file

@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_flow_sensor_clusters_create(zigbee_flow_sensor_cfg
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_flow_meas_cluster(cluster_list, esp_zb_flow_meas_cluster_create(flow_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}

View file

@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_occupancy_sensor_clusters_create(zigbee_occupancy_
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_occupancy_sensing_cluster(cluster_list, esp_zb_occupancy_sensing_cluster_create(occupancy_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}

View file

@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_pressure_sensor_clusters_create(zigbee_pressure_se
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_pressure_meas_cluster(cluster_list, esp_zb_pressure_meas_cluster_create(pressure_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}