Skip to content

feat(zigbee): Add Binary Output support #11560

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

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
@@ -1,6 +1,6 @@
# Arduino-ESP32 Zigbee Binary Input Example
# Arduino-ESP32 Zigbee Binary Input Output Example

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.
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.

# Supported Targets

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

## Binary Input Functions

* The example implements two binary inputs:
- HVAC Fan Status: Reports the current state of a fan
- Security Zone Armed: Reports the armed state of a security zone
* 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.
## Binary Input/Output Functions

* The example implements three binary devices:
- **Binary Fan Device (Endpoint 1)**:
- Binary Input: HVAC Fan Status - Reports the current state of a fan
- Binary Output: HVAC Fan - Controls the fan switch with callback function
- **Binary Zone Device (Endpoint 2)**:
- Binary Input: Security Zone Armed - Reports the armed state of a security zone
- **Binary Humidifier Device (Endpoint 3)**:
- Binary Output: HVAC Humidifier - Controls the humidifier switch with callback function
* 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.
* Holding the button for more than 3 seconds will trigger a factory reset of the Zigbee device.

## Hardware Required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
// limitations under the License.

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

uint8_t binaryPin = A0;
uint8_t button = BOOT_PIN;

ZigbeeBinary zbBinaryFan = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER);
ZigbeeBinary zbBinaryZone = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER + 1);
ZigbeeBinary zbBinaryHumidifier = ZigbeeBinary(BINARY_DEVICE_ENDPOINT_NUMBER + 2);

bool binaryStatus = false;
bool zoneStatus = false;

void fanSwitch(bool state) {
Serial.println("Fan switch changed to: " + String(state));
if (state) {
zbBinaryFan.setBinaryInput(state);
zbBinaryFan.reportBinaryInput();
} else {
zbBinaryFan.setBinaryInput(state);
zbBinaryFan.reportBinaryInput();
}
}

void humidifierSwitch(bool state) {
Serial.println("Humidifier switch changed to: " + String(state));
}

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

// Set up binary fan status input (HVAC)
// Set up binary fan status input + switch output (HVAC)
zbBinaryFan.addBinaryInput();
zbBinaryFan.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_HVAC_FAN_STATUS);
zbBinaryFan.setBinaryInputDescription("Fan Status");

zbBinaryFan.addBinaryOutput();
zbBinaryFan.setBinaryOutputApplication(BINARY_OUTPUT_APPLICATION_TYPE_HVAC_FAN);
zbBinaryFan.setBinaryOutputDescription("Fan Switch");

zbBinaryFan.onBinaryOutputChange(fanSwitch);

// Set up binary zone armed input (Security)
zbBinaryZone.addBinaryInput();
zbBinaryZone.setBinaryInputApplication(BINARY_INPUT_APPLICATION_TYPE_SECURITY_ZONE_ARMED);
zbBinaryZone.setBinaryInputDescription("Zone Armed");

// Set up binary humidifier output (HVAC)
zbBinaryHumidifier.addBinaryOutput();
zbBinaryHumidifier.setBinaryOutputApplication(BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HUMIDIFIER);
zbBinaryHumidifier.setBinaryOutputDescription("Humidifier Switch");

zbBinaryHumidifier.onBinaryOutputChange(humidifierSwitch);

// Add endpoints to Zigbee Core
Zigbee.addEndpoint(&zbBinaryFan);
Zigbee.addEndpoint(&zbBinaryZone);
Zigbee.addEndpoint(&zbBinaryHumidifier);

Serial.println("Starting Zigbee...");
// When all EPs are registered, start Zigbee in End Device mode
Expand Down Expand Up @@ -101,12 +130,19 @@ void loop() {
Zigbee.factoryReset();
}
}
// Toggle binary input
binaryStatus = !binaryStatus;
zbBinaryFan.setBinaryInput(binaryStatus);
zbBinaryZone.setBinaryInput(binaryStatus);
zbBinaryFan.reportBinaryInput();

// Toggle fan
zbBinaryFan.setBinaryOutput(!zbBinaryFan.getBinaryOutput());
zbBinaryFan.reportBinaryOutput();

// Toggle zone
zoneStatus = !zoneStatus;
zbBinaryZone.setBinaryInput(zoneStatus);
zbBinaryZone.reportBinaryInput();

// Toggle humidifier
zbBinaryHumidifier.setBinaryOutput(!zbBinaryHumidifier.getBinaryOutput());
zbBinaryHumidifier.reportBinaryOutput();
}
delay(100);
}
152 changes: 152 additions & 0 deletions libraries/Zigbee/src/ep/ZigbeeBinary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ bool ZigbeeBinary::addBinaryInput() {
return true;
}

bool ZigbeeBinary::addBinaryOutput() {
esp_zb_attribute_list_t *esp_zb_binary_output_cluster = esp_zb_binary_output_cluster_create(NULL);

// Create default description for Binary Output
char default_description[] = "\x0D"
"Binary Output";
uint32_t application_type = 0x00000000 | (0x04 << 24); // Group ID 0x04

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);
if (ret != ESP_OK) {
log_e("Failed to add description attribute: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}

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);
if (ret != ESP_OK) {
log_e("Failed to add application type attribute: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}

ret = esp_zb_cluster_list_add_binary_output_cluster(_cluster_list, esp_zb_binary_output_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
if (ret != ESP_OK) {
log_e("Failed to add Binary Output cluster: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
_binary_clusters |= BINARY_OUTPUT;
return true;
}

// Check Zigbee Cluster Specification 3.14.11.19.4 Binary Inputs (BI) Types for application type values
bool ZigbeeBinary::setBinaryInputApplication(uint32_t application_type) {
if (!(_binary_clusters & BINARY_INPUT)) {
Expand All @@ -61,6 +90,26 @@ bool ZigbeeBinary::setBinaryInputApplication(uint32_t application_type) {
return true;
}

// Check Zigbee Cluster Specification 3.14.11.19.5 Binary Outputs (BO) Types for application type values
bool ZigbeeBinary::setBinaryOutputApplication(uint32_t application_type) {
if (!(_binary_clusters & BINARY_OUTPUT)) {
log_e("Binary Output cluster not added");
return false;
}

// Add the Binary Output group ID (0x04) to the application type
uint32_t application_type_value = (0x04 << 24) | application_type;

esp_zb_attribute_list_t *binary_output_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
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);
if (ret != ESP_OK) {
log_e("Failed to set Binary Output application type: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}

bool ZigbeeBinary::setBinaryInput(bool input) {
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
if (!(_binary_clusters & BINARY_INPUT)) {
Expand Down Expand Up @@ -141,4 +190,107 @@ bool ZigbeeBinary::setBinaryInputDescription(const char *description) {
return true;
}

bool ZigbeeBinary::setBinaryOutputDescription(const char *description) {
if (!(_binary_clusters & BINARY_OUTPUT)) {
log_e("Binary Output cluster not added");
return false;
}

// Allocate a new array of size length + 2 (1 for the length, 1 for null terminator)
char zb_description[ZB_MAX_NAME_LENGTH + 2];

// Convert description to ZCL string
size_t description_length = strlen(description);
if (description_length > ZB_MAX_NAME_LENGTH) {
log_e("Description is too long");
return false;
}

// Get and check the binary output cluster
esp_zb_attribute_list_t *binary_output_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
if (binary_output_cluster == nullptr) {
log_e("Failed to get binary output cluster");
return false;
}

// Store the length as the first element
zb_description[0] = static_cast<char>(description_length); // Cast size_t to char
// Use memcpy to copy the characters to the result array
memcpy(zb_description + 1, description, description_length);
// Null-terminate the array
zb_description[description_length + 1] = '\0';

// Update the description attribute
esp_err_t ret = esp_zb_cluster_update_attr(binary_output_cluster, ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, (void *)zb_description);
if (ret != ESP_OK) {
log_e("Failed to set description: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}

//set attribute method -> method overridden in child class
void ZigbeeBinary::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT) {
if (message->attribute.id == ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
_output_state = *(bool *)message->attribute.data.value;
binaryOutputChanged();
} else {
log_w("Received message ignored. Attribute ID: %d not supported for Binary Output", message->attribute.id);
}
} else {
log_w("Received message ignored. Cluster ID: %d not supported for Binary endpoint", message->info.cluster);
}
}

void ZigbeeBinary::binaryOutputChanged() {
if (_on_binary_output_change) {
_on_binary_output_change(_output_state);
} else {
log_w("No callback function set for binary output change");
}
}

bool ZigbeeBinary::setBinaryOutput(bool output) {
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
_output_state = output;
binaryOutputChanged();

log_v("Updating binary output to %d", output);
/* Update binary output */
esp_zb_lock_acquire(portMAX_DELAY);
ret = esp_zb_zcl_set_attribute_val(
_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
);
esp_zb_lock_release();

if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) {
log_e("Failed to set binary output: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret));
return false;
}
return true;
}

bool ZigbeeBinary::reportBinaryOutput() {
/* Send report attributes command */
esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID;
report_attr_cmd.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI;
report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT;
report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint;
report_attr_cmd.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC;

esp_zb_lock_acquire(portMAX_DELAY);
esp_err_t ret = esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
esp_zb_lock_release();
if (ret != ESP_OK) {
log_e("Failed to send Binary Output report: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
log_v("Binary Output report sent");
return true;
}

#endif // CONFIG_ZB_ENABLED
48 changes: 36 additions & 12 deletions libraries/Zigbee/src/ep/ZigbeeBinary.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,26 @@ enum zigbee_binary_clusters {
#define BINARY_INPUT_APPLICATION_TYPE_SECURITY_HEAT_DETECTION 0x01000008 // Type 0x01, Index 0x0008
#define BINARY_INPUT_APPLICATION_TYPE_SECURITY_OTHER 0x0100FFFF // Type 0x01, Index 0xFFFF

// HVAC application types for Binary Output (more can be found in Zigbee Cluster Specification 3.14.11.19.5 Binary Outputs (BO) Types)
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_BOILER 0x00000003 // Type 0x00, Index 0x0003
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_CHILLER 0x0000000D // Type 0x00, Index 0x000D
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_FAN 0x00000022 // Type 0x00, Index 0x0022
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HEATING_VALVE 0x0000002C // Type 0x00, Index 0x002C
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_HUMIDIFIER 0x00000033 // Type 0x00, Index 0x0033
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_PREHEAT 0x00000034 // Type 0x00, Index 0x0034
#define BINARY_OUTPUT_APPLICATION_TYPE_HVAC_OTHER 0x0000FFFF // Type 0x00, Index 0xFFFF

// Security application types for Binary Output
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ARM_DISARM_COMMAND 0x01000000 // Type 0x01, Index 0x0000
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_OCCUPANCY_CONTROL 0x01000001 // Type 0x01, Index 0x0001
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ENABLE_CONTROL 0x01000002 // Type 0x01, Index 0x0002
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_ACCESS_CONTROL 0x01000003 // Type 0x01, Index 0x0003
#define BINARY_OUTPUT_APPLICATION_TYPE_SECURITY_OTHER 0x0100FFFF // Type 0x01, Index 0xFFFF

typedef struct zigbee_binary_cfg_s {
esp_zb_basic_cluster_cfg_t basic_cfg;
esp_zb_identify_cluster_cfg_t identify_cfg;
// esp_zb_binary_output_cluster_cfg_t binary_output_cfg;
esp_zb_binary_output_cluster_cfg_t binary_output_cfg;
esp_zb_binary_input_cluster_cfg_t binary_input_cfg;
} zigbee_binary_cfg_t;

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

// Add binary cluster
bool addBinaryInput();
// bool addBinaryOutput();
bool addBinaryOutput();

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

// Set the application type and description for the binary output
// bool setBinaryOutputApplication(uint32_t application_type); // Check esp_zigbee_zcl_binary_output.h for application type values
// bool setBinaryOutputDescription(const char *description);
bool setBinaryOutputApplication(uint32_t application_type); // Check esp_zigbee_zcl_binary_output.h for application type values
bool setBinaryOutputDescription(const char *description);

// Use to set a cb function to be called on binary output change
// void onBinaryOutputChange(void (*callback)(bool binary_output)) {
// _on_binary_output_change = callback;
// }
void onBinaryOutputChange(void (*callback)(bool binary_output)) {
_on_binary_output_change = callback;
}

// Set the binary input value
// Set the binary input/output value
bool setBinaryInput(bool input);
bool setBinaryOutput(bool output);

// Get the Binary Output value
bool getBinaryOutput() {
return _output_state;
}

// Report Binary Input value
// Report Binary Input/Output value
bool reportBinaryInput();
bool reportBinaryOutput();

private:
// void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;
void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;

// void (*_on_binary_output_change)(bool);
// void binaryOutputChanged(bool binary_output);
void (*_on_binary_output_change)(bool);
void binaryOutputChanged();

bool _output_state;
uint8_t _binary_clusters;
};

Expand Down
Loading