From c04bdccd91e54b11af90700903f23e4609c16fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:12:19 +0200 Subject: [PATCH] feat(zigbee): Remove static variables, improve binding, new example --- .../Zigbee_On_Off_MultiSwitch/README.md | 110 +++++++ .../Zigbee_On_Off_MultiSwitch.ino | 244 ++++++++++++++ .../Zigbee_On_Off_MultiSwitch/ci.json | 6 + libraries/Zigbee/src/ZigbeeCore.cpp | 300 +++++++++++++++--- libraries/Zigbee/src/ZigbeeCore.h | 25 ++ libraries/Zigbee/src/ZigbeeEP.cpp | 53 +++- libraries/Zigbee/src/ZigbeeEP.h | 25 +- .../Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp | 43 ++- .../Zigbee/src/ep/ZigbeeColorDimmerSwitch.h | 7 +- libraries/Zigbee/src/ep/ZigbeeSwitch.cpp | 45 ++- libraries/Zigbee/src/ep/ZigbeeSwitch.h | 8 +- libraries/Zigbee/src/ep/ZigbeeThermostat.cpp | 60 ++-- libraries/Zigbee/src/ep/ZigbeeThermostat.h | 7 +- 13 files changed, 830 insertions(+), 103 deletions(-) create mode 100644 libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md create mode 100644 libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/Zigbee_On_Off_MultiSwitch.ino create mode 100644 libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/ci.json diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md new file mode 100644 index 00000000000..7ec806a5a62 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md @@ -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) diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/Zigbee_On_Off_MultiSwitch.ino b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/Zigbee_On_Off_MultiSwitch.ino new file mode 100644 index 00000000000..279dee58e9f --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/Zigbee_On_Off_MultiSwitch.ino @@ -0,0 +1,244 @@ +#ifndef ZIGBEE_MODE_ZCZR +#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode" +#endif + +#include "Zigbee.h" +#include + +#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"); + } + } +} \ No newline at end of file diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/ci.json b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/ci.json new file mode 100644 index 00000000000..e79a477da11 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee_zczr,ZigbeeMode=zczr", + "requires": [ + "CONFIG_SOC_IEEE802154_SUPPORTED=y" + ] +} diff --git a/libraries/Zigbee/src/ZigbeeCore.cpp b/libraries/Zigbee/src/ZigbeeCore.cpp index b93542159a6..987a6cc4f2e 100644 --- a/libraries/Zigbee/src/ZigbeeCore.cpp +++ b/libraries/Zigbee/src/ZigbeeCore.cpp @@ -5,6 +5,7 @@ #include "ZigbeeHandlers.cpp" #include "Arduino.h" +#include #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::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 bound_devices = (*it)->getBoundDevices(); - for (std::list::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 bound_devices = (*it)->getBoundDevices(); + for (std::list::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::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 bound_devices = (*it)->getBoundDevices(); + for (std::list::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,28 @@ 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 +522,193 @@ 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::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 found_devices; + static std::vector 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, - record->dst_addr_mode - ); + log_d("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); + all_records.push_back(*record); + record = record->next; + } + + // If this is not the last chunk, request the next one + if (table_info->index + table_info->count < table_info->total) { + 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); + } 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) { + + 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)); + } - 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)); + // Track this device as found + found_devices.insert(dev_id); } - // Add to list of bound devices of proper endpoint + // Now process each endpoint and update its bound devices for (std::list::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] - ); + log_d("Processing endpoint %d", (*it)->getEndpoint()); + std::list bound_devices = (*it)->getBoundDevices(); + std::list devices_to_remove; + + // First, identify devices that need to be removed + for (std::list::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::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::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 + } + } } } - record = record->next; - } - // Continue reading the binding table - if (table_info->index + table_info->count < table_info->total) { - /* There are unreported binding table entries, request for them. */ - req->start_index = table_info->index + table_info->count; - esp_zb_zdo_binding_table_req(req, bindingTableCb, req); - done = false; + // Print bound devices + log_d("Filling bounded devices finished"); + free(req); } - } - - if (done) { - // Print bound devices - log_d("Filling bounded devices finished"); + } else { + log_e("Binding table request failed with status: %d", zdo_status); free(req); } } diff --git a/libraries/Zigbee/src/ZigbeeCore.h b/libraries/Zigbee/src/ZigbeeCore.h index 06c3ec4551a..321f12c5653 100644 --- a/libraries/Zigbee/src/ZigbeeCore.h +++ b/libraries/Zigbee/src/ZigbeeCore.h @@ -8,6 +8,7 @@ #include "esp_zigbee_core.h" #include "zdo/esp_zigbee_zdo_common.h" +#include "aps/esp_zigbee_aps.h" #include #include #include "ZigbeeEP.h" @@ -100,6 +101,7 @@ class ZigbeeCore { 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 @@ class ZigbeeCore { } 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,30 @@ class ZigbeeCore { 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; diff --git a/libraries/Zigbee/src/ZigbeeEP.cpp b/libraries/Zigbee/src/ZigbeeEP.cpp index cf52a902983..314b0c03c40 100644 --- a/libraries/Zigbee/src/ZigbeeEP.cpp +++ b/libraries/Zigbee/src/ZigbeeEP.cpp @@ -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,51 @@ 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::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::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"; diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index e13b3b59de9..a86852bf3c8 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -66,12 +66,16 @@ class ZigbeeEP { 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 +102,10 @@ class ZigbeeEP { bool epAllowMultipleBinding() { return _allow_multiple_binding; } + bool epUseManualBinding() { + return _use_manual_binding; + } + // OTA methods /** @@ -138,6 +146,14 @@ class ZigbeeEP { _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 +173,9 @@ class ZigbeeEP { 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 _bound_devices; SemaphoreHandle_t lock; zb_power_source_t _power_source; diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp index 68f287153cb..da65c2ec65d 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp @@ -7,6 +7,7 @@ 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 + _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(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(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(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(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 diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h index dbe50a20230..ada1f75fbb4 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h @@ -47,10 +47,13 @@ class ZigbeeColorDimmerSwitch : public ZigbeeEP { 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); }; diff --git a/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp index 86c68ae1870..c4468af301f 100644 --- a/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp @@ -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(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(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(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(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 diff --git a/libraries/Zigbee/src/ep/ZigbeeSwitch.h b/libraries/Zigbee/src/ep/ZigbeeSwitch.h index 5c527bec6e3..0e7011c3312 100644 --- a/libraries/Zigbee/src/ep/ZigbeeSwitch.h +++ b/libraries/Zigbee/src/ep/ZigbeeSwitch.h @@ -37,10 +37,12 @@ class ZigbeeSwitch : public ZigbeeEP { 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 diff --git a/libraries/Zigbee/src/ep/ZigbeeThermostat.cpp b/libraries/Zigbee/src/ep/ZigbeeThermostat.cpp index 357bcaed1bc..b242198e38e 100644 --- a/libraries/Zigbee/src/ep/ZigbeeThermostat.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeThermostat.cpp @@ -11,6 +11,7 @@ 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 + _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(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(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(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(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) { diff --git a/libraries/Zigbee/src/ep/ZigbeeThermostat.h b/libraries/Zigbee/src/ep/ZigbeeThermostat.h index 7895115e1d1..acdcd68e512 100644 --- a/libraries/Zigbee/src/ep/ZigbeeThermostat.h +++ b/libraries/Zigbee/src/ep/ZigbeeThermostat.h @@ -48,6 +48,7 @@ class ZigbeeThermostat : public ZigbeeEP { 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 @@ class ZigbeeThermostat : public ZigbeeEP { 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; };