feat(zigbee): Remove static variables, improve binding, new example (#11316)

* feat(zigbee): Remove static variables, improve binding, new example

* feat(example): Add missing header

* 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-05-27 12:13:47 +02:00 committed by GitHub
parent 9090b46da5
commit 542274d5ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 863 additions and 103 deletions

View file

@ -0,0 +1,110 @@
# Arduino-ESP32 Zigbee Multi-Switch Example
This example demonstrates how to configure a Zigbee device as a multi-switch controller that can control up to three different Zigbee lights independently. The switch can operate in either coordinator or router mode, making it compatible with Home Assistant integration.
# Supported Targets
Currently, this example supports the following targets.
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
## Hardware Required
* One development board (ESP32-H2 or ESP32-C6) acting as Zigbee multi-switch controller
* One or more Zigbee light devices (loaded with Zigbee_On_Off_Light example)
* A USB cable for power supply and programming
### Configure the Project
The example uses the BOOT button (pin 9) on ESP32-C6 and ESP32-H2 as the physical switch input. The switch can be configured to operate in two modes:
1. **Coordinator Mode**: For running your own Zigbee network
2. **Router Mode**: For Home Assistant integration
#### Using Arduino IDE
To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits).
* Before Compile/Verify, select the correct board: `Tools -> Board`
* Select the Zigbee mode: `Tools -> Zigbee mode: Zigbee ZCZR (coordinator/router)`
* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs`
* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port
* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`
## Features
The multi-switch example provides the following functionality:
1. **Light Configuration**
- Configure up to 3 different lights using their endpoint and IEEE address
- Configuration is stored in NVS (Non-Volatile Storage) and persists after power loss
- Remove configured lights when needed
2. **Control Commands**
- Control all bound lights simultaneously:
- Turn all bound lights ON
- Turn all bound lights OFF
- Toggle all bound lights
- Control individual lights (1-3):
- Turn light ON
- Turn light OFF
- Toggle light
3. **Network Management**
- Factory reset capability
- Open network for device joining
- View bound devices and current light configurations
## Serial Commands
The example accepts the following commands through the serial interface:
* `config` - Configure a new light (requires light number, endpoint, and IEEE address)
* `remove` - Remove a configured light
* `on` - Turn all bound lights ON
* `off` - Turn all bound lights OFF
* `toggle` - Toggle all bound lights
* `1on`, `2on`, `3on` - Turn individual light ON
* `1off`, `2off`, `3off` - Turn individual light OFF
* `1toggle`, `2toggle`, `3toggle` - Toggle individual light
* `freset` - Perform factory reset
* `open_network` - Open network for device joining (only for coordinator role)
## Troubleshooting
If the End device flashed with the example `Zigbee_On_Off_Light` is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. It is recommended to do this if you re-flash the coordinator.
You can do the following:
* In the Arduino IDE go to the Tools menu and set `Erase All Flash Before Sketch Upload` to `Enabled`
* In the `Zigbee_On_Off_Light` example sketch call `Zigbee.factoryReset()`
By default, the coordinator network is closed after rebooting or flashing new firmware.
To open the network you have 2 options:
* Open network after reboot by setting `Zigbee.setRebootOpenNetwork(time)` before calling `Zigbee.begin()`
* In application you can anytime call `Zigbee.openNetwork(time)` to open the network for devices to join
***Important: Make sure you are using a good quality USB cable and that you have a reliable power source***
* **LED not blinking:** Check the wiring connection and the IO selection
* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed
* **COM port not detected:** Check the USB cable and the USB to Serial driver installation
If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute).
## Contribute
To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst)
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!
Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else.
## Resources
* Official ESP32 Forum: [Link](https://esp32.com)
* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf)
* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf)
* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com)

View file

@ -0,0 +1,274 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @brief This example demonstrates simple Zigbee multi-light switch.
*
* The example demonstrates how to use Zigbee library to control multiple light bulbs.
* The light bulbs are Zigbee devices, which are controlled by a Zigbee coordinator/router (Multi-Switch).
* Settings are stored in NVS to not be lost after power loss.
* Configuring and controlling the lights is done via serial input.
*
* Proper Zigbee mode must be selected in Tools->Zigbee mode
* and also the correct partition scheme must be selected in Tools->Partition Scheme.
*
* Please check the README.md for instructions and more detailed description.
*
* Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
*/
#ifndef ZIGBEE_MODE_ZCZR
#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
#include <Preferences.h>
#define ZIGBEE_ROLE ZIGBEE_ROUTER // ZIGBEE_ROUTER for HomeAssistant integration, ZIGBEE_COORDINATOR for running own network
/* Zigbee switch configuration */
#define SWITCH_ENDPOINT_NUMBER 1
uint8_t button = BOOT_PIN;
ZigbeeSwitch zbSwitch = ZigbeeSwitch(SWITCH_ENDPOINT_NUMBER);
int buttonState;
int lastButtonState = LOW;
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
// To be stored in NVS to not be lost after power loss
Preferences prefs;
zb_device_params_t light_1;
zb_device_params_t light_2;
zb_device_params_t light_3;
void storeLightParams(zb_device_params_t *light, int light_number) {
char key[10];
snprintf(key, sizeof(key), "light_%d", light_number);
prefs.putBytes(key, light, sizeof(zb_device_params_t));
}
void loadLightParams(zb_device_params_t *light, int light_number) {
char key[10];
snprintf(key, sizeof(key), "light_%d", light_number);
prefs.getBytes(key, light, sizeof(zb_device_params_t));
}
/********************* Arduino functions **************************/
void setup() {
Serial.begin(115200);
// Initialize Preferences
prefs.begin("lights", false); // false means read/write mode
// Load saved light parameters
loadLightParams(&light_1, 1);
loadLightParams(&light_2, 2);
loadLightParams(&light_3, 3);
// Init button switch
pinMode(button, INPUT_PULLUP);
// Set Zigbee device name and model
zbSwitch.setManufacturerAndModel("Espressif", "ZBMultiSwitch");
// Set binding settings depending on the role
if (ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
zbSwitch.allowMultipleBinding(true); // To allow binding multiple lights to the switch
} else {
zbSwitch.setManualBinding(true); //Set manual binding to true, so binding is done on Home Assistant side
}
// Add endpoint to Zigbee Core
Serial.println("Adding ZigbeeSwitch endpoint to Zigbee Core");
Zigbee.addEndpoint(&zbSwitch);
// When all EPs are registered, start Zigbee with given role
if (!Zigbee.begin(ZIGBEE_ROLE)) {
Serial.println("Zigbee failed to start!");
Serial.println("Rebooting...");
ESP.restart();
}
Serial.println("Connecting to network");
while (!Zigbee.connected()) {
Serial.print(".");
delay(100);
}
Serial.println();
}
void loop() {
// Handle button switch in loop()
if (digitalRead(button) == LOW) { // Push button pressed
// Key debounce handling
while (digitalRead(button) == LOW) {
delay(50);
}
// Print bound devices
Serial.println("Bound devices:");
zbSwitch.printBoundDevices(Serial);
Serial.println("Lights configured:");
Serial.printf("Light 1: %d %s\n", light_1.endpoint, Zigbee.formatIEEEAddress(light_1.ieee_addr));
Serial.printf("Light 2: %d %s\n", light_2.endpoint, Zigbee.formatIEEEAddress(light_2.ieee_addr));
Serial.printf("Light 3: %d %s\n", light_3.endpoint, Zigbee.formatIEEEAddress(light_3.ieee_addr));
}
// Handle serial input to configure and control the lights
if (Serial.available()) {
String command = Serial.readString();
Serial.println("Command: " + command);
if (command == "config") {
//wait for light number, endpoint and ieee address
Serial.println("Enter light number (1-3):");
while (!Serial.available()) {
delay(100);
}
int light_number = Serial.parseInt();
Serial.println("Enter endpoint:");
while (!Serial.available()) {
delay(100);
}
int endpoint = Serial.parseInt();
Serial.println("Enter ieee address:");
while (!Serial.available()) {
delay(100);
}
String ieee_address = Serial.readStringUntil('\n');
ieee_address.trim();
//convert ieee address to uint8_t array (format in string is 00:00:00:00:00:00:00:00)
uint8_t ieee_address_array[8];
int index = 0;
bool valid = true;
// Check if the string has the correct format (8 hex pairs with colons)
if (ieee_address.length() != 23) { // 8 pairs * 2 + 7 colons
Serial.println("Invalid IEEE address format. Expected format: 00:00:00:00:00:00:00:00");
valid = false;
} else {
for (int i = 0; i < ieee_address.length() && index < 8 && valid; i += 3) {
// Check for colon at expected positions
if (i > 0 && ieee_address.charAt(i - 1) != ':') {
valid = false;
break;
}
// Convert two hex characters to a byte
char hex[3] = {ieee_address.charAt(i), ieee_address.charAt(i + 1), '\0'};
char *endptr;
long value = strtol(hex, &endptr, 16);
if (*endptr != '\0' || value < 0 || value > 255) {
valid = false;
break;
}
// Store bytes in reverse order to match Zigbee standard
ieee_address_array[7 - index++] = (uint8_t)value;
}
}
if (!valid || index != 8) {
Serial.println("Invalid IEEE address. Please enter a valid address in format: 00:00:00:00:00:00:00:00");
return;
}
//set the light parameters
if (light_number == 1) {
light_1.endpoint = endpoint;
memcpy(light_1.ieee_addr, ieee_address_array, 8);
storeLightParams(&light_1, 1);
} else if (light_number == 2) {
light_2.endpoint = endpoint;
memcpy(light_2.ieee_addr, ieee_address_array, 8);
storeLightParams(&light_2, 2);
} else if (light_number == 3) {
light_3.endpoint = endpoint;
memcpy(light_3.ieee_addr, ieee_address_array, 8);
storeLightParams(&light_3, 3);
}
Serial.printf("Light %d configured\n", light_number);
} else if (command == "remove") {
//wait for light number
Serial.println("Enter light number (1-3):");
while (!Serial.available()) {
delay(100);
}
int light_number = Serial.parseInt();
uint8_t ieee_address_empty[8] = {0, 0, 0, 0, 0, 0, 0, 0};
if (light_number == 1) {
light_1.endpoint = 0;
memcpy(light_1.ieee_addr, ieee_address_empty, 8);
storeLightParams(&light_1, 1);
} else if (light_number == 2) {
light_2.endpoint = 0;
memcpy(light_2.ieee_addr, ieee_address_empty, 8);
storeLightParams(&light_2, 2);
} else if (light_number == 3) {
light_3.endpoint = 0;
memcpy(light_3.ieee_addr, ieee_address_empty, 8);
storeLightParams(&light_3, 3);
}
Serial.printf("Light %d removed\n", light_number);
} else if (command == "on") {
Serial.println(" --> SIG Input : All Lights ON");
zbSwitch.lightOn();
} else if (command == "off") {
Serial.println(" --> SIG Input : All Lights OFF");
zbSwitch.lightOff();
} else if (command == "toggle") {
Serial.println(" --> SIG Input : All Lights Toggle");
zbSwitch.lightToggle();
} else if (command == "1on") {
Serial.println(" --> SIG Input : Light 1 ON");
zbSwitch.lightOn(light_1.endpoint, light_1.ieee_addr);
} else if (command == "1off") {
Serial.println(" --> SIG Input : Light 1 OFF");
zbSwitch.lightOff(light_1.endpoint, light_1.ieee_addr);
} else if (command == "1toggle") {
Serial.println(" --> SIG Input : Light 1 Toggle");
zbSwitch.lightToggle(light_1.endpoint, light_1.ieee_addr);
} else if (command == "2on") {
Serial.println(" --> SIG Input : Light 2 ON");
zbSwitch.lightOn(light_2.endpoint, light_2.ieee_addr);
} else if (command == "2off") {
Serial.println(" --> SIG Input : Light 2 OFF");
zbSwitch.lightOff(light_2.endpoint, light_2.ieee_addr);
} else if (command == "2toggle") {
Serial.println(" --> SIG Input : Light 2 Toggle");
zbSwitch.lightToggle(light_2.endpoint, light_2.ieee_addr);
} else if (command == "3on") {
Serial.println(" --> SIG Input : Light 3 ON");
zbSwitch.lightOn(light_3.endpoint, light_3.ieee_addr);
} else if (command == "3off") {
Serial.println(" --> SIG Input : Light 3 OFF");
zbSwitch.lightOff(light_3.endpoint, light_3.ieee_addr);
} else if (command == "3toggle") {
Serial.println(" --> SIG Input : Light 3 Toggle");
zbSwitch.lightToggle(light_3.endpoint, light_3.ieee_addr);
} else if (command == "freset") {
Serial.println(" --> SIG Input : Factory Reset!");
delay(1500);
Zigbee.factoryReset();
} else if (command == "open_network") {
Serial.println(" --> SIG Input : Open Network");
if (ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
Zigbee.openNetwork(180);
} else {
Serial.println("Open network is only available for coordinator role");
}
} else {
Serial.println("Unknown command");
}
}
}

View file

@ -0,0 +1,6 @@
{
"fqbn_append": "PartitionScheme=zigbee_zczr,ZigbeeMode=zczr",
"requires": [
"CONFIG_SOC_IEEE802154_SUPPORTED=y"
]
}

View file

@ -5,6 +5,7 @@
#include "ZigbeeHandlers.cpp"
#include "Arduino.h"
#include <set>
#ifdef __cplusplus
extern "C" {
@ -30,6 +31,7 @@ ZigbeeCore::ZigbeeCore() {
_connected = false;
_scan_duration = 3; // default scan duration
_rx_on_when_idle = true;
_debug = false;
if (!lock) {
lock = xSemaphoreCreateBinary();
if (lock == NULL) {
@ -40,6 +42,7 @@ ZigbeeCore::ZigbeeCore() {
//forward declaration
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message);
bool zb_apsde_data_indication_handler(esp_zb_apsde_data_ind_t ind);
bool ZigbeeCore::begin(esp_zb_cfg_t *role_cfg, bool erase_nvs) {
if (!zigbeeInit(role_cfg, erase_nvs)) {
@ -173,6 +176,9 @@ bool ZigbeeCore::zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs) {
return false;
}
// Register APSDATA INDICATION handler to catch bind/unbind requests
esp_zb_aps_data_indication_handler_register(zb_apsde_data_indication_handler);
//Erase NVRAM before creating connection to new Coordinator
if (erase_nvs) {
esp_zb_nvram_erase_at_start(true);
@ -223,6 +229,13 @@ void ZigbeeCore::openNetwork(uint8_t time) {
}
}
void ZigbeeCore::closeNetwork() {
if (started()) {
log_v("Closing network");
esp_zb_bdb_close_network();
}
}
static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) {
ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask));
}
@ -234,6 +247,8 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p;
//coordinator variables
esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL;
//router variables
esp_zb_zdo_signal_device_update_params_t *dev_update_params = NULL;
//main switch
switch (sig_type) {
@ -267,7 +282,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
} else {
// Save the channel mask to NVRAM in case of reboot which may be on a different channel after a change in the network
Zigbee.setNVRAMChannelMask(1 << esp_zb_get_current_channel());
Zigbee._connected = true;
Zigbee._connected = true; // Coordinator is always connected
}
Zigbee.searchBindings();
}
@ -287,6 +302,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1],
extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()
);
Zigbee._connected = true;
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else {
log_i("Restart network formation (status: %s)", esp_err_to_name(err_status));
@ -340,20 +356,60 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
*/
// for each endpoint in the list call the findEndpoint function if not bounded or allowed to bind multiple devices
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (!(*it)->bound() || (*it)->epAllowMultipleBinding()) {
// Check if the device is already bound
bool found = false;
// Get the list of devices bound to the EP
std::list<zb_device_params_t *> bound_devices = (*it)->getBoundDevices();
for (std::list<zb_device_params_t *>::iterator device = bound_devices.begin(); device != bound_devices.end(); ++device) {
if (((*device)->short_addr == dev_annce_params->device_short_addr) || (memcmp((*device)->ieee_addr, dev_annce_params->ieee_addr, 8) == 0)) {
found = true;
log_d("Device already bound to endpoint %d", (*it)->getEndpoint());
break;
log_d("Checking endpoint %d", (*it)->getEndpoint());
if (!(*it)->epUseManualBinding()) {
if (!(*it)->bound() || (*it)->epAllowMultipleBinding()) {
// Check if the device is already bound
bool found = false;
// Get the list of devices bound to the EP
std::list<zb_device_params_t *> bound_devices = (*it)->getBoundDevices();
for (std::list<zb_device_params_t *>::iterator device = bound_devices.begin(); device != bound_devices.end(); ++device) {
if (((*device)->short_addr == dev_annce_params->device_short_addr) || (memcmp((*device)->ieee_addr, dev_annce_params->ieee_addr, 8) == 0)) {
found = true;
log_d("Device already bound to endpoint %d", (*it)->getEndpoint());
break;
}
}
if (!found) {
log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint());
(*it)->findEndpoint(&cmd_req);
log_d("Endpoint %d is searching for device", (*it)->getEndpoint());
break; // Only one endpoint per device
}
}
if (!found) {
(*it)->findEndpoint(&cmd_req);
}
}
}
break;
case ESP_ZB_ZDO_SIGNAL_DEVICE_UPDATE: // Router
if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_ROUTER) {
dev_update_params = (esp_zb_zdo_signal_device_update_params_t *)esp_zb_app_signal_get_params(p_sg_p);
log_i("New device commissioned or rejoined (short: 0x%04hx)", dev_update_params->short_addr);
esp_zb_zdo_match_desc_req_param_t cmd_req;
cmd_req.dst_nwk_addr = dev_update_params->short_addr;
cmd_req.addr_of_interest = dev_update_params->short_addr;
// for each endpoint in the list call the findEndpoint function if not bounded or allowed to bind multiple devices
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
log_d("Checking endpoint %d", (*it)->getEndpoint());
if (!(*it)->epUseManualBinding()) {
if (!(*it)->bound() || (*it)->epAllowMultipleBinding()) {
// Check if the device is already bound
bool found = false;
// Get the list of devices bound to the EP
std::list<zb_device_params_t *> bound_devices = (*it)->getBoundDevices();
for (std::list<zb_device_params_t *>::iterator device = bound_devices.begin(); device != bound_devices.end(); ++device) {
if (((*device)->short_addr == dev_update_params->short_addr) || (memcmp((*device)->ieee_addr, dev_update_params->long_addr, 8) == 0)) {
found = true;
log_d("Device already bound to endpoint %d", (*it)->getEndpoint());
break;
}
}
log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint());
if (!found) {
(*it)->findEndpoint(&cmd_req);
log_d("Endpoint %d is searching for device", (*it)->getEndpoint());
break; // Only one endpoint per device
}
}
}
}
@ -380,6 +436,30 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
}
}
// APS DATA INDICATION HANDLER to catch bind/unbind requests
bool zb_apsde_data_indication_handler(esp_zb_apsde_data_ind_t ind) {
if (Zigbee.getDebugMode()) {
log_d("APSDE INDICATION - Received APSDE-DATA indication, status: %d", ind.status);
log_d(
"APSDE INDICATION - dst_endpoint: %d, src_endpoint: %d, dst_addr_mode: %d, src_addr_mode: %d, cluster_id: 0x%04x, asdu_length: %d", ind.dst_endpoint,
ind.src_endpoint, ind.dst_addr_mode, ind.src_addr_mode, ind.cluster_id, ind.asdu_length
);
log_d(
"APSDE INDICATION - dst_short_addr: 0x%04x, src_short_addr: 0x%04x, profile_id: 0x%04x, security_status: %d, lqi: %d, rx_time: %d", ind.dst_short_addr,
ind.src_short_addr, ind.profile_id, ind.security_status, ind.lqi, ind.rx_time
);
}
if (ind.status == 0x00) {
// Catch bind/unbind requests to update the bound devices list
if (ind.cluster_id == 0x21 || ind.cluster_id == 0x22) {
Zigbee.searchBindings();
}
} else {
log_e("APSDE INDICATION - Invalid status of APSDE-DATA indication, error code: %d", ind.status);
}
return false; //False to let the stack process the message as usual
}
void ZigbeeCore::factoryReset(bool restart) {
if (restart) {
log_v("Factory resetting Zigbee stack, device will reboot");
@ -444,63 +524,194 @@ void ZigbeeCore::scanDelete() {
_scan_status = ZB_SCAN_FAILED;
}
// Recall bounded devices from the binding table after reboot
// Recall bounded devices from the binding table after reboot or when requested
void ZigbeeCore::bindingTableCb(const esp_zb_zdo_binding_table_info_t *table_info, void *user_ctx) {
bool done = true;
esp_zb_zdo_mgmt_bind_param_t *req = (esp_zb_zdo_mgmt_bind_param_t *)user_ctx;
esp_zb_zdp_status_t zdo_status = (esp_zb_zdp_status_t)table_info->status;
log_d("Binding table callback for address 0x%04x with status %d", req->dst_addr, zdo_status);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
// Print binding table log simple
log_d("Binding table info: total %d, index %d, count %d", table_info->total, table_info->index, table_info->count);
if (table_info->total == 0) {
log_d("No binding table entries found");
// Clear all bound devices since there are no entries
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
log_d("Clearing bound devices for EP %d", (*it)->getEndpoint());
(*it)->clearBoundDevices();
}
free(req);
return;
}
// Create a set to track found devices using both short and IEEE addresses
struct DeviceIdentifier {
uint8_t endpoint;
uint16_t short_addr;
esp_zb_ieee_addr_t ieee_addr;
bool is_ieee;
bool operator<(const DeviceIdentifier &other) const {
if (endpoint != other.endpoint) {
return endpoint < other.endpoint;
}
if (is_ieee != other.is_ieee) {
return is_ieee < other.is_ieee;
}
if (is_ieee) {
return memcmp(ieee_addr, other.ieee_addr, sizeof(esp_zb_ieee_addr_t)) < 0;
}
return short_addr < other.short_addr;
}
};
static std::set<DeviceIdentifier> found_devices;
static std::vector<esp_zb_zdo_binding_table_record_t> all_records;
// If this is the first chunk (index 0), clear the previous data
if (table_info->index == 0) {
found_devices.clear();
all_records.clear();
}
// Add current records to our collection
esp_zb_zdo_binding_table_record_t *record = table_info->record;
for (int i = 0; i < table_info->count; i++) {
log_d(
"Binding table record: src_endp %d, dst_endp %d, cluster_id 0x%04x, dst_addr_mode %d", record->src_endp, record->dst_endp, record->cluster_id,
"Processing record %d: src_endp %d, dst_endp %d, cluster_id 0x%04x, dst_addr_mode %d", i, record->src_endp, record->dst_endp, record->cluster_id,
record->dst_addr_mode
);
zb_device_params_t *device = (zb_device_params_t *)calloc(1, sizeof(zb_device_params_t));
device->endpoint = record->dst_endp;
if (record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT || record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT) {
device->short_addr = record->dst_address.addr_short;
} else { //ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT
memcpy(device->ieee_addr, record->dst_address.addr_long, sizeof(esp_zb_ieee_addr_t));
}
// Add to list of bound devices of proper endpoint
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if ((*it)->getEndpoint() == record->src_endp) {
(*it)->addBoundDevice(device);
log_d(
"Device bound to EP %d -> device endpoint: %d, short addr: 0x%04x, ieee addr: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", record->src_endp,
device->endpoint, device->short_addr, device->ieee_addr[7], device->ieee_addr[6], device->ieee_addr[5], device->ieee_addr[4], device->ieee_addr[3],
device->ieee_addr[2], device->ieee_addr[1], device->ieee_addr[0]
);
}
}
all_records.push_back(*record);
record = record->next;
}
// Continue reading the binding table
// If this is not the last chunk, request the next one
if (table_info->index + table_info->count < table_info->total) {
/* There are unreported binding table entries, request for them. */
log_d("Requesting next chunk of binding table (current index: %d, count: %d, total: %d)", table_info->index, table_info->count, table_info->total);
req->start_index = table_info->index + table_info->count;
esp_zb_zdo_binding_table_req(req, bindingTableCb, req);
done = false;
}
}
} else {
// This is the last chunk, process all records
log_d("Processing final chunk of binding table, total records: %d", all_records.size());
for (const auto &record : all_records) {
if (done) {
// Print bound devices
log_d("Filling bounded devices finished");
DeviceIdentifier dev_id;
dev_id.endpoint = record.src_endp;
dev_id.is_ieee = (record.dst_addr_mode == ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT);
if (dev_id.is_ieee) {
memcpy(dev_id.ieee_addr, record.dst_address.addr_long, sizeof(esp_zb_ieee_addr_t));
dev_id.short_addr = 0xFFFF; // Invalid short address
} else {
dev_id.short_addr = record.dst_address.addr_short;
memset(dev_id.ieee_addr, 0, sizeof(esp_zb_ieee_addr_t));
}
// Track this device as found
found_devices.insert(dev_id);
}
// Now process each endpoint and update its bound devices
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
log_d("Processing endpoint %d", (*it)->getEndpoint());
std::list<zb_device_params_t *> bound_devices = (*it)->getBoundDevices();
std::list<zb_device_params_t *> devices_to_remove;
// First, identify devices that need to be removed
for (std::list<zb_device_params_t *>::iterator dev_it = bound_devices.begin(); dev_it != bound_devices.end(); ++dev_it) {
DeviceIdentifier dev_id;
dev_id.endpoint = (*it)->getEndpoint();
// Create both short and IEEE address identifiers for the device
bool found = false;
// Check if device exists with short address
if ((*dev_it)->short_addr != 0xFFFF) {
dev_id.is_ieee = false;
dev_id.short_addr = (*dev_it)->short_addr;
memset(dev_id.ieee_addr, 0, sizeof(esp_zb_ieee_addr_t));
if (found_devices.find(dev_id) != found_devices.end()) {
found = true;
}
}
// Check if device exists with IEEE address
if (!found) {
dev_id.is_ieee = true;
memcpy(dev_id.ieee_addr, (*dev_it)->ieee_addr, sizeof(esp_zb_ieee_addr_t));
dev_id.short_addr = 0xFFFF;
if (found_devices.find(dev_id) != found_devices.end()) {
found = true;
}
}
if (!found) {
devices_to_remove.push_back(*dev_it);
}
}
// Remove devices that are no longer in the binding table
for (std::list<zb_device_params_t *>::iterator dev_it = devices_to_remove.begin(); dev_it != devices_to_remove.end(); ++dev_it) {
(*it)->removeBoundDevice(*dev_it);
free(*dev_it);
}
// Now add new devices from the binding table
for (const auto &record : all_records) {
if (record.src_endp == (*it)->getEndpoint()) {
log_d("Processing binding record for EP %d", record.src_endp);
zb_device_params_t *device = (zb_device_params_t *)calloc(1, sizeof(zb_device_params_t));
if (!device) {
log_e("Failed to allocate memory for device params");
continue;
}
device->endpoint = record.dst_endp;
bool is_ieee = (record.dst_addr_mode == ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT);
if (is_ieee) {
memcpy(device->ieee_addr, record.dst_address.addr_long, sizeof(esp_zb_ieee_addr_t));
device->short_addr = 0xFFFF;
} else {
device->short_addr = record.dst_address.addr_short;
memset(device->ieee_addr, 0, sizeof(esp_zb_ieee_addr_t));
}
// Check if device already exists
bool device_exists = false;
for (std::list<zb_device_params_t *>::iterator dev_it = bound_devices.begin(); dev_it != bound_devices.end(); ++dev_it) {
if (is_ieee) {
if (memcmp((*dev_it)->ieee_addr, device->ieee_addr, sizeof(esp_zb_ieee_addr_t)) == 0) {
device_exists = true;
break;
}
} else {
if ((*dev_it)->short_addr == device->short_addr) {
device_exists = true;
break;
}
}
}
if (!device_exists) {
(*it)->addBoundDevice(device);
log_d(
"Device bound to EP %d -> device endpoint: %d, %s: %s", record.src_endp, device->endpoint, is_ieee ? "ieee addr" : "short addr",
is_ieee ? formatIEEEAddress(device->ieee_addr) : formatShortAddress(device->short_addr)
);
} else {
log_d("Device already exists, freeing allocated memory");
free(device); // Free the device if it already exists
}
}
}
}
// Print bound devices
log_d("Filling bounded devices finished");
free(req);
}
} else {
log_e("Binding table request failed with status: %d", zdo_status);
free(req);
}
}

View file

@ -8,6 +8,7 @@
#include "esp_zigbee_core.h"
#include "zdo/esp_zigbee_zdo_common.h"
#include "aps/esp_zigbee_aps.h"
#include <esp32-hal-log.h>
#include <list>
#include "ZigbeeEP.h"
@ -100,6 +101,7 @@ private:
uint8_t _open_network;
zigbee_scan_result_t *_scan_result;
SemaphoreHandle_t lock;
bool _debug;
bool zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs);
static void scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor);
@ -156,6 +158,7 @@ public:
}
void setRebootOpenNetwork(uint8_t time);
void openNetwork(uint8_t time);
void closeNetwork();
//scan_duration Time spent scanning each channel, in units of ((1 << scan_duration) + 1) * a beacon time. (15.36 microseconds)
void scanNetworks(uint32_t channel_mask = ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK, uint8_t scan_duration = 5);
@ -166,8 +169,29 @@ public:
void factoryReset(bool restart = true);
void setDebugMode(bool debug) {
_debug = debug;
}
bool getDebugMode() {
return _debug;
}
// Friend function declaration to allow access to private members
friend void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct);
friend bool zb_apsde_data_indication_handler(esp_zb_apsde_data_ind_t ind);
// Helper functions for formatting addresses
static inline const char *formatIEEEAddress(const esp_zb_ieee_addr_t addr) {
static char buf[24];
snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", addr[7], addr[6], addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
return buf;
}
static inline const char *formatShortAddress(uint16_t addr) {
static char buf[7];
snprintf(buf, sizeof(buf), "0x%04X", addr);
return buf;
}
};
extern ZigbeeCore Zigbee;

View file

@ -7,11 +7,6 @@
#include "esp_zigbee_cluster.h"
#include "zcl/esp_zigbee_zcl_power_config.h"
bool ZigbeeEP::_is_bound = false;
bool ZigbeeEP::_allow_multiple_binding = false;
//TODO: is_bound and allow_multiple_binding to make not static
/* Zigbee End Device Class */
ZigbeeEP::ZigbeeEP(uint8_t endpoint) {
_endpoint = endpoint;
@ -22,6 +17,9 @@ ZigbeeEP::ZigbeeEP(uint8_t endpoint) {
_read_model = NULL;
_read_manufacturer = NULL;
_time_status = 0;
_is_bound = false;
_use_manual_binding = false;
_allow_multiple_binding = false;
if (!lock) {
lock = xSemaphoreCreateBinary();
if (lock == NULL) {
@ -562,6 +560,54 @@ void ZigbeeEP::requestOTAUpdate() {
esp_zb_lock_release();
}
void ZigbeeEP::removeBoundDevice(uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr) {
log_d(
"Attempting to remove device with endpoint %d and IEEE address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", endpoint, ieee_addr[7], ieee_addr[6], ieee_addr[5],
ieee_addr[4], ieee_addr[3], ieee_addr[2], ieee_addr[1], ieee_addr[0]
);
for (std::list<zb_device_params_t *>::iterator it = _bound_devices.begin(); it != _bound_devices.end(); ++it) {
if ((*it)->endpoint == endpoint && memcmp((*it)->ieee_addr, ieee_addr, sizeof(esp_zb_ieee_addr_t)) == 0) {
log_d("Found matching device, removing it");
_bound_devices.erase(it);
if (_bound_devices.empty()) {
_is_bound = false;
}
return;
}
}
log_w("No matching device found for removal");
}
void ZigbeeEP::removeBoundDevice(zb_device_params_t *device) {
if (!device) {
log_e("Invalid device parameters provided");
return;
}
log_d(
"Attempting to remove device with endpoint %d, short address 0x%04x, IEEE address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", device->endpoint,
device->short_addr, device->ieee_addr[7], device->ieee_addr[6], device->ieee_addr[5], device->ieee_addr[4], device->ieee_addr[3], device->ieee_addr[2],
device->ieee_addr[1], device->ieee_addr[0]
);
for (std::list<zb_device_params_t *>::iterator it = _bound_devices.begin(); it != _bound_devices.end(); ++it) {
bool endpoint_matches = ((*it)->endpoint == device->endpoint);
bool short_addr_matches = (device->short_addr != 0xFFFF && (*it)->short_addr == device->short_addr);
bool ieee_addr_matches = (memcmp((*it)->ieee_addr, device->ieee_addr, sizeof(esp_zb_ieee_addr_t)) == 0);
if (endpoint_matches && (short_addr_matches || ieee_addr_matches)) {
log_d("Found matching device by %s, removing it", short_addr_matches ? "short address" : "IEEE address");
_bound_devices.erase(it);
if (_bound_devices.empty()) {
_is_bound = false;
}
return;
}
}
log_w("No matching device found for removal");
}
const char *ZigbeeEP::esp_zb_zcl_status_to_name(esp_zb_zcl_status_t status) {
switch (status) {
case ESP_ZB_ZCL_STATUS_SUCCESS: return "Success";

View file

@ -66,12 +66,15 @@ public:
return _bound_devices;
}
static bool bound() {
bool bound() {
return _is_bound;
}
static void allowMultipleBinding(bool bind) {
void allowMultipleBinding(bool bind) {
_allow_multiple_binding = bind;
}
void setManualBinding(bool bind) {
_use_manual_binding = bind;
}
// Set Manufacturer name and model
bool setManufacturerAndModel(const char *name, const char *model);
@ -98,6 +101,9 @@ public:
bool epAllowMultipleBinding() {
return _allow_multiple_binding;
}
bool epUseManualBinding() {
return _use_manual_binding;
}
// OTA methods
/**
@ -138,6 +144,14 @@ public:
_is_bound = true;
}
virtual void removeBoundDevice(uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr);
virtual void removeBoundDevice(zb_device_params_t *device);
virtual void clearBoundDevices() {
_bound_devices.clear();
_is_bound = false;
}
void onIdentify(void (*callback)(uint16_t)) {
_on_identify = callback;
}
@ -157,8 +171,9 @@ protected:
esp_zb_ha_standard_devices_t _device_id;
esp_zb_endpoint_config_t _ep_config;
esp_zb_cluster_list_t *_cluster_list;
static bool _is_bound;
static bool _allow_multiple_binding;
bool _is_bound;
bool _allow_multiple_binding;
bool _use_manual_binding;
std::list<zb_device_params_t *> _bound_devices;
SemaphoreHandle_t lock;
zb_power_source_t _power_source;

View file

@ -6,7 +6,8 @@ ZigbeeColorDimmerSwitch *ZigbeeColorDimmerSwitch::_instance = nullptr;
ZigbeeColorDimmerSwitch::ZigbeeColorDimmerSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID;
_instance = this; // Set the static pointer to this instance
_instance = this; // Set the static pointer to this instance
_device = nullptr; // Initialize light pointer to null
esp_zb_color_dimmable_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_SWITCH_CONFIG();
_cluster_list = esp_zb_color_dimmable_switch_clusters_create(&switch_cfg);
@ -17,20 +18,39 @@ ZigbeeColorDimmerSwitch::ZigbeeColorDimmerSwitch(uint8_t endpoint) : ZigbeeEP(en
}
void ZigbeeColorDimmerSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeColorDimmerSwitch *instance = static_cast<ZigbeeColorDimmerSwitch *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
log_i("Bound successfully!");
if (user_ctx) {
zb_device_params_t *light = (zb_device_params_t *)user_ctx;
if (instance->_device) {
zb_device_params_t *light = (zb_device_params_t *)instance->_device;
log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint);
_instance->_bound_devices.push_back(light);
log_d("Light bound to a switch on EP %d", instance->_endpoint);
instance->_bound_devices.push_back(light);
}
_is_bound = true;
instance->_is_bound = true;
} else {
log_e("Binding failed!");
instance->_device = nullptr;
}
}
void ZigbeeColorDimmerSwitch::bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeColorDimmerSwitch *instance = static_cast<ZigbeeColorDimmerSwitch *>(user_ctx);
if (instance) {
log_d("bindCbWrapper on EP %d", instance->_endpoint);
instance->bindCb(zdo_status, user_ctx);
}
}
void ZigbeeColorDimmerSwitch::findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeColorDimmerSwitch *instance = static_cast<ZigbeeColorDimmerSwitch *>(user_ctx);
if (instance) {
log_d("findCbWrapper on EP %d", instance->_endpoint);
instance->findCb(zdo_status, addr, endpoint, user_ctx);
}
}
void ZigbeeColorDimmerSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeColorDimmerSwitch *instance = static_cast<ZigbeeColorDimmerSwitch *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
log_d("Found light endpoint");
esp_zb_zdo_bind_req_param_t bind_req;
@ -39,22 +59,23 @@ void ZigbeeColorDimmerSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t ad
light->short_addr = addr;
esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr);
esp_zb_get_long_address(bind_req.src_address);
bind_req.src_endp = *((uint8_t *)user_ctx); //_endpoint;
bind_req.src_endp = instance->_endpoint;
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;
bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED;
memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t));
bind_req.dst_endp = endpoint;
bind_req.req_dst_addr = esp_zb_get_short_address();
instance->_device = light;
log_v("Try to bind on/off control of dimmable light");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeColorDimmerSwitch::bindCbWrapper, NULL);
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL;
log_v("Try to bind level control of dimmable light");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeColorDimmerSwitch::bindCbWrapper, NULL);
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL;
log_v("Try to bind color control of dimmable light");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeColorDimmerSwitch::bindCbWrapper, this);
} else {
log_v("No color dimmable light endpoint found");
log_d("No color dimmable light endpoint found");
}
}
@ -70,7 +91,7 @@ void ZigbeeColorDimmerSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cm
.num_out_clusters = 3,
.cluster_list = cluster_list,
};
esp_zb_zdo_match_cluster(&color_dimmable_light_req, findCb, &_endpoint);
esp_zb_zdo_match_cluster(&color_dimmable_light_req, ZigbeeColorDimmerSwitch::findCbWrapper, this);
}
// Methods to control the light

View file

@ -47,10 +47,13 @@ public:
private:
// save instance of the class in order to use it in static functions
static ZigbeeColorDimmerSwitch *_instance;
zb_device_params_t *_device;
void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req);
static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
static void bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx);
static void findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
void calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y);
};

View file

@ -7,6 +7,7 @@ ZigbeeSwitch *ZigbeeSwitch::_instance = nullptr;
ZigbeeSwitch::ZigbeeSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID;
_instance = this; // Set the static pointer to this instance
_device = nullptr;
esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG();
_cluster_list = esp_zb_on_off_switch_clusters_create(&switch_cfg);
@ -15,18 +16,40 @@ ZigbeeSwitch::ZigbeeSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) {
}
void ZigbeeSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeSwitch *instance = static_cast<ZigbeeSwitch *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
log_i("Bound successfully!");
if (user_ctx) {
zb_device_params_t *light = (zb_device_params_t *)user_ctx;
if (instance->_device) {
zb_device_params_t *light = (zb_device_params_t *)instance->_device;
log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint);
_instance->_bound_devices.push_back(light);
log_d("Light bound to a switch on EP %d", instance->_endpoint);
instance->_bound_devices.push_back(light);
}
_is_bound = true;
instance->_is_bound = true;
} else {
instance->_device = nullptr;
}
}
void ZigbeeSwitch::bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeSwitch *instance = static_cast<ZigbeeSwitch *>(user_ctx);
if (instance) {
log_d("bindCbWrapper on EP %d", instance->_endpoint);
instance->bindCb(zdo_status, user_ctx);
}
}
// Static wrapper for findCb
void ZigbeeSwitch::findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeSwitch *instance = static_cast<ZigbeeSwitch *>(user_ctx);
if (instance) {
log_d("findCbWrapper on EP %d", instance->_endpoint);
instance->findCb(zdo_status, addr, endpoint, user_ctx);
}
}
void ZigbeeSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeSwitch *instance = static_cast<ZigbeeSwitch *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
log_d("Found light endpoint");
esp_zb_zdo_bind_req_param_t bind_req;
@ -34,15 +57,21 @@ void ZigbeeSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t
light->endpoint = endpoint;
light->short_addr = addr;
esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr);
log_d("Light found: short address(0x%x), endpoint(%d)", light->short_addr, light->endpoint);
esp_zb_get_long_address(bind_req.src_address);
bind_req.src_endp = *((uint8_t *)user_ctx); //_endpoint;
bind_req.src_endp = instance->_endpoint;
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;
bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED;
memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t));
bind_req.dst_endp = endpoint;
bind_req.req_dst_addr = esp_zb_get_short_address();
log_i("Try to bind On/Off");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light);
log_v("Try to bind On/Off");
//save light params in the class
instance->_device = light;
log_d("Find callback on EP %d", instance->_endpoint);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeSwitch::bindCbWrapper, this);
} else {
log_d("No light endpoint found");
}
@ -59,7 +88,7 @@ void ZigbeeSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) {
.num_out_clusters = 1,
.cluster_list = cluster_list,
};
esp_zb_zdo_match_cluster(&on_off_req, findCb, &_endpoint);
esp_zb_zdo_match_cluster(&on_off_req, ZigbeeSwitch::findCbWrapper, this);
}
// Methods to control the light

View file

@ -37,10 +37,12 @@ public:
private:
// save instance of the class in order to use it in static functions
static ZigbeeSwitch *_instance;
zb_device_params_t *_device;
void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req);
static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
static void findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
static void bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx);
};
#endif // CONFIG_ZB_ENABLED

View file

@ -10,7 +10,8 @@ ZigbeeThermostat *ZigbeeThermostat::_instance = nullptr;
ZigbeeThermostat::ZigbeeThermostat(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_THERMOSTAT_DEVICE_ID;
_instance = this; // Set the static pointer to this instance
_instance = this; // Set the static pointer to this instance
_device = nullptr; // Initialize sensor pointer to null
//use custom config to avoid narrowing error -> must be fixed in zigbee-sdk
esp_zb_thermostat_cfg_t thermostat_cfg = ZB_DEFAULT_THERMOSTAT_CONFIG();
@ -29,21 +30,39 @@ ZigbeeThermostat::ZigbeeThermostat(uint8_t endpoint) : ZigbeeEP(endpoint) {
}
void ZigbeeThermostat::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeThermostat *instance = static_cast<ZigbeeThermostat *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
if (user_ctx) {
zb_device_params_t *sensor = (zb_device_params_t *)user_ctx;
log_i("The temperature sensor originating from address(0x%x) on endpoint(%d)", sensor->short_addr, sensor->endpoint);
_instance->_bound_devices.push_back(sensor);
} else {
log_v("Local binding success");
log_i("Bound successfully!");
if (instance->_device) {
zb_device_params_t *sensor = (zb_device_params_t *)instance->_device;
log_i("The sensor originating from address(0x%x) on endpoint(%d)", sensor->short_addr, sensor->endpoint);
log_d("Sensor bound to thermostat on EP %d", instance->_endpoint);
instance->_bound_devices.push_back(sensor);
}
_is_bound = true;
instance->_is_bound = true;
} else {
log_e("Binding failed!");
instance->_device = nullptr;
}
}
void ZigbeeThermostat::bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
ZigbeeThermostat *instance = static_cast<ZigbeeThermostat *>(user_ctx);
if (instance) {
log_d("bindCbWrapper on EP %d", instance->_endpoint);
instance->bindCb(zdo_status, user_ctx);
}
}
void ZigbeeThermostat::findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeThermostat *instance = static_cast<ZigbeeThermostat *>(user_ctx);
if (instance) {
log_d("findCbWrapper on EP %d", instance->_endpoint);
instance->findCb(zdo_status, addr, endpoint, user_ctx);
}
}
void ZigbeeThermostat::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
ZigbeeThermostat *instance = static_cast<ZigbeeThermostat *>(user_ctx);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
log_i("Found temperature sensor");
esp_zb_zdo_bind_req_param_t bind_req;
@ -56,37 +75,34 @@ void ZigbeeThermostat::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uin
/* 1. Send binding request to the sensor */
bind_req.req_dst_addr = addr;
log_d("Request temperature sensor to bind us");
/* populate the src information of the binding */
memcpy(bind_req.src_address, sensor->ieee_addr, sizeof(esp_zb_ieee_addr_t));
bind_req.src_endp = endpoint;
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT;
log_d("Bind temperature sensor");
/* populate the dst information of the binding */
bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED;
esp_zb_get_long_address(bind_req.dst_address_u.addr_long);
bind_req.dst_endp = *((uint8_t *)user_ctx); //_endpoint;
bind_req.dst_endp = instance->_endpoint;
log_i("Request temperature sensor to bind us");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeThermostat::bindCbWrapper, NULL);
/* 2. Send binding request to self */
bind_req.req_dst_addr = esp_zb_get_short_address();
/* populate the src information of the binding */
esp_zb_get_long_address(bind_req.src_address);
bind_req.src_endp = *((uint8_t *)user_ctx); //_endpoint;
bind_req.src_endp = instance->_endpoint;
bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT;
/* populate the dst information of the binding */
bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED;
memcpy(bind_req.dst_address_u.addr_long, sensor->ieee_addr, sizeof(esp_zb_ieee_addr_t));
bind_req.dst_endp = endpoint;
log_i("Try to bind Temperature Measurement");
//save sensor params in the class
instance->_device = sensor;
log_i("Bind temperature sensor");
esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)sensor);
log_d("Find callback on EP %d", instance->_endpoint);
esp_zb_zdo_device_bind_req(&bind_req, ZigbeeThermostat::bindCbWrapper, this);
} else {
log_d("No temperature sensor endpoint found");
}
}
@ -96,7 +112,7 @@ void ZigbeeThermostat::findEndpoint(esp_zb_zdo_match_desc_req_param_t *param) {
param->num_in_clusters = 1;
param->num_out_clusters = 0;
param->cluster_list = cluster_list;
esp_zb_zdo_match_cluster(param, findCb, &_endpoint);
esp_zb_zdo_match_cluster(param, ZigbeeThermostat::findCbWrapper, this);
}
void ZigbeeThermostat::zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) {

View file

@ -48,6 +48,7 @@ public:
private:
// save instance of the class in order to use it in static functions
static ZigbeeThermostat *_instance;
zb_device_params_t *_device;
void (*_on_temp_recieve)(float);
void (*_on_config_recieve)(float, float, float);
@ -56,8 +57,10 @@ private:
float _tolerance;
void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req);
static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx);
void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
static void bindCbWrapper(esp_zb_zdp_status_t zdo_status, void *user_ctx);
static void findCbWrapper(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx);
void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) override;
};