Skip to content

Commit c04bdcc

Browse files
committed
feat(zigbee): Remove static variables, improve binding, new example
1 parent f122366 commit c04bdcc

13 files changed

+830
-103
lines changed
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)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#ifndef ZIGBEE_MODE_ZCZR
2+
#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"
3+
#endif
4+
5+
#include "Zigbee.h"
6+
#include <Preferences.h>
7+
8+
#define ZIGBEE_ROLE ZIGBEE_ROUTER // ZIGBEE_ROUTER for HomeAssistant integration, ZIGBEE_COORDINATOR for running own network
9+
10+
/* Zigbee switch configuration */
11+
#define SWITCH_ENDPOINT_NUMBER 1
12+
13+
uint8_t button = BOOT_PIN;
14+
15+
ZigbeeSwitch zbSwitch = ZigbeeSwitch(SWITCH_ENDPOINT_NUMBER);
16+
17+
int buttonState;
18+
int lastButtonState = LOW;
19+
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
20+
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
21+
22+
// To be stored in NVS to not be lost after power loss
23+
Preferences prefs;
24+
25+
zb_device_params_t light_1;
26+
zb_device_params_t light_2;
27+
zb_device_params_t light_3;
28+
29+
void storeLightParams(zb_device_params_t *light, int light_number) {
30+
char key[10];
31+
snprintf(key, sizeof(key), "light_%d", light_number);
32+
prefs.putBytes(key, light, sizeof(zb_device_params_t));
33+
}
34+
35+
void loadLightParams(zb_device_params_t *light, int light_number) {
36+
char key[10];
37+
snprintf(key, sizeof(key), "light_%d", light_number);
38+
prefs.getBytes(key, light, sizeof(zb_device_params_t));
39+
}
40+
41+
/********************* Arduino functions **************************/
42+
void setup() {
43+
Serial.begin(115200);
44+
45+
// Initialize Preferences
46+
prefs.begin("lights", false); // false means read/write mode
47+
48+
// Load saved light parameters
49+
loadLightParams(&light_1, 1);
50+
loadLightParams(&light_2, 2);
51+
loadLightParams(&light_3, 3);
52+
53+
// Init button switch
54+
pinMode(button, INPUT_PULLUP);
55+
56+
// Set Zigbee device name and model
57+
zbSwitch.setManufacturerAndModel("Espressif", "ZBMultiSwitch");
58+
59+
// Set binding settings depending on the role
60+
if(ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
61+
zbSwitch.allowMultipleBinding(true); // To allow binding multiple lights to the switch
62+
} else {
63+
zbSwitch.setManualBinding(true); //Set manual binding to true, so binding is done on Home Assistant side
64+
}
65+
66+
// Add endpoint to Zigbee Core
67+
Serial.println("Adding ZigbeeSwitch endpoint to Zigbee Core");
68+
Zigbee.addEndpoint(&zbSwitch);
69+
70+
// When all EPs are registered, start Zigbee with given role
71+
if (!Zigbee.begin(ZIGBEE_ROLE)) {
72+
Serial.println("Zigbee failed to start!");
73+
Serial.println("Rebooting...");
74+
ESP.restart();
75+
}
76+
77+
Serial.println("Connecting to network");
78+
while (!Zigbee.connected()) {
79+
Serial.print(".");
80+
delay(100);
81+
}
82+
Serial.println();
83+
}
84+
85+
void loop() {
86+
// Handle button switch in loop()
87+
if (digitalRead(button) == LOW) { // Push button pressed
88+
// Key debounce handling
89+
while (digitalRead(button) == LOW) {
90+
delay(50);
91+
}
92+
// Print bound devices
93+
Serial.println("Bound devices:");
94+
zbSwitch.printBoundDevices(Serial);
95+
Serial.println("Lights configured:");
96+
Serial.printf("Light 1: %d %s\n", light_1.endpoint, Zigbee.formatIEEEAddress(light_1.ieee_addr));
97+
Serial.printf("Light 2: %d %s\n", light_2.endpoint, Zigbee.formatIEEEAddress(light_2.ieee_addr));
98+
Serial.printf("Light 3: %d %s\n", light_3.endpoint, Zigbee.formatIEEEAddress(light_3.ieee_addr));
99+
}
100+
// Handle serial input to configure and control the lights
101+
if (Serial.available()) {
102+
String command = Serial.readString();
103+
Serial.println("Command: " + command);
104+
105+
if(command == "config") {
106+
//wait for light number, endpoint and ieee address
107+
Serial.println("Enter light number (1-3):");
108+
while (!Serial.available()) {
109+
delay(100);
110+
}
111+
int light_number = Serial.parseInt();
112+
Serial.println("Enter endpoint:");
113+
while (!Serial.available()) {
114+
delay(100);
115+
}
116+
int endpoint = Serial.parseInt();
117+
Serial.println("Enter ieee address:");
118+
while (!Serial.available()) {
119+
delay(100);
120+
}
121+
String ieee_address = Serial.readStringUntil('\n');
122+
ieee_address.trim();
123+
//convert ieee address to uint8_t array (format in string is 00:00:00:00:00:00:00:00)
124+
uint8_t ieee_address_array[8];
125+
int index = 0;
126+
bool valid = true;
127+
128+
// Check if the string has the correct format (8 hex pairs with colons)
129+
if (ieee_address.length() != 23) { // 8 pairs * 2 + 7 colons
130+
Serial.println("Invalid IEEE address format. Expected format: 00:00:00:00:00:00:00:00");
131+
valid = false;
132+
} else {
133+
for (int i = 0; i < ieee_address.length() && index < 8 && valid; i += 3) {
134+
// Check for colon at expected positions
135+
if (i > 0 && ieee_address.charAt(i-1) != ':') {
136+
valid = false;
137+
break;
138+
}
139+
// Convert two hex characters to a byte
140+
char hex[3] = {ieee_address.charAt(i), ieee_address.charAt(i+1), '\0'};
141+
char* endptr;
142+
long value = strtol(hex, &endptr, 16);
143+
if (*endptr != '\0' || value < 0 || value > 255) {
144+
valid = false;
145+
break;
146+
}
147+
// Store bytes in reverse order to match Zigbee standard
148+
ieee_address_array[7 - index++] = (uint8_t)value;
149+
}
150+
}
151+
152+
if (!valid || index != 8) {
153+
Serial.println("Invalid IEEE address. Please enter a valid address in format: 00:00:00:00:00:00:00:00");
154+
return;
155+
}
156+
//set the light parameters
157+
if(light_number == 1) {
158+
light_1.endpoint = endpoint;
159+
memcpy(light_1.ieee_addr, ieee_address_array, 8);
160+
storeLightParams(&light_1, 1);
161+
} else if(light_number == 2) {
162+
light_2.endpoint = endpoint;
163+
memcpy(light_2.ieee_addr, ieee_address_array, 8);
164+
storeLightParams(&light_2, 2);
165+
} else if(light_number == 3) {
166+
light_3.endpoint = endpoint;
167+
memcpy(light_3.ieee_addr, ieee_address_array, 8);
168+
storeLightParams(&light_3, 3);
169+
}
170+
Serial.printf("Light %d configured\n", light_number);
171+
} else if (command == "remove") {
172+
//wait for light number
173+
Serial.println("Enter light number (1-3):");
174+
while (!Serial.available()) {
175+
delay(100);
176+
}
177+
int light_number = Serial.parseInt();
178+
uint8_t ieee_address_empty[8] = {0, 0, 0, 0, 0, 0, 0, 0};
179+
if(light_number == 1) {
180+
light_1.endpoint = 0;
181+
memcpy(light_1.ieee_addr, ieee_address_empty, 8);
182+
storeLightParams(&light_1, 1);
183+
} else if(light_number == 2) {
184+
light_2.endpoint = 0;
185+
memcpy(light_2.ieee_addr, ieee_address_empty, 8);
186+
storeLightParams(&light_2, 2);
187+
} else if(light_number == 3) {
188+
light_3.endpoint = 0;
189+
memcpy(light_3.ieee_addr, ieee_address_empty, 8);
190+
storeLightParams(&light_3, 3);
191+
}
192+
Serial.printf("Light %d removed\n", light_number);
193+
} else if (command == "on") {
194+
Serial.println(" --> SIG Input : All Lights ON");
195+
zbSwitch.lightOn();
196+
} else if (command == "off") {
197+
Serial.println(" --> SIG Input : All Lights OFF");
198+
zbSwitch.lightOff();
199+
} else if (command == "toggle") {
200+
Serial.println(" --> SIG Input : All Lights Toggle");
201+
zbSwitch.lightToggle();
202+
} else if (command == "1on") {
203+
Serial.println(" --> SIG Input : Light 1 ON");
204+
zbSwitch.lightOn(light_1.endpoint, light_1.ieee_addr);
205+
} else if (command == "1off") {
206+
Serial.println(" --> SIG Input : Light 1 OFF");
207+
zbSwitch.lightOff(light_1.endpoint, light_1.ieee_addr);
208+
} else if (command == "1toggle") {
209+
Serial.println(" --> SIG Input : Light 1 Toggle");
210+
zbSwitch.lightToggle(light_1.endpoint, light_1.ieee_addr);
211+
} else if (command == "2on") {
212+
Serial.println(" --> SIG Input : Light 2 ON");
213+
zbSwitch.lightOn(light_2.endpoint, light_2.ieee_addr);
214+
} else if (command == "2off") {
215+
Serial.println(" --> SIG Input : Light 2 OFF");
216+
zbSwitch.lightOff(light_2.endpoint, light_2.ieee_addr);
217+
} else if (command == "2toggle") {
218+
Serial.println(" --> SIG Input : Light 2 Toggle");
219+
zbSwitch.lightToggle(light_2.endpoint, light_2.ieee_addr);
220+
} else if (command == "3on") {
221+
Serial.println(" --> SIG Input : Light 3 ON");
222+
zbSwitch.lightOn(light_3.endpoint, light_3.ieee_addr);
223+
} else if (command == "3off") {
224+
Serial.println(" --> SIG Input : Light 3 OFF");
225+
zbSwitch.lightOff(light_3.endpoint, light_3.ieee_addr);
226+
} else if (command == "3toggle") {
227+
Serial.println(" --> SIG Input : Light 3 Toggle");
228+
zbSwitch.lightToggle(light_3.endpoint, light_3.ieee_addr);
229+
} else if (command == "freset") {
230+
Serial.println(" --> SIG Input : Factory Reset!");
231+
delay(1500);
232+
Zigbee.factoryReset();
233+
} else if (command == "open_network") {
234+
Serial.println(" --> SIG Input : Open Network");
235+
if(ZIGBEE_ROLE == ZIGBEE_COORDINATOR) {
236+
Zigbee.openNetwork(180);
237+
} else {
238+
Serial.println("Open network is only available for coordinator role");
239+
}
240+
} else {
241+
Serial.println("Unknown command");
242+
}
243+
}
244+
}
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)