Skip to content

Commit b4beb16

Browse files
committed
feat(zigbee): Add Binary Output support
1 parent 2592a7b commit b4beb16

File tree

5 files changed

+247
-30
lines changed

5 files changed

+247
-30
lines changed

libraries/Zigbee/examples/Zigbee_Binary_Input/README.md renamed to libraries/Zigbee/examples/Zigbee_Binary_Input_Output/README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Arduino-ESP32 Zigbee Binary Input Example
1+
# Arduino-ESP32 Zigbee Binary Input Output Example
22

3-
This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) binary input device with two different applications: HVAC fan status and security zone armed status.
3+
This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) binary input/output device with multiple applications: HVAC fan status/control, security zone armed status, and HVAC humidifier control.
44

55
# Supported Targets
66

@@ -9,12 +9,17 @@ Currently, this example supports the following targets.
99
| Supported Targets | ESP32-C6 | ESP32-H2 |
1010
| ----------------- | -------- | -------- |
1111

12-
## Binary Input Functions
13-
14-
* The example implements two binary inputs:
15-
- HVAC Fan Status: Reports the current state of a fan
16-
- Security Zone Armed: Reports the armed state of a security zone
17-
* By clicking the button (BOOT) on this board, it will toggle both binary inputs and immediately send a report of their states to the network.
12+
## Binary Input/Output Functions
13+
14+
* The example implements three binary devices:
15+
- **Binary Fan Device (Endpoint 1)**:
16+
- Binary Input: HVAC Fan Status - Reports the current state of a fan
17+
- Binary Output: HVAC Fan - Controls the fan switch with callback function
18+
- **Binary Zone Device (Endpoint 2)**:
19+
- Binary Input: Security Zone Armed - Reports the armed state of a security zone
20+
- **Binary Humidifier Device (Endpoint 3)**:
21+
- Binary Output: HVAC Humidifier - Controls the humidifier switch with callback function
22+
* By clicking the button (BOOT) on this board, it will toggle all binary inputs/outputs and immediately send a report of their states to the network.
1823
* Holding the button for more than 3 seconds will trigger a factory reset of the Zigbee device.
1924

2025
## Hardware Required

libraries/Zigbee/examples/Zigbee_Binary_Input/Zigbee_Binary_Input.ino renamed to libraries/Zigbee/examples/Zigbee_Binary_Input_Output/Zigbee_Binary_Input_Output.ino

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
// limitations under the License.
1414

1515
/**
16-
* @brief This example demonstrates Zigbee binary input device.
16+
* @brief This example demonstrates Zigbee binary input/output device.
1717
*
18-
* The example demonstrates how to use Zigbee library to create an end device binary sensor device.
18+
* The example demonstrates how to use Zigbee library to create an end device binary sensor/switch device.
1919
*
2020
* Proper Zigbee mode must be selected in Tools->Zigbee mode
2121
* and also the correct partition scheme must be selected in Tools->Partition Scheme.
@@ -34,13 +34,28 @@
3434
/* Zigbee binary sensor device configuration */
3535
#define BINARY_DEVICE_ENDPOINT_NUMBER 1
3636

37-
uint8_t binaryPin = A0;
3837
uint8_t button = BOOT_PIN;
3938

4039
ZigbeeBinary zbBinaryFan = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER);
4140
ZigbeeBinary zbBinaryZone = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER + 1);
41+
ZigbeeBinary zbBinaryHumidifier = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER + 2);
4242

43-
bool binaryStatus = false;
43+
bool zoneStatus = false;
44+
45+
void fanSwitch(bool state) {
46+
Serial.println("Fan switch changed to: " + String(state));
47+
if (state) {
48+
zbBinaryFan.setBinaryInput(state);
49+
zbBinaryFan.reportBinaryInput();
50+
} else {
51+
zbBinaryFan.setBinaryInput(state);
52+
zbBinaryFan.reportBinaryInput();
53+
}
54+
}
55+
56+
void humidifierSwitch(bool state) {
57+
Serial.println("Humidifier switch changed to: " + String(state));
58+
}
4459

4560
void setup() {
4661
Serial.begin(115200);
@@ -55,19 +70,33 @@ void setup() {
5570
// Optional: set Zigbee device name and model
5671
zbBinaryFan.setManufacturerAndModel("Espressif", "ZigbeeBinarySensor");
5772

58-
// Set up binary fan status input (HVAC)
73+
// Set up binary fan status input + switch output (HVAC)
5974
zbBinaryFan.addBinaryInput();
6075
zbBinaryFan.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_HVAC_FAN_STATUS);
6176
zbBinaryFan.setBinaryInputDescription("Fan Status");
6277

78+
zbBinaryFan.addBinaryOutput();
79+
zbBinaryFan.setBinaryOutputApplication(BINARY_OUTPUT_APPLICATION_TYPE_HVAC_FAN);
80+
zbBinaryFan.setBinaryOutputDescription("Fan Switch");
81+
82+
zbBinaryFan.onBinaryOutputChange(fanSwitch);
83+
6384
// Set up binary zone armed input (Security)
6485
zbBinaryZone.addBinaryInput();
6586
zbBinaryZone.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_ZONE_ARMED);
6687
zbBinaryZone.setBinaryInputDescription("Zone Armed");
6788

89+
// Set up binary humidifier output (HVAC)
90+
zbBinaryHumidifier.addBinaryOutput();
91+
zbBinaryHumidifier.setBinaryOutputApplication(BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HUMIDIFIER);
92+
zbBinaryHumidifier.setBinaryOutputDescription("Humidifier Switch");
93+
94+
zbBinaryHumidifier.onBinaryOutputChange(humidifierSwitch);
95+
6896
// Add endpoints to Zigbee Core
6997
Zigbee.addEndpoint(&zbBinaryFan);
7098
Zigbee.addEndpoint(&zbBinaryZone);
99+
Zigbee.addEndpoint(&zbBinaryHumidifier);
71100

72101
Serial.println("Starting Zigbee...");
73102
// When all EPs are registered, start Zigbee in End Device mode
@@ -101,12 +130,19 @@ void loop() {
101130
Zigbee.factoryReset();
102131
}
103132
}
104-
// Toggle binary input
105-
binaryStatus = !binaryStatus;
106-
zbBinaryFan.setBinaryInput(binaryStatus);
107-
zbBinaryZone.setBinaryInput(binaryStatus);
108-
zbBinaryFan.reportBinaryInput();
133+
134+
// Toggle fan
135+
zbBinaryFan.setBinaryOutput(!zbBinaryFan.getBinaryOutput());
136+
zbBinaryFan.reportBinaryOutput();
137+
138+
// Toggle zone
139+
zoneStatus = !zoneStatus;
140+
zbBinaryZone.setBinaryInput(zoneStatus);
109141
zbBinaryZone.reportBinaryInput();
142+
143+
// Toggle humidifier
144+
zbBinaryHumidifier.setBinaryOutput(!zbBinaryHumidifier.getBinaryOutput());
145+
zbBinaryHumidifier.reportBinaryOutput();
110146
}
111147
delay(100);
112148
}

libraries/Zigbee/src/ep/ZigbeeBinary.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,35 @@ bool ZigbeeBinary::addBinaryInput() {
4141
return true;
4242
}
4343

44+
bool ZigbeeBinary::addBinaryOutput() {
45+
esp_zb_attribute_list_t *esp_zb_binary_output_cluster = esp_zb_binary_output_cluster_create(NULL);
46+
47+
// Create default description for Binary Output
48+
char default_description[] = "\x0D"
49+
"Binary Output";
50+
uint32_t application_type = 0x00000000 | (0x04 << 24); // Group ID 0x04
51+
52+
esp_err_t ret = esp_zb_binary_output_cluster_add_attr(esp_zb_binary_output_cluster, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, (void *)default_description);
53+
if (ret != ESP_OK) {
54+
log_e("Failed to add description attribute: 0x%x: %s", ret, esp_err_to_name(ret));
55+
return false;
56+
}
57+
58+
ret = esp_zb_binary_output_cluster_add_attr(esp_zb_binary_output_cluster, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_APPLICATION_TYPE_ID, (void *)&application_type);
59+
if (ret != ESP_OK) {
60+
log_e("Failed to add application type attribute: 0x%x: %s", ret, esp_err_to_name(ret));
61+
return false;
62+
}
63+
64+
ret = esp_zb_cluster_list_add_binary_output_cluster(_cluster_list, esp_zb_binary_output_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
65+
if (ret != ESP_OK) {
66+
log_e("Failed to add Binary Output cluster: 0x%x: %s", ret, esp_err_to_name(ret));
67+
return false;
68+
}
69+
_binary_clusters |= BINARY_OUTPUT;
70+
return true;
71+
}
72+
4473
// Check Zigbee Cluster Specification 3.14.11.19.4 Binary Inputs (BI) Types for application type values
4574
bool ZigbeeBinary::setBinaryInputApplication(uint32_t application_type) {
4675
if (!(_binary_clusters & BINARY_INPUT)) {
@@ -61,6 +90,26 @@ bool ZigbeeBinary::setBinaryInputApplication(uint32_t application_type) {
6190
return true;
6291
}
6392

93+
// Check Zigbee Cluster Specification 3.14.11.19.5 Binary Outputs (BO) Types for application type values
94+
bool ZigbeeBinary::setBinaryOutputApplication(uint32_t application_type) {
95+
if (!(_binary_clusters & BINARY_OUTPUT)) {
96+
log_e("Binary Output cluster not added");
97+
return false;
98+
}
99+
100+
// Add the Binary Output group ID (0x04) to the application type
101+
uint32_t application_type_value = (0x04 << 24) | application_type;
102+
103+
esp_zb_attribute_list_t *binary_output_cluster =
104+
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
105+
esp_err_t ret = esp_zb_cluster_update_attr(binary_output_cluster, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_APPLICATION_TYPE_ID, (void *)&application_type_value);
106+
if (ret != ESP_OK) {
107+
log_e("Failed to set Binary Output application type: 0x%x: %s", ret, esp_err_to_name(ret));
108+
return false;
109+
}
110+
return true;
111+
}
112+
64113
bool ZigbeeBinary::setBinaryInput(bool input) {
65114
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
66115
if (!(_binary_clusters & BINARY_INPUT)) {
@@ -141,4 +190,107 @@ bool ZigbeeBinary::setBinaryInputDescription(const char *description) {
141190
return true;
142191
}
143192

193+
bool ZigbeeBinary::setBinaryOutputDescription(const char *description) {
194+
if (!(_binary_clusters & BINARY_OUTPUT)) {
195+
log_e("Binary Output cluster not added");
196+
return false;
197+
}
198+
199+
// Allocate a new array of size length + 2 (1 for the length, 1 for null terminator)
200+
char zb_description[ZB_MAX_NAME_LENGTH + 2];
201+
202+
// Convert description to ZCL string
203+
size_t description_length = strlen(description);
204+
if (description_length > ZB_MAX_NAME_LENGTH) {
205+
log_e("Description is too long");
206+
return false;
207+
}
208+
209+
// Get and check the binary output cluster
210+
esp_zb_attribute_list_t *binary_output_cluster =
211+
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
212+
if (binary_output_cluster == nullptr) {
213+
log_e("Failed to get binary output cluster");
214+
return false;
215+
}
216+
217+
// Store the length as the first element
218+
zb_description[0] = static_cast<char>(description_length); // Cast size_t to char
219+
// Use memcpy to copy the characters to the result array
220+
memcpy(zb_description + 1, description, description_length);
221+
// Null-terminate the array
222+
zb_description[description_length + 1] = '\0';
223+
224+
// Update the description attribute
225+
esp_err_t ret = esp_zb_cluster_update_attr(binary_output_cluster, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, (void *)zb_description);
226+
if (ret != ESP_OK) {
227+
log_e("Failed to set description: 0x%x: %s", ret, esp_err_to_name(ret));
228+
return false;
229+
}
230+
return true;
231+
}
232+
233+
//set attribute method -> method overridden in child class
234+
void ZigbeeBinary::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {
235+
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT) {
236+
if (message->attribute.id == ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
237+
_output_state = *(bool *)message->attribute.data.value;
238+
binaryOutputChanged();
239+
} else {
240+
log_w("Received message ignored. Attribute ID: %d not supported for Binary Output", message->attribute.id);
241+
}
242+
} else {
243+
log_w("Received message ignored. Cluster ID: %d not supported for Binary endpoint", message->info.cluster);
244+
}
245+
}
246+
247+
void ZigbeeBinary::binaryOutputChanged() {
248+
if (_on_binary_output_change) {
249+
_on_binary_output_change(_output_state);
250+
} else {
251+
log_w("No callback function set for binary output change");
252+
}
253+
}
254+
255+
bool ZigbeeBinary::setBinaryOutput(bool output) {
256+
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
257+
_output_state = output;
258+
binaryOutputChanged();
259+
260+
log_v("Updating binary output to %d", output);
261+
/* Update binary output */
262+
esp_zb_lock_acquire(portMAX_DELAY);
263+
ret = esp_zb_zcl_set_attribute_val(
264+
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, &_output_state, false
265+
);
266+
esp_zb_lock_release();
267+
268+
if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) {
269+
log_e("Failed to set binary output: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret));
270+
return false;
271+
}
272+
return true;
273+
}
274+
275+
bool ZigbeeBinary::reportBinaryOutput() {
276+
/* Send report attributes command */
277+
esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
278+
report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
279+
report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID;
280+
report_attr_cmd.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI;
281+
report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT;
282+
report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint;
283+
report_attr_cmd.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC;
284+
285+
esp_zb_lock_acquire(portMAX_DELAY);
286+
esp_err_t ret = esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
287+
esp_zb_lock_release();
288+
if (ret != ESP_OK) {
289+
log_e("Failed to send Binary Output report: 0x%x: %s", ret, esp_err_to_name(ret));
290+
return false;
291+
}
292+
log_v("Binary Output report sent");
293+
return true;
294+
}
295+
144296
#endif // CONFIG_ZB_ENABLED

libraries/Zigbee/src/ep/ZigbeeBinary.h

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,26 @@ enum zigbee_binary_clusters {
3838
#define BINARY_INPUT_APPLICATION_TYPE_SECURITY_HEAT_DETECTION 0x01000008 // Type 0x01, Index 0x0008
3939
#define BINARY_INPUT_APPLICATION_TYPE_SECURITY_OTHER 0x0100FFFF // Type 0x01, Index 0xFFFF
4040

41+
// HVAC application types for Binary Output (more can be found in Zigbee Cluster Specification 3.14.11.19.5 Binary Outputs (BO) Types)
42+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_BOILER 0x00000003 // Type 0x00, Index 0x0003
43+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_CHILLER 0x0000000D // Type 0x00, Index 0x000D
44+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_FAN 0x00000022 // Type 0x00, Index 0x0022
45+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HEATING_VALVE 0x0000002C // Type 0x00, Index 0x002C
46+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HUMIDIFIER 0x00000033 // Type 0x00, Index 0x0033
47+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_PREHEAT 0x00000034 // Type 0x00, Index 0x0034
48+
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_OTHER 0x0000FFFF // Type 0x00, Index 0xFFFF
49+
50+
// Security application types for Binary Output
51+
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ARM_DISARM_COMMAND 0x01000000 // Type 0x01, Index 0x0000
52+
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_OCCUPANCY_CONTROL 0x01000001 // Type 0x01, Index 0x0001
53+
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ENABLE_CONTROL 0x01000002 // Type 0x01, Index 0x0002
54+
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ACCESS_CONTROL 0x01000003 // Type 0x01, Index 0x0003
55+
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_OTHER 0x0100FFFF // Type 0x01, Index 0xFFFF
56+
4157
typedef struct zigbee_binary_cfg_s {
4258
esp_zb_basic_cluster_cfg_t basic_cfg;
4359
esp_zb_identify_cluster_cfg_t identify_cfg;
44-
// esp_zb_binary_output_cluster_cfg_t binary_output_cfg;
60+
esp_zb_binary_output_cluster_cfg_t binary_output_cfg;
4561
esp_zb_binary_input_cluster_cfg_t binary_input_cfg;
4662
} zigbee_binary_cfg_t;
4763

@@ -52,33 +68,41 @@ class ZigbeeBinary : public ZigbeeEP {
5268

5369
// Add binary cluster
5470
bool addBinaryInput();
55-
// bool addBinaryOutput();
71+
bool addBinaryOutput();
5672

5773
// Set the application type and description for the binary input
5874
bool setBinaryInputApplication(uint32_t application_type); // Check esp_zigbee_zcl_binary_input.h for application type values
5975
bool setBinaryInputDescription(const char *description);
6076

6177
// Set the application type and description for the binary output
62-
// bool setBinaryOutputApplication(uint32_t application_type); // Check esp_zigbee_zcl_binary_output.h for application type values
63-
// bool setBinaryOutputDescription(const char *description);
78+
bool setBinaryOutputApplication(uint32_t application_type); // Check esp_zigbee_zcl_binary_output.h for application type values
79+
bool setBinaryOutputDescription(const char *description);
6480

6581
// Use to set a cb function to be called on binary output change
66-
// void onBinaryOutputChange(void (*callback)(bool binary_output)) {
67-
// _on_binary_output_change = callback;
68-
// }
82+
void onBinaryOutputChange(void (*callback)(bool binary_output)) {
83+
_on_binary_output_change = callback;
84+
}
6985

70-
// Set the binary input value
86+
// Set the binary input/output value
7187
bool setBinaryInput(bool input);
88+
bool setBinaryOutput(bool output);
89+
90+
// Get the Binary Output value
91+
bool getBinaryOutput() {
92+
return _output_state;
93+
}
7294

73-
// Report Binary Input value
95+
// Report Binary Input/Output value
7496
bool reportBinaryInput();
97+
bool reportBinaryOutput();
7598

7699
private:
77-
// void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;
100+
void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;
78101

79-
// void (*_on_binary_output_change)(bool);
80-
// void binaryOutputChanged(bool binary_output);
102+
void (*_on_binary_output_change)(bool);
103+
void binaryOutputChanged();
81104

105+
bool _output_state;
82106
uint8_t _binary_clusters;
83107
};
84108

0 commit comments

Comments
 (0)