ZigbeeSwitch
About
The ZigbeeSwitch
class provides a switch endpoint for Zigbee networks. This endpoint implements the Zigbee Home Automation (HA) standard for controlling other devices (typically lights) through on/off commands.
Features: * On/off control commands for bound devices * Group control support * Direct device addressing
API Reference
Constructor
ZigbeeSwitch
Creates a new Zigbee switch endpoint.
ZigbeeSwitch(uint8_t endpoint);
endpoint
- Endpoint number (1-254)
Basic Control Commands
lightToggle
Toggles the state of bound lights (on to off, or off to on).
void lightToggle();
void lightToggle(uint16_t group_addr);
void lightToggle(uint8_t endpoint, uint16_t short_addr);
void lightToggle(uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr);
group_addr
- Group address to control (optional)endpoint
- Target device endpoint (optional)short_addr
- Target device short address (optional)ieee_addr
- Target device IEEE address (optional)
lightOn
Turns on bound lights.
void lightOn();
void lightOn(uint16_t group_addr);
void lightOn(uint8_t endpoint, uint16_t short_addr);
void lightOn(uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr);
group_addr
- Group address to control (optional)endpoint
- Target device endpoint (optional)short_addr
- Target device short address (optional)ieee_addr
- Target device IEEE address (optional)
lightOff
Turns off bound lights.
void lightOff();
void lightOff(uint16_t group_addr);
void lightOff(uint8_t endpoint, uint16_t short_addr);
void lightOff(uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr);
group_addr
- Group address to control (optional)endpoint
- Target device endpoint (optional)short_addr
- Target device short address (optional)ieee_addr
- Target device IEEE address (optional)
Advanced Control Commands
lightOffWithEffect
Turns off lights with a specific effect.
void lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant);
effect_id
- Effect identifiereffect_variant
- Effect variant
lightOnWithTimedOff
Turns on lights with automatic turn-off after specified time.
void lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off);
on_off_control
- Control bytetime_on
- Time to stay on (in 1/10th seconds)time_off
- Time to stay off (in 1/10th seconds)
Example
Basic Switch Implementation
// Copyright 2024 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 light switch.
*
* The example demonstrates how to use Zigbee library to control a light bulb.
* The light bulb is a Zigbee end device, which is controlled by a Zigbee coordinator (Switch).
* Button switch and Zigbee runs in separate tasks.
*
* 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"
/* Zigbee switch configuration */
#define SWITCH_ENDPOINT_NUMBER 5
#define GPIO_INPUT_IO_TOGGLE_SWITCH BOOT_PIN
#define PAIR_SIZE(TYPE_STR_PAIR) (sizeof(TYPE_STR_PAIR) / sizeof(TYPE_STR_PAIR[0]))
typedef enum {
SWITCH_ON_CONTROL,
SWITCH_OFF_CONTROL,
SWITCH_ONOFF_TOGGLE_CONTROL,
SWITCH_LEVEL_UP_CONTROL,
SWITCH_LEVEL_DOWN_CONTROL,
SWITCH_LEVEL_CYCLE_CONTROL,
SWITCH_COLOR_CONTROL,
} SwitchFunction;
typedef struct {
uint8_t pin;
SwitchFunction func;
} SwitchData;
typedef enum {
SWITCH_IDLE,
SWITCH_PRESS_ARMED,
SWITCH_PRESS_DETECTED,
SWITCH_PRESSED,
SWITCH_RELEASE_DETECTED,
} SwitchState;
static SwitchData buttonFunctionPair[] = {{GPIO_INPUT_IO_TOGGLE_SWITCH, SWITCH_ONOFF_TOGGLE_CONTROL}};
ZigbeeSwitch zbSwitch = ZigbeeSwitch(SWITCH_ENDPOINT_NUMBER);
/********************* Zigbee functions **************************/
static void onZbButton(SwitchData *button_func_pair) {
if (button_func_pair->func == SWITCH_ONOFF_TOGGLE_CONTROL) {
// Send toggle command to the light
Serial.println("Toggling light");
zbSwitch.lightToggle();
}
}
/********************* GPIO functions **************************/
static QueueHandle_t gpio_evt_queue = NULL;
static void IRAM_ATTR onGpioInterrupt(void *arg) {
xQueueSendFromISR(gpio_evt_queue, (SwitchData *)arg, NULL);
}
static void enableGpioInterrupt(bool enabled) {
for (int i = 0; i < PAIR_SIZE(buttonFunctionPair); ++i) {
if (enabled) {
enableInterrupt((buttonFunctionPair[i]).pin);
} else {
disableInterrupt((buttonFunctionPair[i]).pin);
}
}
}
/********************* Arduino functions **************************/
void setup() {
Serial.begin(115200);
//Optional: set Zigbee device name and model
zbSwitch.setManufacturerAndModel("Espressif", "ZigbeeSwitch");
//Optional to allow multiple light to bind to the switch
zbSwitch.allowMultipleBinding(true);
//Add endpoint to Zigbee Core
Serial.println("Adding ZigbeeSwitch endpoint to Zigbee Core");
Zigbee.addEndpoint(&zbSwitch);
//Open network for 180 seconds after boot
Zigbee.setRebootOpenNetwork(180);
// Init button switch
for (int i = 0; i < PAIR_SIZE(buttonFunctionPair); i++) {
pinMode(buttonFunctionPair[i].pin, INPUT_PULLUP);
/* create a queue to handle gpio event from isr */
gpio_evt_queue = xQueueCreate(10, sizeof(SwitchData));
if (gpio_evt_queue == 0) {
Serial.println("Queue creating failed, rebooting...");
ESP.restart();
}
attachInterruptArg(buttonFunctionPair[i].pin, onGpioInterrupt, (void *)(buttonFunctionPair + i), FALLING);
}
// When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode
if (!Zigbee.begin(ZIGBEE_COORDINATOR)) {
Serial.println("Zigbee failed to start!");
Serial.println("Rebooting...");
ESP.restart();
}
Serial.println("Waiting for Light to bound to the switch");
//Wait for switch to bound to a light:
while (!zbSwitch.bound()) {
Serial.printf(".");
delay(500);
}
// Optional: List all bound devices and read manufacturer and model name
std::list<zb_device_params_t *> boundLights = zbSwitch.getBoundDevices();
for (const auto &device : boundLights) {
Serial.printf("Device on endpoint %d, short address: 0x%x\r\n", device->endpoint, device->short_addr);
Serial.printf(
"IEEE Address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\r\n", 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]
);
char *manufacturer = zbSwitch.readManufacturer(device->endpoint, device->short_addr, device->ieee_addr);
char *model = zbSwitch.readModel(device->endpoint, device->short_addr, device->ieee_addr);
if (manufacturer != nullptr) {
Serial.printf("Light manufacturer: %s\r\n", manufacturer);
}
if (model != nullptr) {
Serial.printf("Light model: %s\r\n", model);
}
}
Serial.println();
}
void loop() {
// Handle button switch in loop()
uint8_t pin = 0;
SwitchData buttonSwitch;
static SwitchState buttonState = SWITCH_IDLE;
bool eventFlag = false;
/* check if there is any queue received, if yes read out the buttonSwitch */
if (xQueueReceive(gpio_evt_queue, &buttonSwitch, portMAX_DELAY)) {
pin = buttonSwitch.pin;
enableGpioInterrupt(false);
eventFlag = true;
}
while (eventFlag) {
bool value = digitalRead(pin);
switch (buttonState) {
case SWITCH_IDLE: buttonState = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_IDLE; break;
case SWITCH_PRESS_DETECTED: buttonState = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_RELEASE_DETECTED; break;
case SWITCH_RELEASE_DETECTED:
buttonState = SWITCH_IDLE;
/* callback to button_handler */
(*onZbButton)(&buttonSwitch);
break;
default: break;
}
if (buttonState == SWITCH_IDLE) {
enableGpioInterrupt(true);
eventFlag = false;
break;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
// print the bound lights every 10 seconds
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 10000) {
lastPrint = millis();
zbSwitch.printBoundDevices(Serial);
}
}
Multi Switch Implementation
// 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");
}
}
}