Skip to content

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/README.md
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
#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");
}
}
}
6 changes: 6 additions & 0 deletions libraries/Zigbee/examples/Zigbee_On_Off_MultiSwitch/ci.json
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"
]
}
Loading
Loading