- Notifications
You must be signed in to change notification settings - Fork7.8k
feat(zigbee): Remove static variables, improve binding, new example#11316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
me-no-dev merged 4 commits intoespressif:masterfromP-R-O-C-H-Y:feat/zigbee-bind-multiswitchMay 27, 2025
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
Show all changes
4 commits Select commitHold shift + click to select a range
c04bdcc feat(zigbee): Remove static variables, improve binding, new example
P-R-O-C-H-Ydb5d678 Merge branch 'master' into feat/zigbee-bind-multiswitch
P-R-O-C-H-Y94e00fe feat(example): Add missing header
P-R-O-C-H-Y4dff4ec ci(pre-commit): Apply automatic fixes
pre-commit-ci-lite[bot]File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
110 changes: 110 additions & 0 deletionslibraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
274 changes: 274 additions & 0 deletionslibraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/Zigbee_On_Off_MultiSwitch.ino
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "fqbn_append": "PartitionScheme=zigbee_zczr,ZigbeeMode=zczr", | ||
| "requires": [ | ||
| "CONFIG_SOC_IEEE802154_SUPPORTED=y" | ||
| ] | ||
| } |
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.