Skip to content

Commit 542274d

Browse files
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>
1 parent 9090b46 commit 542274d

13 files changed

+862
-102
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Arduino-ESP32 Zigbee Multi-Switch Example
2+
3+
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.
4+
5+
# Supported Targets
6+
7+
Currently, this example supports the following targets.
8+
9+
| Supported Targets | ESP32-C6 | ESP32-H2 |
10+
| ----------------- | -------- | -------- |
11+
12+
## Hardware Required
13+
14+
* One development board (ESP32-H2 or ESP32-C6) acting as Zigbee multi-switch controller
15+
* One or more Zigbee light devices (loaded with Zigbee_On_Off_Light example)
16+
* A USB cable for power supply and programming
17+
18+
### Configure the Project
19+
20+
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:
21+
22+
1. **Coordinator Mode**: For running your own Zigbee network
23+
2. **Router Mode**: For Home Assistant integration
24+
25+
#### Using Arduino IDE
26+
27+
To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits).
28+
29+
* Before Compile/Verify, select the correct board: `Tools -> Board`
30+
* Select the Zigbee mode: `Tools -> Zigbee mode: Zigbee ZCZR (coordinator/router)`
31+
* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs`
32+
* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port
33+
* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`
34+
35+
## Features
36+
37+
The multi-switch example provides the following functionality:
38+
39+
1. **Light Configuration**
40+
- Configure up to 3 different lights using their endpoint and IEEE address
41+
- Configuration is stored in NVS (Non-Volatile Storage) and persists after power loss
42+
- Remove configured lights when needed
43+
44+
2. **Control Commands**
45+
- Control all bound lights simultaneously:
46+
- Turn all bound lights ON
47+
- Turn all bound lights OFF
48+
- Toggle all bound lights
49+
- Control individual lights (1-3):
50+
- Turn light ON
51+
- Turn light OFF
52+
- Toggle light
53+
54+
3. **Network Management**
55+
- Factory reset capability
56+
- Open network for device joining
57+
- View bound devices and current light configurations
58+
59+
## Serial Commands
60+
61+
The example accepts the following commands through the serial interface:
62+
63+
* `config` - Configure a new light (requires light number, endpoint, and IEEE address)
64+
* `remove` - Remove a configured light
65+
* `on` - Turn all bound lights ON
66+
* `off` - Turn all bound lights OFF
67+
* `toggle` - Toggle all bound lights
68+
* `1on`, `2on`, `3on` - Turn individual light ON
69+
* `1off`, `2off`, `3off` - Turn individual light OFF
70+
* `1toggle`, `2toggle`, `3toggle` - Toggle individual light
71+
* `freset` - Perform factory reset
72+
* `open_network` - Open network for device joining (only for coordinator role)
73+
74+
## Troubleshooting
75+
76+
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.
77+
You can do the following:
78+
79+
* In the Arduino IDE go to the Tools menu and set `Erase All Flash Before Sketch Upload` to `Enabled`
80+
* In the `Zigbee_On_Off_Light` example sketch call `Zigbee.factoryReset()`
81+
82+
By default, the coordinator network is closed after rebooting or flashing new firmware.
83+
To open the network you have 2 options:
84+
85+
* Open network after reboot by setting `Zigbee.setRebootOpenNetwork(time)` before calling `Zigbee.begin()`
86+
* In application you can anytime call `Zigbee.openNetwork(time)` to open the network for devices to join
87+
88+
***Important: Make sure you are using a good quality USB cable and that you have a reliable power source***
89+
90+
* **LED not blinking:** Check the wiring connection and the IO selection
91+
* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed
92+
* **COM port not detected:** Check the USB cable and the USB to Serial driver installation
93+
94+
If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute).
95+
96+
## Contribute
97+
98+
To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst)
99+
100+
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!
101+
102+
Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else.
103+
104+
## Resources
105+
106+
* Official ESP32 Forum: [Link](https://esp32.com)
107+
* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
108+
* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf)
109+
* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf)
110+
* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com)
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @brief This example demonstrates simple Zigbee multi-light switch.
17+
*
18+
* The example demonstrates how to use Zigbee library to control multiple light bulbs.
19+
* The light bulbs are Zigbee devices, which are controlled by a Zigbee coordinator/router (Multi-Switch).
20+
* Settings are stored in NVS to not be lost after power loss.
21+
* Configuring and controlling the lights is done via serial input.
22+
*
23+
* Proper Zigbee mode must be selected in Tools->Zigbee mode
24+
* and also the correct partition scheme must be selected in Tools->Partition Scheme.
25+
*
26+
* Please check the README.md for instructions and more detailed description.
27+
*
28+
* Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
29+
*/
30+
31+
#ifndef ZIGBEE_MODE_ZCZR
32+
#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"
33+
#endif
34+
35+
#include "Zigbee.h"
36+
#include <Preferences.h>
37+
38+
#define ZIGBEE_ROLE ZIGBEE_ROUTER // ZIGBEE_ROUTER for HomeAssistant integration, ZIGBEE_COORDINATOR for running own network
39+
40+
/* Zigbee switch configuration */
41+
#define SWITCH_ENDPOINT_NUMBER 1
42+
43+
uint8_t button = BOOT_PIN;
44+
45+
ZigbeeSwitch zbSwitch = ZigbeeSwitch(SWITCH_ENDPOINT_NUMBER);
46+
47+
int buttonState;
48+
int lastButtonState = LOW;
49+
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
50+
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
51+
52+
// To be stored in NVS to not be lost after power loss
53+
Preferences prefs;
54+
55+
zb_device_params_t light_1;
56+
zb_device_params_t light_2;
57+
zb_device_params_t light_3;
58+
59+
void storeLightParams(zb_device_params_t *light, int light_number) {
60+
char key[10];
61+
snprintf(key, sizeof(key), "light_%d", light_number);
62+
prefs.putBytes(key, light, sizeof(zb_device_params_t));
63+
}
64+
65+
void loadLightParams(zb_device_params_t *light, int light_number) {
66+
char key[10];
67+
snprintf(key, sizeof(key), "light_%d", light_number);
68+
prefs.getBytes(key, light, sizeof(zb_device_params_t));
69+
}
70+
71+
/********************* Arduino functions **************************/
72+
void setup() {
73+
Serial.begin(115200);
74+
75+
// Initialize Preferences
76+
prefs.begin("lights", false); // false means read/write mode
77+
78+
// Load saved light parameters
79+
loadLightParams(&light_1, 1);
80+
loadLightParams(&light_2, 2);
81+
loadLightParams(&light_3, 3);
82+
83+
// Init button switch
84+
pinMode(button, INPUT_PULLUP);
85+
86+
// Set Zigbee device name and model
87+
zbSwitch.setManufacturerAndModel("Espressif", "ZBMultiSwitch");
88+
89+
// Set binding settings depending on the role
90+
if (ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
91+
zbSwitch.allowMultipleBinding(true); // To allow binding multiple lights to the switch
92+
} else {
93+
zbSwitch.setManualBinding(true); //Set manual binding to true, so binding is done on Home Assistant side
94+
}
95+
96+
// Add endpoint to Zigbee Core
97+
Serial.println("Adding ZigbeeSwitch endpoint to Zigbee Core");
98+
Zigbee.addEndpoint(&zbSwitch);
99+
100+
// When all EPs are registered, start Zigbee with given role
101+
if (!Zigbee.begin(ZIGBEE_ROLE)) {
102+
Serial.println("Zigbee failed to start!");
103+
Serial.println("Rebooting...");
104+
ESP.restart();
105+
}
106+
107+
Serial.println("Connecting to network");
108+
while (!Zigbee.connected()) {
109+
Serial.print(".");
110+
delay(100);
111+
}
112+
Serial.println();
113+
}
114+
115+
void loop() {
116+
// Handle button switch in loop()
117+
if (digitalRead(button) == LOW) { // Push button pressed
118+
// Key debounce handling
119+
while (digitalRead(button) == LOW) {
120+
delay(50);
121+
}
122+
// Print bound devices
123+
Serial.println("Bound devices:");
124+
zbSwitch.printBoundDevices(Serial);
125+
Serial.println("Lights configured:");
126+
Serial.printf("Light 1: %d %s\n", light_1.endpoint, Zigbee.formatIEEEAddress(light_1.ieee_addr));
127+
Serial.printf("Light 2: %d %s\n", light_2.endpoint, Zigbee.formatIEEEAddress(light_2.ieee_addr));
128+
Serial.printf("Light 3: %d %s\n", light_3.endpoint, Zigbee.formatIEEEAddress(light_3.ieee_addr));
129+
}
130+
// Handle serial input to configure and control the lights
131+
if (Serial.available()) {
132+
String command = Serial.readString();
133+
Serial.println("Command: " + command);
134+
135+
if (command == "config") {
136+
//wait for light number, endpoint and ieee address
137+
Serial.println("Enter light number (1-3):");
138+
while (!Serial.available()) {
139+
delay(100);
140+
}
141+
int light_number = Serial.parseInt();
142+
Serial.println("Enter endpoint:");
143+
while (!Serial.available()) {
144+
delay(100);
145+
}
146+
int endpoint = Serial.parseInt();
147+
Serial.println("Enter ieee address:");
148+
while (!Serial.available()) {
149+
delay(100);
150+
}
151+
String ieee_address = Serial.readStringUntil('\n');
152+
ieee_address.trim();
153+
//convert ieee address to uint8_t array (format in string is 00:00:00:00:00:00:00:00)
154+
uint8_t ieee_address_array[8];
155+
int index = 0;
156+
bool valid = true;
157+
158+
// Check if the string has the correct format (8 hex pairs with colons)
159+
if (ieee_address.length() != 23) { // 8 pairs * 2 + 7 colons
160+
Serial.println("Invalid IEEE address format. Expected format: 00:00:00:00:00:00:00:00");
161+
valid = false;
162+
} else {
163+
for (int i = 0; i < ieee_address.length() && index < 8 && valid; i += 3) {
164+
// Check for colon at expected positions
165+
if (i > 0 && ieee_address.charAt(i - 1) != ':') {
166+
valid = false;
167+
break;
168+
}
169+
// Convert two hex characters to a byte
170+
char hex[3] = {ieee_address.charAt(i), ieee_address.charAt(i + 1), '\0'};
171+
char *endptr;
172+
long value = strtol(hex, &endptr, 16);
173+
if (*endptr != '\0' || value < 0 || value > 255) {
174+
valid = false;
175+
break;
176+
}
177+
// Store bytes in reverse order to match Zigbee standard
178+
ieee_address_array[7 - index++] = (uint8_t)value;
179+
}
180+
}
181+
182+
if (!valid || index != 8) {
183+
Serial.println("Invalid IEEE address. Please enter a valid address in format: 00:00:00:00:00:00:00:00");
184+
return;
185+
}
186+
//set the light parameters
187+
if (light_number == 1) {
188+
light_1.endpoint = endpoint;
189+
memcpy(light_1.ieee_addr, ieee_address_array, 8);
190+
storeLightParams(&light_1, 1);
191+
} else if (light_number == 2) {
192+
light_2.endpoint = endpoint;
193+
memcpy(light_2.ieee_addr, ieee_address_array, 8);
194+
storeLightParams(&light_2, 2);
195+
} else if (light_number == 3) {
196+
light_3.endpoint = endpoint;
197+
memcpy(light_3.ieee_addr, ieee_address_array, 8);
198+
storeLightParams(&light_3, 3);
199+
}
200+
Serial.printf("Light %d configured\n", light_number);
201+
} else if (command == "remove") {
202+
//wait for light number
203+
Serial.println("Enter light number (1-3):");
204+
while (!Serial.available()) {
205+
delay(100);
206+
}
207+
int light_number = Serial.parseInt();
208+
uint8_t ieee_address_empty[8] = {0, 0, 0, 0, 0, 0, 0, 0};
209+
if (light_number == 1) {
210+
light_1.endpoint = 0;
211+
memcpy(light_1.ieee_addr, ieee_address_empty, 8);
212+
storeLightParams(&light_1, 1);
213+
} else if (light_number == 2) {
214+
light_2.endpoint = 0;
215+
memcpy(light_2.ieee_addr, ieee_address_empty, 8);
216+
storeLightParams(&light_2, 2);
217+
} else if (light_number == 3) {
218+
light_3.endpoint = 0;
219+
memcpy(light_3.ieee_addr, ieee_address_empty, 8);
220+
storeLightParams(&light_3, 3);
221+
}
222+
Serial.printf("Light %d removed\n", light_number);
223+
} else if (command == "on") {
224+
Serial.println(" --> SIG Input : All Lights ON");
225+
zbSwitch.lightOn();
226+
} else if (command == "off") {
227+
Serial.println(" --> SIG Input : All Lights OFF");
228+
zbSwitch.lightOff();
229+
} else if (command == "toggle") {
230+
Serial.println(" --> SIG Input : All Lights Toggle");
231+
zbSwitch.lightToggle();
232+
} else if (command == "1on") {
233+
Serial.println(" --> SIG Input : Light 1 ON");
234+
zbSwitch.lightOn(light_1.endpoint, light_1.ieee_addr);
235+
} else if (command == "1off") {
236+
Serial.println(" --> SIG Input : Light 1 OFF");
237+
zbSwitch.lightOff(light_1.endpoint, light_1.ieee_addr);
238+
} else if (command == "1toggle") {
239+
Serial.println(" --> SIG Input : Light 1 Toggle");
240+
zbSwitch.lightToggle(light_1.endpoint, light_1.ieee_addr);
241+
} else if (command == "2on") {
242+
Serial.println(" --> SIG Input : Light 2 ON");
243+
zbSwitch.lightOn(light_2.endpoint, light_2.ieee_addr);
244+
} else if (command == "2off") {
245+
Serial.println(" --> SIG Input : Light 2 OFF");
246+
zbSwitch.lightOff(light_2.endpoint, light_2.ieee_addr);
247+
} else if (command == "2toggle") {
248+
Serial.println(" --> SIG Input : Light 2 Toggle");
249+
zbSwitch.lightToggle(light_2.endpoint, light_2.ieee_addr);
250+
} else if (command == "3on") {
251+
Serial.println(" --> SIG Input : Light 3 ON");
252+
zbSwitch.lightOn(light_3.endpoint, light_3.ieee_addr);
253+
} else if (command == "3off") {
254+
Serial.println(" --> SIG Input : Light 3 OFF");
255+
zbSwitch.lightOff(light_3.endpoint, light_3.ieee_addr);
256+
} else if (command == "3toggle") {
257+
Serial.println(" --> SIG Input : Light 3 Toggle");
258+
zbSwitch.lightToggle(light_3.endpoint, light_3.ieee_addr);
259+
} else if (command == "freset") {
260+
Serial.println(" --> SIG Input : Factory Reset!");
261+
delay(1500);
262+
Zigbee.factoryReset();
263+
} else if (command == "open_network") {
264+
Serial.println(" --> SIG Input : Open Network");
265+
if (ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
266+
Zigbee.openNetwork(180);
267+
} else {
268+
Serial.println("Open network is only available for coordinator role");
269+
}
270+
} else {
271+
Serial.println("Unknown command");
272+
}
273+
}
274+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"fqbn_append": "PartitionScheme=zigbee_zczr,ZigbeeMode=zczr",
3+
"requires": [
4+
"CONFIG_SOC_IEEE802154_SUPPORTED=y"
5+
]
6+
}

0 commit comments

Comments
 (0)