Skip to content

Commit 732a7cb

Browse files
feat(zigbee): Add Time cluster support + fix of duplicate indentify cluster (espressif#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>
1 parent 2fecc48 commit 732a7cb

File tree

9 files changed

+181
-8
lines changed

9 files changed

+181
-8
lines changed

Diff for: libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino

+21
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
#define TEMP_SENSOR_ENDPOINT_NUMBER 10
3737
uint8_t button = BOOT_PIN;
3838

39+
// Optional Time cluster variables
40+
struct tm timeinfo;
41+
struct tm *localTime;
42+
int32_t timezone;
43+
3944
ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER);
4045

4146
/************************ Temp sensor *****************************/
@@ -66,6 +71,9 @@ void setup() {
6671
// Optional: Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C)
6772
zbTempSensor.setTolerance(1);
6873

74+
// Optional: Time cluster configuration (default params, as this device will revieve time from coordinator)
75+
zbTempSensor.addTimeCluster();
76+
6977
// Add endpoint to Zigbee Core
7078
Zigbee.addEndpoint(&zbTempSensor);
7179

@@ -85,6 +93,19 @@ void setup() {
8593
}
8694
Serial.println();
8795

96+
// Optional: If time cluster is added, time can be read from the coordinator
97+
timeinfo = zbTempSensor.getTime();
98+
timezone = zbTempSensor.getTimezone();
99+
100+
Serial.println("UTC time:");
101+
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
102+
103+
time_t local = mktime(&timeinfo) + timezone;
104+
localTime = localtime(&local);
105+
106+
Serial.println("Local time with timezone:");
107+
Serial.println(localTime, "%A, %B %d %Y %H:%M:%S");
108+
88109
// Start Temperature sensor reading task
89110
xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL);
90111

Diff for: libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino

+15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ float sensor_max_temp;
4545
float sensor_min_temp;
4646
float sensor_tolerance;
4747

48+
struct tm timeinfo = {}; // Time structure for Time cluster
49+
4850
/****************** Temperature sensor handling *******************/
4951
void recieveSensorTemp(float temperature) {
5052
Serial.printf("Temperature sensor value: %.2f°C\n", temperature);
@@ -71,6 +73,19 @@ void setup() {
7173
//Optional: set Zigbee device name and model
7274
zbThermostat.setManufacturerAndModel("Espressif", "ZigbeeThermostat");
7375

76+
//Optional Time cluster configuration
77+
//example time January 13, 2025 13:30:30 CET
78+
timeinfo.tm_year = 2025 - 1900; // = 2025
79+
timeinfo.tm_mon = 0; // January
80+
timeinfo.tm_mday = 13; // 13th
81+
timeinfo.tm_hour = 12; // 12 hours - 1 hour (CET)
82+
timeinfo.tm_min = 30; // 30 minutes
83+
timeinfo.tm_sec = 30; // 30 seconds
84+
timeinfo.tm_isdst = -1;
85+
86+
// Set time and gmt offset (timezone in seconds -> CET = +3600 seconds)
87+
zbThermostat.addTimeCluster(timeinfo, 3600);
88+
7489
//Add endpoint to Zigbee Core
7590
Zigbee.addEndpoint(&zbThermostat);
7691

Diff for: libraries/Zigbee/src/ZigbeeEP.cpp

+125
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,129 @@ void ZigbeeEP::zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message) {
238238
}
239239
}
240240

241+
void ZigbeeEP::addTimeCluster(tm time, int32_t gmt_offset) {
242+
time_t utc_time = 0;
243+
244+
// Check if time is set
245+
if (time.tm_year > 0) {
246+
// Convert time to UTC
247+
utc_time = mktime(&time);
248+
}
249+
250+
// Create time cluster server attributes
251+
esp_zb_attribute_list_t *time_cluster_server = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME);
252+
esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID, (void *)&gmt_offset);
253+
esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ID, (void *)&utc_time);
254+
// Create time cluster client attributes
255+
esp_zb_attribute_list_t *time_cluster_client = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME);
256+
// Add time clusters to cluster list
257+
esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_server, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
258+
esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_client, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE);
259+
}
260+
261+
void ZigbeeEP::setTime(tm time) {
262+
time_t utc_time = mktime(&time);
263+
esp_zb_lock_acquire(portMAX_DELAY);
264+
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);
265+
esp_zb_lock_release();
266+
}
267+
268+
void ZigbeeEP::setTimezone(int32_t gmt_offset) {
269+
esp_zb_lock_acquire(portMAX_DELAY);
270+
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);
271+
esp_zb_lock_release();
272+
}
273+
274+
tm ZigbeeEP::getTime(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) {
275+
/* Read peer time */
276+
esp_zb_zcl_read_attr_cmd_t read_req;
277+
278+
if (short_addr >= 0) {
279+
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
280+
read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr;
281+
} else {
282+
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
283+
memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t));
284+
}
285+
286+
uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ID};
287+
read_req.attr_number = ZB_ARRAY_LENTH(attributes);
288+
read_req.attr_field = attributes;
289+
290+
read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME;
291+
292+
read_req.zcl_basic_cmd.dst_endpoint = endpoint;
293+
read_req.zcl_basic_cmd.src_endpoint = _endpoint;
294+
295+
// clear read time
296+
_read_time = 0;
297+
298+
log_v("Reading time from endpoint %d", endpoint);
299+
esp_zb_zcl_read_attr_cmd_req(&read_req);
300+
301+
//Wait for response or timeout
302+
if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) {
303+
log_e("Error while reading time");
304+
return tm();
305+
}
306+
307+
struct tm *timeinfo = localtime(&_read_time);
308+
if (timeinfo) {
309+
return *timeinfo;
310+
} else {
311+
log_e("Error while converting time");
312+
return tm();
313+
}
314+
}
315+
316+
int32_t ZigbeeEP::getTimezone(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) {
317+
/* Read peer timezone */
318+
esp_zb_zcl_read_attr_cmd_t read_req;
319+
320+
if (short_addr >= 0) {
321+
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
322+
read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr;
323+
} else {
324+
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
325+
memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t));
326+
}
327+
328+
uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID};
329+
read_req.attr_number = ZB_ARRAY_LENTH(attributes);
330+
read_req.attr_field = attributes;
331+
332+
read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME;
333+
334+
read_req.zcl_basic_cmd.dst_endpoint = endpoint;
335+
read_req.zcl_basic_cmd.src_endpoint = _endpoint;
336+
337+
// clear read timezone
338+
_read_timezone = 0;
339+
340+
log_v("Reading timezone from endpoint %d", endpoint);
341+
esp_zb_zcl_read_attr_cmd_req(&read_req);
342+
343+
//Wait for response or timeout
344+
if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) {
345+
log_e("Error while reading timezone");
346+
}
347+
348+
return _read_timezone;
349+
}
350+
351+
void ZigbeeEP::zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute) {
352+
/* Time cluster attributes */
353+
if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME) {
354+
log_v("Time attribute received");
355+
log_v("Time: %lld", *(uint32_t *)attribute->data.value);
356+
_read_time = *(uint32_t *)attribute->data.value;
357+
xSemaphoreGive(lock);
358+
} else if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S32) {
359+
log_v("Timezone attribute received");
360+
log_v("Timezone: %d", *(int32_t *)attribute->data.value);
361+
_read_timezone = *(int32_t *)attribute->data.value;
362+
xSemaphoreGive(lock);
363+
}
364+
}
365+
241366
#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED

Diff for: libraries/Zigbee/src/ZigbeeEP.h

+18-4
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,27 @@ class ZigbeeEP {
8282
_allow_multiple_binding = bind;
8383
}
8484

85-
// Manufacturer name and model implemented
85+
// Set Manufacturer name and model
8686
void setManufacturerAndModel(const char *name, const char *model);
87-
void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255);
88-
void setBatteryPercentage(uint8_t percentage);
89-
void reportBatteryPercentage();
9087

9188
// Methods to read manufacturer and model name from selected endpoint and short address
9289
char *readManufacturer(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr);
9390
char *readModel(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr);
9491

92+
// Set Power source and battery percentage for battery powered devices
93+
void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255);
94+
void setBatteryPercentage(uint8_t percentage);
95+
void reportBatteryPercentage();
96+
97+
// Set time
98+
void addTimeCluster(tm time = {0}, int32_t gmt_offset = 0); // gmt offset in seconds
99+
void setTime(tm time);
100+
void setTimezone(int32_t gmt_offset);
101+
102+
// Get time from Coordinator or specific endpoint (blocking until response)
103+
struct tm getTime(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0});
104+
int32_t getTimezone(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0}); // gmt offset in seconds
105+
95106
bool epAllowMultipleBinding() {
96107
return _allow_multiple_binding;
97108
}
@@ -104,6 +115,7 @@ class ZigbeeEP {
104115
virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) {};
105116
virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented
106117
virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message);
118+
virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented
107119

108120
virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {};
109121

@@ -120,6 +132,8 @@ class ZigbeeEP {
120132
char *_read_manufacturer;
121133
char *_read_model;
122134
void (*_on_identify)(uint16_t time);
135+
time_t _read_time;
136+
int32_t _read_timezone;
123137

124138
protected:
125139
uint8_t _endpoint;

Diff for: libraries/Zigbee/src/ZigbeeHandlers.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_re
105105
if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) {
106106
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BASIC) {
107107
(*it)->zbReadBasicCluster(&variable->attribute); //method zbReadBasicCluster implemented in the common EP class
108+
} else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_TIME) {
109+
(*it)->zbReadTimeCluster(&variable->attribute); //method zbReadTimeCluster implemented in the common EP class
108110
} else {
109111
(*it)->zbAttributeRead(message->info.cluster, &variable->attribute); //method zbAttributeRead must be implemented in specific EP class
110112
}

Diff for: libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ esp_zb_cluster_list_t *zigbee_carbon_dioxide_sensor_clusters_create(zigbee_carbo
1111
esp_zb_cluster_list_add_carbon_dioxide_measurement_cluster(
1212
cluster_list, esp_zb_carbon_dioxide_measurement_cluster_create(carbon_dioxide_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE
1313
);
14-
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);
1514
return cluster_list;
1615
}
1716

Diff for: libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_flow_sensor_clusters_create(zigbee_flow_sensor_cfg
99
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1010
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1111
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);
12-
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);
1312
return cluster_list;
1413
}
1514

Diff for: libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_occupancy_sensor_clusters_create(zigbee_occupancy_
99
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1010
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1111
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);
12-
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);
1312
return cluster_list;
1413
}
1514

Diff for: libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_pressure_sensor_clusters_create(zigbee_pressure_se
99
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1010
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
1111
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);
12-
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);
1312
return cluster_list;
1413
}
1514

0 commit comments

Comments
 (0)