Skip to content

Commit 6eb99d3

Browse files
feat(zigbee): Add IAS Zone endpoints (Contact Switch + Door/Window Handle) (#10918)
* feat(zigbee): Add IAS Zone endpoints * ci(pre-commit): Apply automatic fixes * fix(ci): Typo fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 2040cba commit 6eb99d3

11 files changed

+533
-0
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS
290290
libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp
291291
libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp
292292
libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp
293+
libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp
294+
libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp
293295
)
294296

295297
set(ARDUINO_LIBRARY_BLE_SRCS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Arduino-ESP32 Zigbee Contact Switch Example
2+
3+
This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) contact switch (IAS Zone),
4+
that can be used for example as window/door sensor having 2 states - closed/open.
5+
6+
# Supported Targets
7+
8+
Currently, this example supports the following targets.
9+
10+
| Supported Targets | ESP32-C6 | ESP32-H2 |
11+
| ----------------- | -------- | -------- |
12+
13+
## Hardware Required
14+
15+
* A USB cable for power supply and programming
16+
17+
### Configure the Project
18+
19+
Set the Button GPIO by changing the `button` variable. By default, it's the pin `BOOT_PIN` (BOOT button on ESP32-C6 and ESP32-H2).
20+
Set the Sensor GPIO by changing the `sensor_pin` variable.
21+
22+
#### Using Arduino IDE
23+
24+
To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits).
25+
26+
* Before Compile/Verify, select the correct board: `Tools -> Board`.
27+
* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ED (end device)`
28+
* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs`
29+
* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port.
30+
* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`.
31+
32+
## Troubleshooting
33+
34+
If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board.
35+
36+
***Important: Make sure you are using a good quality USB cable and that you have a reliable power source***
37+
38+
* **LED not blinking:** Check the wiring connection and the IO selection.
39+
* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed.
40+
* **COM port not detected:** Check the USB cable and the USB to Serial driver installation.
41+
42+
If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute).
43+
44+
## Contribute
45+
46+
To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst)
47+
48+
If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome!
49+
50+
Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else.
51+
52+
## Resources
53+
54+
* Official ESP32 Forum: [Link](https://esp32.com)
55+
* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
56+
* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf)
57+
* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf)
58+
* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @brief This example demonstrates Zigbee contact switch (IAS Zone).
17+
*
18+
* The example demonstrates how to use Zigbee library to create a end device contact switch.
19+
* The contact switch is a Zigbee end device, which is reporting data to the Zigbee network.
20+
*
21+
* Proper Zigbee mode must be selected in Tools->Zigbee mode
22+
* and also the correct partition scheme must be selected in Tools->Partition Scheme.
23+
*
24+
* Please check the README.md for instructions and more detailed description.
25+
*
26+
* Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
27+
*/
28+
29+
#ifndef ZIGBEE_MODE_ED
30+
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
31+
#endif
32+
33+
#include "Zigbee.h"
34+
35+
/* Zigbee contact sensor configuration */
36+
#define CONTACT_SWITCH_ENDPOINT_NUMBER 10
37+
uint8_t button = BOOT_PIN;
38+
uint8_t sensor_pin = 4;
39+
40+
ZigbeeContactSwitch zbContactSwitch = ZigbeeContactSwitch(CONTACT_SWITCH_ENDPOINT_NUMBER);
41+
42+
void setup() {
43+
Serial.begin(115200);
44+
45+
// Init button + switch
46+
pinMode(button, INPUT_PULLUP);
47+
pinMode(sensor_pin, INPUT_PULLUP);
48+
49+
// Optional: set Zigbee device name and model
50+
zbContactSwitch.setManufacturerAndModel("Espressif", "ZigbeeContactSwitch");
51+
52+
// Add endpoint to Zigbee Core
53+
Zigbee.addEndpoint(&zbContactSwitch);
54+
55+
Serial.println("Starting Zigbee...");
56+
// When all EPs are registered, start Zigbee in End Device mode
57+
if (!Zigbee.begin()) {
58+
Serial.println("Zigbee failed to start!");
59+
Serial.println("Rebooting...");
60+
ESP.restart();
61+
} else {
62+
Serial.println("Zigbee started successfully!");
63+
}
64+
Serial.println("Connecting to network");
65+
while (!Zigbee.connected()) {
66+
Serial.print(".");
67+
delay(100);
68+
}
69+
Serial.println();
70+
}
71+
72+
void loop() {
73+
// Checking pin for contact change
74+
static bool contact = false;
75+
if (digitalRead(sensor_pin) == HIGH && !contact) {
76+
// Update contact sensor value
77+
zbContactSwitch.setOpen();
78+
contact = true;
79+
} else if (digitalRead(sensor_pin) == LOW && contact) {
80+
zbContactSwitch.setClosed();
81+
contact = false;
82+
}
83+
84+
// Checking button for factory reset
85+
if (digitalRead(button) == LOW) { // Push button pressed
86+
// Key debounce handling
87+
delay(100);
88+
int startTime = millis();
89+
while (digitalRead(button) == LOW) {
90+
delay(50);
91+
if ((millis() - startTime) > 3000) {
92+
// If key pressed for more than 3secs, factory reset Zigbee and reboot
93+
Serial.println("Resetting Zigbee to factory and rebooting in 1s.");
94+
delay(1000);
95+
Zigbee.factoryReset();
96+
}
97+
}
98+
}
99+
delay(100);
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed",
3+
"requires": [
4+
"CONFIG_SOC_IEEE802154_SUPPORTED=y"
5+
]
6+
}

libraries/Zigbee/src/Zigbee.h

+2
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@
1818
#include "ep/ZigbeeFlowSensor.h"
1919
#include "ep/ZigbeeOccupancySensor.h"
2020
#include "ep/ZigbeeCarbonDioxideSensor.h"
21+
#include "ep/ZigbeeContactSwitch.h"
22+
#include "ep/ZigbeeDoorWindowHandle.h"

libraries/Zigbee/src/ZigbeeEP.h

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class ZigbeeEP {
118118
virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented
119119

120120
virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {};
121+
virtual void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {};
121122

122123
virtual void addBoundDevice(zb_device_params_t *device) {
123124
_bound_devices.push_back(device);

libraries/Zigbee/src/ZigbeeHandlers.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_mes
1010
static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message);
1111
static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message);
1212
static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message);
13+
static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message);
1314
static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message);
1415

1516
// Zigbee action handlers
@@ -24,6 +25,9 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id,
2425
case ESP_ZB_CORE_CMD_IAS_ZONE_ZONE_STATUS_CHANGE_NOT_ID:
2526
ret = zb_cmd_ias_zone_status_change_handler((esp_zb_zcl_ias_zone_status_change_notification_message_t *)message);
2627
break;
28+
case ESP_ZB_CORE_IAS_ZONE_ENROLL_RESPONSE_VALUE_CB_ID:
29+
ret = zb_cmd_ias_zone_enroll_response_handler((esp_zb_zcl_ias_zone_enroll_response_message_t *)message);
30+
break;
2731
case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break;
2832
default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break;
2933
}
@@ -160,6 +164,24 @@ static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone
160164
return ESP_OK;
161165
}
162166

167+
static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {
168+
if (!message) {
169+
log_e("Empty message");
170+
return ESP_FAIL;
171+
}
172+
if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) {
173+
log_e("Received message: error status(%d)", message->info.status);
174+
return ESP_ERR_INVALID_ARG;
175+
}
176+
log_v("IAS Zone Enroll Response received");
177+
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
178+
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
179+
(*it)->zbIASZoneEnrollResponse(message);
180+
}
181+
}
182+
return ESP_OK;
183+
}
184+
163185
static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message) {
164186
if (!message) {
165187
log_e("Empty message");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include "ZigbeeContactSwitch.h"
2+
#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
3+
4+
esp_zb_cluster_list_t *zigbee_contact_switch_clusters_create(zigbee_contact_switch_cfg_t *contact_switch) {
5+
esp_zb_basic_cluster_cfg_t *basic_cfg = contact_switch ? &(contact_switch->basic_cfg) : NULL;
6+
esp_zb_identify_cluster_cfg_t *identify_cfg = contact_switch ? &(contact_switch->identify_cfg) : NULL;
7+
esp_zb_ias_zone_cluster_cfg_t *ias_zone_cfg = contact_switch ? &(contact_switch->ias_zone_cfg) : NULL;
8+
esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
9+
esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
10+
esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
11+
esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, esp_zb_ias_zone_cluster_create(ias_zone_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
12+
return cluster_list;
13+
}
14+
15+
ZigbeeContactSwitch::ZigbeeContactSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) {
16+
_device_id = ESP_ZB_HA_IAS_ZONE_ID;
17+
_zone_status = 0;
18+
_zone_id = 0xff;
19+
_ias_cie_endpoint = 1;
20+
21+
//Create custom contact switch configuration
22+
zigbee_contact_switch_cfg_t contact_switch_cfg = ZIGBEE_DEFAULT_CONTACT_SWITCH_CONFIG();
23+
_cluster_list = zigbee_contact_switch_clusters_create(&contact_switch_cfg);
24+
25+
_ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_IAS_ZONE_ID, .app_device_version = 0};
26+
}
27+
28+
void ZigbeeContactSwitch::setIASClientEndpoint(uint8_t ep_number) {
29+
_ias_cie_endpoint = ep_number;
30+
}
31+
32+
void ZigbeeContactSwitch::setClosed() {
33+
log_v("Setting Contact switch to closed");
34+
uint8_t closed = 0; // ALARM1 = 0, ALARM2 = 0
35+
esp_zb_lock_acquire(portMAX_DELAY);
36+
esp_zb_zcl_set_attribute_val(
37+
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &closed, false
38+
);
39+
esp_zb_lock_release();
40+
_zone_status = closed;
41+
report();
42+
}
43+
44+
void ZigbeeContactSwitch::setOpen() {
45+
log_v("Setting Contact switch to open");
46+
uint8_t open = ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1 | ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM2; // ALARM1 = 1, ALARM2 = 1
47+
esp_zb_lock_acquire(portMAX_DELAY);
48+
esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &open, false);
49+
esp_zb_lock_release();
50+
_zone_status = open;
51+
report();
52+
}
53+
54+
void ZigbeeContactSwitch::report() {
55+
/* Send IAS Zone status changed notification command */
56+
57+
esp_zb_zcl_ias_zone_status_change_notif_cmd_t status_change_notif_cmd;
58+
status_change_notif_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
59+
status_change_notif_cmd.zcl_basic_cmd.src_endpoint = _endpoint;
60+
status_change_notif_cmd.zcl_basic_cmd.dst_endpoint = _ias_cie_endpoint; //default is 1
61+
memcpy(status_change_notif_cmd.zcl_basic_cmd.dst_addr_u.addr_long, _ias_cie_addr, sizeof(esp_zb_ieee_addr_t));
62+
63+
status_change_notif_cmd.zone_status = _zone_status;
64+
status_change_notif_cmd.extend_status = 0;
65+
status_change_notif_cmd.zone_id = _zone_id;
66+
status_change_notif_cmd.delay = 0;
67+
68+
esp_zb_lock_acquire(portMAX_DELAY);
69+
esp_zb_zcl_ias_zone_status_change_notif_cmd_req(&status_change_notif_cmd);
70+
esp_zb_lock_release();
71+
log_v("IAS Zone status changed notification sent");
72+
}
73+
74+
void ZigbeeContactSwitch::zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {
75+
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE) {
76+
log_v("IAS Zone Enroll Response: zone id(%d), status(%d)", message->zone_id, message->response_code);
77+
if (message->response_code == ESP_ZB_ZCL_IAS_ZONE_ENROLL_RESPONSE_CODE_SUCCESS) {
78+
log_v("IAS Zone Enroll Response: success");
79+
esp_zb_lock_acquire(portMAX_DELAY);
80+
memcpy(
81+
_ias_cie_addr,
82+
(*(esp_zb_ieee_addr_t *)
83+
esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_IAS_CIE_ADDRESS_ID)
84+
->data_p),
85+
sizeof(esp_zb_ieee_addr_t)
86+
);
87+
esp_zb_lock_release();
88+
_zone_id = message->zone_id;
89+
}
90+
91+
} else {
92+
log_w("Received message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster);
93+
}
94+
}
95+
96+
#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* Class of Zigbee contact switch (IAS Zone) endpoint inherited from common EP class */
2+
3+
#pragma once
4+
5+
#include "soc/soc_caps.h"
6+
#include "sdkconfig.h"
7+
#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
8+
9+
#include "ZigbeeEP.h"
10+
#include "ha/esp_zigbee_ha_standard.h"
11+
12+
// clang-format off
13+
#define ZIGBEE_DEFAULT_CONTACT_SWITCH_CONFIG() \
14+
{ \
15+
.basic_cfg = \
16+
{ \
17+
.zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, \
18+
.power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_DEFAULT_VALUE, \
19+
}, \
20+
.identify_cfg = \
21+
{ \
22+
.identify_time = ESP_ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, \
23+
}, \
24+
.ias_zone_cfg = \
25+
{ \
26+
.zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, \
27+
.zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_CONTACT_SWITCH, \
28+
.zone_status = 0, \
29+
.ias_cie_addr = ESP_ZB_ZCL_ZONE_IAS_CIE_ADDR_DEFAULT, \
30+
.zone_id = 0xff, \
31+
.zone_ctx = {0, 0, 0, 0}, \
32+
}, \
33+
}
34+
// clang-format on
35+
36+
typedef struct zigbee_contact_switch_cfg_s {
37+
esp_zb_basic_cluster_cfg_t basic_cfg;
38+
esp_zb_identify_cluster_cfg_t identify_cfg;
39+
esp_zb_ias_zone_cluster_cfg_t ias_zone_cfg;
40+
} zigbee_contact_switch_cfg_t;
41+
42+
class ZigbeeContactSwitch : public ZigbeeEP {
43+
public:
44+
ZigbeeContactSwitch(uint8_t endpoint);
45+
~ZigbeeContactSwitch() {}
46+
47+
// Set the IAS Client endpoint number (default is 1)
48+
void setIASClientEndpoint(uint8_t ep_number);
49+
50+
// Set the contact switch value to closed
51+
void setClosed();
52+
53+
// Set the contact switch value to open
54+
void setOpen();
55+
56+
// Report the contact switch value, done automatically after setting the position
57+
void report();
58+
59+
private:
60+
void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) override;
61+
uint8_t _zone_status;
62+
uint8_t _zone_id;
63+
esp_zb_ieee_addr_t _ias_cie_addr;
64+
uint8_t _ias_cie_endpoint;
65+
};
66+
67+
#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED

0 commit comments

Comments
 (0)