From 4c4313adfd2379a0b06b15e2f209e2c5e1990105 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:02:49 +0000 Subject: [PATCH] ci(pre-commit): Apply automatic fixes --- .../Zigbee_Color_Dimmable_Light.ino | 18 +- .../Zigbee_Color_Dimmable_Light/ci.json | 14 +- .../Zigbee_Color_Dimmer_Switch.ino | 15 +- .../Zigbee_Color_Dimmer_Switch/ci.json | 14 +- .../Zigbee_On_Off_Light.ino | 12 +- .../examples/Zigbee_On_Off_Light/ci.json | 206 ++++++- .../Zigbee_On_Off_Switch.ino | 214 +++----- .../examples/Zigbee_On_Off_Switch/ci.json | 119 ++++- .../Zigbee_Scan_Networks.ino | 143 ++--- .../examples/Zigbee_Scan_Networks/ci.json | 408 +++++++++++++- .../Zigbee_Temperature_Sensor.ino | 224 ++++---- .../Zigbee_Temperature_Sensor/ci.json | 174 +++++- .../Zigbee_Thermostat/Zigbee_Thermostat.ino | 216 ++++---- .../Zigbee/examples/Zigbee_Thermostat/ci.json | 153 +++++- libraries/Zigbee/src/ZigbeeCore.cpp | 451 +++------------- libraries/Zigbee/src/ZigbeeCore.h | 135 +---- libraries/Zigbee/src/ZigbeeEP.cpp | 502 +++++++++++++----- libraries/Zigbee/src/ZigbeeEP.h | 152 ++---- libraries/Zigbee/src/ZigbeeHandlers.cpp | 153 +----- .../src/ep/ZigbeeColorDimmableLight.cpp | 133 +---- .../Zigbee/src/ep/ZigbeeColorDimmableLight.h | 250 ++++++++- .../Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp | 426 ++------------- .../Zigbee/src/ep/ZigbeeColorDimmerSwitch.h | 149 ++++-- libraries/Zigbee/src/ep/ZigbeeLight.cpp | 68 +-- libraries/Zigbee/src/ep/ZigbeeLight.h | 220 +++++++- libraries/Zigbee/src/ep/ZigbeeSwitch.cpp | 296 +++-------- 26 files changed, 2612 insertions(+), 2253 deletions(-) diff --git a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino index 7a68b830b2f..c12ecadbd64 100644 --- a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino +++ b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino @@ -15,7 +15,7 @@ /** * @brief This example demonstrates Zigbee Color Dimmable light bulb. * - * The example demonstrates how to use Zigbee library to create an end device with + * The example demonstrates how to use Zigbee library to create an end device with * color dimmable light end point. * The light bulb is a Zigbee end device, which is controlled by a Zigbee coordinator. * @@ -23,7 +23,7 @@ * 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/) */ @@ -34,9 +34,9 @@ #include "ZigbeeCore.h" #include "ep/ZigbeeColorDimmableLight.h" -#define LED_PIN RGB_BUILTIN -#define BUTTON_PIN 9 // C6/H2 Boot button -#define ZIGBEE_LIGHT_ENDPOINT 10 +#define LED_PIN RGB_BUILTIN +#define BUTTON_PIN 9 // C6/H2 Boot button +#define ZIGBEE_LIGHT_ENDPOINT 10 ZigbeeColorDimmableLight zbColorLight = ZigbeeColorDimmableLight(ZIGBEE_LIGHT_ENDPOINT); @@ -52,7 +52,7 @@ void identify(uint16_t time) { log_d("Identify called for %d seconds", time); if (time == 0) { // If identify time is 0, stop blinking and restore light as it was used for identify - zbColorLight.restoreLight(); + zbColorLight.restoreLight(); return; } rgbLedWrite(LED_PIN, 255 * blink, 255 * blink, 255 * blink); @@ -79,7 +79,7 @@ void setup() { // Add endpoint to Zigbee Core log_d("Adding ZigbeeLight endpoint to Zigbee Core"); Zigbee.addEndpoint(&zbColorLight); - + // When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE log_d("Calling Zigbee.begin()"); Zigbee.begin(); @@ -93,7 +93,7 @@ void loop() { int startTime = millis(); while (digitalRead(BUTTON_PIN) == LOW) { delay(50); - if((millis() - startTime) > 3000) { + if ((millis() - startTime) > 3000) { // If key pressed for more than 3secs, factory reset Zigbee and reboot Serial.printf("Reseting Zigbee to factory settings, reboot.\n"); Zigbee.factoryReset(); @@ -101,4 +101,4 @@ void loop() { } } delay(100); -} \ No newline at end of file +} diff --git a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/ci.json b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/ci.json index 83af1395ee6..3aaf44eb376 100644 --- a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/ci.json +++ b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/ci.json @@ -1,16 +1,16 @@ { - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { + "fqbn": { "esp32c6": [ "espressif:esp32:esp32c6:PartitionScheme=zigbee,ZigbeeMode=ed" ], "esp32h2": [ "espressif:esp32:esp32h2:PartitionScheme=zigbee,ZigbeeMode=ed" ] + }, + "targets": { + "esp32": false, + "esp32c3": false, + "esp32s2": false, + "esp32s3": false } } diff --git a/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/Zigbee_Color_Dimmer_Switch.ino b/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/Zigbee_Color_Dimmer_Switch.ino index b8cb061a91c..fac0a92316f 100644 --- a/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/Zigbee_Color_Dimmer_Switch.ino +++ b/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/Zigbee_Color_Dimmer_Switch.ino @@ -19,7 +19,7 @@ * The RGB light bulb is a Zigbee end device, which is controlled by a Zigbee coordinator (Switch). * To turn on/off the light, push the button on the switch. * To change the color or level of the light, send serial commands to the switch. - * + * * By setting the switch to allow multiple binding, so it can bind to multiple lights. * Also every 30 seconds, all bound lights are printed to the serial console. * @@ -27,7 +27,7 @@ * 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/) */ @@ -39,7 +39,7 @@ #include "ep/ZigbeeColorDimmerSwitch.h" /* Switch configuration */ -#define SWITCH_PIN 9 // ESP32-C6/H2 Boot button +#define SWITCH_PIN 9 // ESP32-C6/H2 Boot button #define SWITCH_ENDPOINT_NUMBER 5 /* Zigbee switch */ @@ -47,7 +47,7 @@ ZigbeeColorDimmerSwitch zbSwitch = ZigbeeColorDimmerSwitch(SWITCH_ENDPOINT_NUMBE /********************* Arduino functions **************************/ void setup() { - + Serial.begin(115200); while (!Serial) { delay(10); @@ -67,14 +67,13 @@ void setup() { //Open network for 180 seconds after boot Zigbee.setRebootOpenNetwork(180); - + //When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode Zigbee.begin(ZIGBEE_COORDINATOR); - + Serial.println("Waiting for Light to bound to the switch"); //Wait for switch to bound to a light: - while(!zbSwitch.isBound()) - { + while (!zbSwitch.isBound()) { Serial.printf("."); delay(500); } diff --git a/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/ci.json b/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/ci.json index 203fce34b38..c916121b991 100644 --- a/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/ci.json +++ b/libraries/Zigbee/examples/Zigbee_Color_Dimmer_Switch/ci.json @@ -1,16 +1,16 @@ { - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { + "fqbn": { "esp32c6": [ "espressif:esp32:esp32c6:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" ], "esp32h2": [ "espressif:esp32:esp32h2:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" ] + }, + "targets": { + "esp32": false, + "esp32c3": false, + "esp32s2": false, + "esp32s3": false } } diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_Light/Zigbee_On_Off_Light.ino b/libraries/Zigbee/examples/Zigbee_On_Off_Light/Zigbee_On_Off_Light.ino index 2bbe5ba3e7b..576623e990a 100644 --- a/libraries/Zigbee/examples/Zigbee_On_Off_Light/Zigbee_On_Off_Light.ino +++ b/libraries/Zigbee/examples/Zigbee_On_Off_Light/Zigbee_On_Off_Light.ino @@ -22,7 +22,7 @@ * 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/) */ @@ -33,8 +33,8 @@ #include "ZigbeeCore.h" #include "ep/ZigbeeLight.h" -#define LED_PIN RGB_BUILTIN -#define BUTTON_PIN 9 // ESP32-C6/H2 Boot button +#define LED_PIN RGB_BUILTIN +#define BUTTON_PIN 9 // ESP32-C6/H2 Boot button #define ZIGBEE_LIGHT_ENDPOINT 10 ZigbeeLight zbLight = ZigbeeLight(ZIGBEE_LIGHT_ENDPOINT); @@ -62,7 +62,7 @@ void setup() { //Add endpoint to Zigbee Core log_d("Adding ZigbeeLight endpoint to Zigbee Core"); Zigbee.addEndpoint(&zbLight); - + // When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE log_d("Calling Zigbee.begin()"); Zigbee.begin(); @@ -76,7 +76,7 @@ void loop() { int startTime = millis(); while (digitalRead(BUTTON_PIN) == LOW) { delay(50); - if((millis() - startTime) > 3000) { + if ((millis() - startTime) > 3000) { // If key pressed for more than 3secs, factory reset Zigbee and reboot Serial.printf("Reseting Zigbee to factory settings, reboot.\n"); Zigbee.factoryReset(); @@ -84,4 +84,4 @@ void loop() { } } delay(100); -} \ No newline at end of file +} diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_Light/ci.json b/libraries/Zigbee/examples/Zigbee_On_Off_Light/ci.json index 83af1395ee6..09487dea05d 100644 --- a/libraries/Zigbee/examples/Zigbee_On_Off_Light/ci.json +++ b/libraries/Zigbee/examples/Zigbee_On_Off_Light/ci.json @@ -1,16 +1,194 @@ -{ - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { - "esp32c6": [ - "espressif:esp32:esp32c6:PartitionScheme=zigbee,ZigbeeMode=ed" - ], - "esp32h2": [ - "espressif:esp32:esp32h2:PartitionScheme=zigbee,ZigbeeMode=ed" - ] +// 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 "ZigbeeCore.h" +#include "ep/ZigbeeSwitch.h" + +#define SWITCH_ENDPOINT_NUMBER 5 + +/* Switch configuration */ +#define GPIO_INPUT_IO_TOGGLE_SWITCH 9 +#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 + 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); + while (!Serial) { + delay(10); + } + + //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 + log_d("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) { + log_e("Queue was not created and must not be used"); + while (1); + } + attachInterruptArg(buttonFunctionPair[i].pin, onGpioInterrupt, (void *)(buttonFunctionPair + i), FALLING); + } + + // When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode + log_d("Calling Zigbee.begin()"); + Zigbee.begin(ZIGBEE_COORDINATOR); + + Serial.println("Waiting for Light to bound to the switch"); + //Wait for switch to bound to a light: + while (!zbSwitch.isBound()) { + Serial.printf("."); + delay(500); + } + + // Optional: read manufacturer and model name from the bound light + std::list boundLights = zbSwitch.getBoundDevices(); + //List all bound lights + for (const auto &device : boundLights) { + Serial.printf("Device on endpoint %d, short address: 0x%x\n", device->endpoint, device->short_addr); + Serial.printf( + "IEEE Address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", device->ieee_addr[0], device->ieee_addr[1], device->ieee_addr[2], device->ieee_addr[3], + device->ieee_addr[4], device->ieee_addr[5], device->ieee_addr[6], device->ieee_addr[7] + ); + Serial.printf("Light manufacturer: %s", zbSwitch.readManufacturer(device->endpoint, device->short_addr)); + Serial.printf("Light model: %s", zbSwitch.readModel(device->endpoint, device->short_addr)); + } + + 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(); } } diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_Switch/Zigbee_On_Off_Switch.ino b/libraries/Zigbee/examples/Zigbee_On_Off_Switch/Zigbee_On_Off_Switch.ino index f9f7c9a711d..a72ade201ae 100644 --- a/libraries/Zigbee/examples/Zigbee_On_Off_Switch/Zigbee_On_Off_Switch.ino +++ b/libraries/Zigbee/examples/Zigbee_On_Off_Switch/Zigbee_On_Off_Switch.ino @@ -3,7 +3,7 @@ // 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 @@ -13,181 +13,101 @@ // limitations under the License. /** - * @brief This example demonstrates simple Zigbee light switch. + * @brief This example demonstrates Zigbee Network Scanning. * - * 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. + * The example demonstrates how to use ESP Zigbee stack to scan for Zigbee networks. * - * Proper Zigbee mode must be selected in Tools->Zigbee mode - * and also the correct partition scheme must be selected in Tools->Partition Scheme. + * Any Zigbee mode can be selected in Tools->Zigbee mode + * with proper Zigbee partition scheme 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" +#if !defined(ZIGBEE_MODE_ED) && !defined(ZIGBEE_MODE_ZCZR) +#error "Zigbee device mode is not selected in Tools->Zigbee mode" #endif #include "ZigbeeCore.h" -#include "ep/ZigbeeSwitch.h" - -#define SWITCH_ENDPOINT_NUMBER 5 - -/* Switch configuration */ -#define GPIO_INPUT_IO_TOGGLE_SWITCH 9 -#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 - 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); -} +#ifdef ZIGBEE_MODE_ZCZR +zigbee_role_t role = ZIGBEE_ROUTER; // or can be ZIGBEE_COORDINATOR, but it wont scan itself +#else +zigbee_role_t role = ZIGBEE_END_DEVICE; +#endif -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); +void printScannedNetworks(uint16_t networksFound) { + if (networksFound == 0) { + Serial.println("No networks found"); + } else { + zigbee_scan_result_t *scan_result = Zigbee.getScanResult(); + Serial.println("\nScan done"); + Serial.print(networksFound); + Serial.println(" networks found:"); + Serial.println("Nr | PAN ID | CH | Permit Joining | Router Capacity | End Device Capacity | Extended PAN ID"); + for (int i = 0; i < networksFound; ++i) { + // Print all available info for each network found + Serial.printf("%2d", i + 1); + Serial.print(" | "); + Serial.printf("0x%04hx", scan_result[i].short_pan_id); + Serial.print(" | "); + Serial.printf("%2d", scan_result[i].logic_channel); + Serial.print(" | "); + Serial.printf("%-14.14s", scan_result[i].permit_joining ? "Yes" : "No"); + Serial.print(" | "); + Serial.printf("%-15.15s", scan_result[i].router_capacity ? "Yes" : "No"); + Serial.print(" | "); + Serial.printf("%-19.19s", scan_result[i].end_device_capacity ? "Yes" : "No"); + Serial.print(" | "); + Serial.printf( + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", scan_result[i].extended_pan_id[7], scan_result[i].extended_pan_id[6], scan_result[i].extended_pan_id[5], + scan_result[i].extended_pan_id[4], scan_result[i].extended_pan_id[3], scan_result[i].extended_pan_id[2], scan_result[i].extended_pan_id[1], + scan_result[i].extended_pan_id[0] + ); + Serial.println(); + delay(10); } + Serial.println(""); + // Delete the scan result to free memory for code below. + Zigbee.scanDelete(); } } -/********************* Arduino functions **************************/ void setup() { - Serial.begin(115200); while (!Serial) { delay(10); } - //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 - log_d("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) { - log_e("Queue was not created and must not be used"); - while (1); - } - attachInterruptArg(buttonFunctionPair[i].pin, onGpioInterrupt, (void *)(buttonFunctionPair + i), FALLING); - } + // Initialize Zigbee stack without any EPs just for scanning + Zigbee.begin(role); - // When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode - log_d("Calling Zigbee.begin()"); - Zigbee.begin(ZIGBEE_COORDINATOR); - - Serial.println("Waiting for Light to bound to the switch"); - //Wait for switch to bound to a light: - while(!zbSwitch.isBound()) - { - Serial.printf("."); - delay(500); + // Waint until Zigbee stack is ready + while (!Zigbee.isStarted()) { + delay(100); } - // Optional: read manufacturer and model name from the bound light - std::list boundLights = zbSwitch.getBoundDevices(); - //List all bound lights - for (const auto& device : boundLights) { - Serial.printf("Device on endpoint %d, short address: 0x%x\n", device->endpoint, device->short_addr); - Serial.printf("IEEE Address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", device->ieee_addr[0], device->ieee_addr[1], device->ieee_addr[2], device->ieee_addr[3], device->ieee_addr[4], device->ieee_addr[5], device->ieee_addr[6], device->ieee_addr[7]); - Serial.printf("Light manufacturer: %s", zbSwitch.readManufacturer(device->endpoint, device->short_addr)); - Serial.printf("Light model: %s", zbSwitch.readModel(device->endpoint, device->short_addr)); - } - - Serial.println(); + Serial.println("Setup done"); + // Start Zigbee Network Scan with default parameters (all channels, scan time 5) + Zigbee.scanNetworks(); } 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; + // check Zigbee Network Scan process + int16_t ZigbeeScanStatus = Zigbee.scanComplete(); + if (ZigbeeScanStatus < 0) { // it is busy scanning or got an error + if (ZigbeeScanStatus == ZB_SCAN_FAILED) { + Serial.println("WiFi Scan has failed. Starting again."); + Zigbee.scanNetworks(); } - vTaskDelay(10 / portTICK_PERIOD_MS); + // other option is status ZB_SCAN_RUNNING - just wait. + } else { // Found Zero or more Wireless Networks + printScannedNetworks(ZigbeeScanStatus); + Zigbee.scanNetworks(); // start over... } - // print the bound lights every 10 seconds - static uint32_t lastPrint = 0; - if (millis() - lastPrint > 10000) { - lastPrint = millis(); - zbSwitch.printBoundDevices(); - } + // Loop can do something else... + delay(500); + Serial.println("Loop running..."); } diff --git a/libraries/Zigbee/examples/Zigbee_On_Off_Switch/ci.json b/libraries/Zigbee/examples/Zigbee_On_Off_Switch/ci.json index 203fce34b38..bfb578663db 100644 --- a/libraries/Zigbee/examples/Zigbee_On_Off_Switch/ci.json +++ b/libraries/Zigbee/examples/Zigbee_On_Off_Switch/ci.json @@ -1,16 +1,107 @@ -{ - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { - "esp32c6": [ - "espressif:esp32:esp32c6:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" - ], - "esp32h2": [ - "espressif:esp32:esp32h2:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" - ] +// 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 Zigbee temperature sensor. + * + * The example demonstrates how to use Zigbee library to create a end device temperature sensor. + * The temperature sensor is a Zigbee end device, which is controlled by a Zigbee coordinator. + * + * 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_ED +#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode" +#endif + +#include "ZigbeeCore.h" +#include "ep/ZigbeeTempSensor.h" + +#define BUTTON_PIN 9 //Boot button for C6/H2 +#define TEMP_SENSOR_ENDPOINT_NUMBER 10 + +ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER); + +/************************ Temp sensor *****************************/ +static void temp_sensor_value_update(void *arg) { + for (;;) { + // Read temperature sensor value + float tsens_value = temperatureRead(); + log_v("Temperature sensor value: %.2f°C", tsens_value); + // Update temperature value in Temperature sensor EP + zbTempSensor.setTemperature(tsens_value); + delay(1000); } } + +/********************* Arduino functions **************************/ +void setup() { + + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + // Init button switch + pinMode(BUTTON_PIN, INPUT); + + // Optional: set Zigbee device name and model + zbTempSensor.setManufacturerAndModel("Espressif", "ZigbeeTempSensor"); + + // Set minimum and maximum temperature measurement value (10-50°C is default range for chip temperature measurement) + zbTempSensor.setMinMaxValue(10, 50); + + // Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C) + zbTempSensor.setTolerance(1); + + // Add endpoint to Zigbee Core + Zigbee.addEndpoint(&zbTempSensor); + + // When all EPs are registered, start Zigbee in End Device mode + Zigbee.begin(); + + // Start Temperature sensor reading task + xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL); + + // Set reporting interval for temperature measurement in seconds, must be called after Zigbee.begin() + // min_interval and max_interval in seconds, delta (temp change in °C) + // if min = 1 and max = 0, reporting is sent only when temperature changes by delta + // if min = 0 and max = 10, reporting is sent every 10 secods or temperature changes by delta + // if min = 0, max = 10 and delta = 0, reporting is sent every 10 seconds regardless of temperature change + zbTempSensor.setReporting(1, 0, 1); +} + +void loop() { + // Cheking button for factory reset + if (digitalRead(BUTTON_PIN) == LOW) { // Push button pressed + // Key debounce handling + delay(100); + int startTime = millis(); + while (digitalRead(BUTTON_PIN) == LOW) { + delay(50); + if ((millis() - startTime) > 3000) { + // If key pressed for more than 3secs, factory reset Zigbee and reboot + Serial.printf("Reseting Zigbee to factory settings, reboot.\n"); + Zigbee.factoryReset(); + } + } + zbTempSensor.reportTemperature(); + } + delay(100); +} diff --git a/libraries/Zigbee/examples/Zigbee_Scan_Networks/Zigbee_Scan_Networks.ino b/libraries/Zigbee/examples/Zigbee_Scan_Networks/Zigbee_Scan_Networks.ino index 3d67d74c8fe..0fef83f0ad7 100644 --- a/libraries/Zigbee/examples/Zigbee_Scan_Networks/Zigbee_Scan_Networks.ino +++ b/libraries/Zigbee/examples/Zigbee_Scan_Networks/Zigbee_Scan_Networks.ino @@ -3,7 +3,7 @@ // 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 @@ -13,100 +13,107 @@ // limitations under the License. /** - * @brief This example demonstrates Zigbee Network Scanning. + * @brief This example demonstrates simple Zigbee thermostat. * - * The example demonstrates how to use ESP Zigbee stack to scan for Zigbee networks. + * The example demonstrates how to use Zigbee library to get data from temperature + * sensor end device and act as an thermostat. + * The temperature sensor is a Zigbee end device, which is controlled by a Zigbee coordinator (thermostat). * - * Any Zigbee mode can be selected in Tools->Zigbee mode - * with proper Zigbee partition scheme in Tools->Partition Scheme. + * 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/) */ -#if !defined(ZIGBEE_MODE_ED) && !defined(ZIGBEE_MODE_ZCZR) -#error "Zigbee device mode is not selected in Tools->Zigbee mode" +#ifndef ZIGBEE_MODE_ZCZR +#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode" #endif #include "ZigbeeCore.h" +#include "ep/ZigbeeThermostat.h" -#ifdef ZIGBEE_MODE_ZCZR -zigbee_role_t role = ZIGBEE_ROUTER; // or can be ZIGBEE_COORDINATOR, but it wont scan itself -#else -zigbee_role_t role = ZIGBEE_END_DEVICE; -#endif +#define BUTTON_PIN 9 // Boot button for C6/H2 +#define THERMOSTAT_ENDPOINT_NUMBER 5 -void printScannedNetworks(uint16_t networksFound) { - if (networksFound == 0) { - Serial.println("No networks found"); - } else { - zigbee_scan_result_t *scan_result = Zigbee.getScanResult(); - Serial.println("\nScan done"); - Serial.print(networksFound); - Serial.println(" networks found:"); - Serial.println("Nr | PAN ID | CH | Permit Joining | Router Capacity | End Device Capacity | Extended PAN ID"); - for (int i = 0; i < networksFound; ++i) { - // Print all available info for each network found - Serial.printf("%2d", i + 1); - Serial.print(" | "); - Serial.printf("0x%04hx", scan_result[i].short_pan_id); - Serial.print(" | "); - Serial.printf("%2d", scan_result[i].logic_channel); - Serial.print(" | "); - Serial.printf("%-14.14s", scan_result[i].permit_joining ? "Yes" : "No"); - Serial.print(" | "); - Serial.printf("%-15.15s", scan_result[i].router_capacity ? "Yes" : "No"); - Serial.print(" | "); - Serial.printf("%-19.19s", scan_result[i].end_device_capacity ? "Yes" : "No"); - Serial.print(" | "); - Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", scan_result[i].extended_pan_id[7], scan_result[i].extended_pan_id[6], - scan_result[i].extended_pan_id[5], scan_result[i].extended_pan_id[4], - scan_result[i].extended_pan_id[3], scan_result[i].extended_pan_id[2], - scan_result[i].extended_pan_id[1], scan_result[i].extended_pan_id[0]); - Serial.println(); - delay(10); - } - Serial.println(""); - // Delete the scan result to free memory for code below. - Zigbee.scanDelete(); - } +ZigbeeThermostat zbThermostat = ZigbeeThermostat(THERMOSTAT_ENDPOINT_NUMBER); + +// Save temperature sensor data +float sensor_temp; +float sensor_max_temp; +float sensor_min_temp; +float sensor_tolerance; + +/****************** Temperature sensor handling *******************/ +void recieveSensorTemp(float temperature) { + Serial.printf("Temperature sensor value: %.2f°C\n", temperature); + sensor_temp = temperature; } +void recieveSensorConfig(float min_temp, float max_temp, float tolerance) { + Serial.printf("Temperature sensor settings: min %.2f°C, max %.2f°C, tolerance %.2f°C\n", min_temp, max_temp, tolerance); + sensor_min_temp = min_temp; + sensor_max_temp = max_temp; + sensor_tolerance = tolerance; +} +/********************* Arduino functions **************************/ void setup() { Serial.begin(115200); while (!Serial) { delay(10); } - // Initialize Zigbee stack without any EPs just for scanning - Zigbee.begin(role); + // Init button switch + pinMode(BUTTON_PIN, INPUT); - // Waint until Zigbee stack is ready - while (!Zigbee.isStarted()) { - delay(100); + // Set callback functions for temperature and configuration recieve + zbThermostat.onTempRecieve(recieveSensorTemp); + zbThermostat.onConfigRecieve(recieveSensorConfig); + + //Optional: set Zigbee device name and model + zbThermostat.setManufacturerAndModel("Espressif", "ZigbeeThermostat"); + + //Add endpoint to Zigbee Core + Zigbee.addEndpoint(&zbThermostat); + + //Open network for 180 seconds after boot + Zigbee.setRebootOpenNetwork(180); + + // When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode + Zigbee.begin(ZIGBEE_COORDINATOR); + + Serial.println("Waiting for Temperature sensor to bound to the switch"); + + //Wait for switch to bound to a light: + while (!zbThermostat.isBound()) { + Serial.printf("."); + delay(500); } - Serial.println("Setup done"); - // Start Zigbee Network Scan with default parameters (all channels, scan time 5) - Zigbee.scanNetworks(); + // Get temperature sensor configuration + zbThermostat.getSensorSettings(); + Serial.println(); } void loop() { - // check Zigbee Network Scan process - int16_t ZigbeeScanStatus = Zigbee.scanComplete(); - if (ZigbeeScanStatus < 0) { // it is busy scanning or got an error - if (ZigbeeScanStatus == ZB_SCAN_FAILED) { - Serial.println("WiFi Scan has failed. Starting again."); - Zigbee.scanNetworks(); + // Handle button switch in loop() + if (digitalRead(BUTTON_PIN) == LOW) { // Push button pressed + + // Key debounce handling + while (digitalRead(BUTTON_PIN) == LOW) { + delay(50); } - // other option is status ZB_SCAN_RUNNING - just wait. - } else { // Found Zero or more Wireless Networks - printScannedNetworks(ZigbeeScanStatus); - Zigbee.scanNetworks(); // start over... + + // Set reporting interval for temperature sensor + zbThermostat.setTemperatureReporting(0, 10, 2); } - // Loop can do something else... - delay(500); - Serial.println("Loop running..."); + // Print temperature sensor data each 10 seconds + static uint32_t last_print = 0; + if (millis() - last_print > 10000) { + last_print = millis(); + int temp_percent = (int)((sensor_temp - sensor_min_temp) / (sensor_max_temp - sensor_min_temp) * 100); + Serial.printf("Loop temperature info: %.2f°C (%d %%)\n", sensor_temp, temp_percent); + } } diff --git a/libraries/Zigbee/examples/Zigbee_Scan_Networks/ci.json b/libraries/Zigbee/examples/Zigbee_Scan_Networks/ci.json index 83af1395ee6..2b089cf03ec 100644 --- a/libraries/Zigbee/examples/Zigbee_Scan_Networks/ci.json +++ b/libraries/Zigbee/examples/Zigbee_Scan_Networks/ci.json @@ -1,16 +1,396 @@ -{ - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { - "esp32c6": [ - "espressif:esp32:esp32c6:PartitionScheme=zigbee,ZigbeeMode=ed" - ], - "esp32h2": [ - "espressif:esp32:esp32h2:PartitionScheme=zigbee,ZigbeeMode=ed" - ] +/* Zigbee Core Functions */ + +#include "ZigbeeCore.h" +#if SOC_IEEE802154_SUPPORTED + +#include "ZigbeeHandlers.cpp" +#include "Arduino.h" + +ZigbeeCore::ZigbeeCore() { + _radio_config.radio_mode = ZB_RADIO_MODE_NATIVE; // Use the native 15.4 radio + _host_config.host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE; // Disable host connection + _zb_ep_list = esp_zb_ep_list_create(); + _primary_channel_mask = ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK; + _open_network = 0; + _scan_status = ZB_SCAN_FAILED; + _started = false; +} +ZigbeeCore::~ZigbeeCore() {} + +//forward declaration +static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message); + +bool ZigbeeCore::begin(esp_zb_cfg_t *role_cfg, bool erase_nvs) { + if (!zigbeeInit(role_cfg, erase_nvs)) { + return false; + } + _role = (zigbee_role_t)role_cfg->esp_zb_role; + return true; +} + +bool ZigbeeCore::begin(zigbee_role_t role, bool erase_nvs) { + bool status = true; + switch (role) { + case ZIGBEE_COORDINATOR: + { + _role = ZIGBEE_COORDINATOR; + esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_COORDINATOR_CONFIG(); + status = zigbeeInit(&zb_nwk_cfg, erase_nvs); + break; + } + case ZIGBEE_ROUTER: + { + _role = ZIGBEE_ROUTER; + esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_ROUTER_CONFIG(); + status = zigbeeInit(&zb_nwk_cfg, erase_nvs); + break; + } + case ZIGBEE_END_DEVICE: + { + _role = ZIGBEE_END_DEVICE; + esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_ED_CONFIG(); + status = zigbeeInit(&zb_nwk_cfg, erase_nvs); + break; + } + default: log_e("Invalid Zigbee Role"); return false; + } + return status; +} + +void ZigbeeCore::addEndpoint(ZigbeeEP *ep) { + ep_objects.push_back(ep); + + log_d("Endpoint: %d, Device ID: 0x%04x", ep->_endpoint, ep->_device_id); + //Register clusters and ep_list to the ZigbeeCore class's ep_list + if (ep->_ep_config.endpoint == 0 || ep->_cluster_list == nullptr) { + log_e("Endpoint config or Cluster list is not initialized, EP not added to ZigbeeCore's EP list"); + return; + } + + esp_zb_ep_list_add_ep(_zb_ep_list, ep->_cluster_list, ep->_ep_config); +} + +static void esp_zb_task(void *pvParameters) { + /* initialize Zigbee stack */ + ESP_ERROR_CHECK(esp_zb_start(false)); + esp_zb_stack_main_loop(); +} + +// Zigbee core init function +bool ZigbeeCore::zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs) { + // Zigbee platform configuration + esp_zb_platform_config_t platform_config = { + .radio_config = _radio_config, + .host_config = _host_config, + }; + + esp_err_t err = esp_zb_platform_config(&platform_config); + if (err != ESP_OK) { + log_e("Failed to configure Zigbee platform"); + return false; + } + + // Initialize Zigbee stack + log_d("Initialize Zigbee stack"); + esp_zb_init(zb_cfg); + + // Register all Zigbee EPs in list + if (ep_objects.empty()) { + log_w("No Zigbee EPs to register"); + } else { + log_d("Register all Zigbee EPs in list"); + err = esp_zb_device_register(_zb_ep_list); + if (err != ESP_OK) { + log_e("Failed to register Zigbee EPs"); + return false; + } + + //print the list of Zigbee EPs from ep_objects + log_i("List of registered Zigbee EPs:"); + for (std::list::iterator it = ep_objects.begin(); it != ep_objects.end(); ++it) { + log_i("Device type: %s, Endpoint: %d, Device ID: 0x%04x", getDeviceTypeString((*it)->_device_id), (*it)->_endpoint, (*it)->_device_id); + } + } + // Register Zigbee action handler + esp_zb_core_action_handler_register(zb_action_handler); + err = esp_zb_set_primary_network_channel_set(_primary_channel_mask); + if (err != ESP_OK) { + log_e("Failed to set primary network channel mask"); + return false; + } + + //Erase NVRAM before creating connection to new Coordinator + if (erase_nvs) { + esp_zb_nvram_erase_at_start(true); + } + + // Create Zigbee task and start Zigbee stack + xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL); + + return true; +} + +void ZigbeeCore::setRadioConfig(esp_zb_radio_config_t config) { + _radio_config = config; +} + +esp_zb_radio_config_t ZigbeeCore::getRadioConfig() { + return _radio_config; +} + +void ZigbeeCore::setHostConfig(esp_zb_host_config_t config) { + _host_config = config; +} + +esp_zb_host_config_t ZigbeeCore::getHostConfig() { + return _host_config; +} + +void ZigbeeCore::setPrimaryChannelMask(uint32_t mask) { + _primary_channel_mask = mask; +} + +void ZigbeeCore::setRebootOpenNetwork(uint8_t time) { + _open_network = time; +} + +void ZigbeeCore::openNetwork(uint8_t time) { + if (_started) { + log_v("Openning network for joining for %d seconds", time); + esp_zb_bdb_open_network(time); + } +} + +static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { + ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask)); +} + +void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { + //common variables + uint32_t *p_sg_p = signal_struct->p_app_signal; + esp_err_t err_status = signal_struct->esp_err_status; + esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p; + //coordinator variables + esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL; + + //main switch + switch (sig_type) { + case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: // Common + log_i("Zigbee stack initialized"); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION); + break; + case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: // Common + case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: // Common + if (err_status == ESP_OK) { + log_i("Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non"); + if (esp_zb_bdb_is_factory_new()) { + // Role specific code + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { + log_i("Start network formation"); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION); + } else { + log_i("Start network steering"); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); + } + //----------------- + + } else { + log_i("Device rebooted"); + Zigbee._started = true; + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR && Zigbee._open_network > 0) { + log_i("Openning network for joining for %d seconds", Zigbee._open_network); + esp_zb_bdb_open_network(Zigbee._open_network); + } + } + } else { + /* commissioning failed */ + log_e("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status)); + } + break; + case ESP_ZB_BDB_SIGNAL_FORMATION: // Coordinator + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { + if (err_status == ESP_OK) { + esp_zb_ieee_addr_t extended_pan_id; + esp_zb_get_extended_pan_id(extended_pan_id); + log_i( + "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", + extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], + extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address() + ); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); + } else { + log_i("Restart network formation (status: %s)", esp_err_to_name(err_status)); + esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000); + } + } + break; + case ESP_ZB_BDB_SIGNAL_STEERING: // Router and End Device + Zigbee._started = true; + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { + if (err_status == ESP_OK) { + log_i("Network steering started"); + } + } else { + if (err_status == ESP_OK) { + esp_zb_ieee_addr_t extended_pan_id; + esp_zb_get_extended_pan_id(extended_pan_id); + log_i( + "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", + extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], + extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address() + ); + } else { + log_i("Network steering was not successful (status: %s)", esp_err_to_name(err_status)); + esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000); + } + } + break; + case ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE: // Coordinator + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { + dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p); + log_i("New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr); + esp_zb_zdo_match_desc_req_param_t cmd_req; + cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; + cmd_req.addr_of_interest = dev_annce_params->device_short_addr; + log_v("Device capabilities: 0x%02x", dev_annce_params->capability); + /* + capability: + Bit 0 – Alternate PAN Coordinator + Bit 1 – Device type: 1- ZigBee Router; 0 – End Device + Bit 2 – Power Source: 1 Main powered + Bit 3 – Receiver on when Idle + Bit 4 – Reserved + Bit 5 – Reserved + Bit 6 – Security capability + Bit 7 – Reserved + */ + + // for each endpoint in the list call the findEndpoint function if not bounded or allowed to bind multiple devices + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (!(*it)->isBound() || (*it)->epAllowMultipleBinding()) { + (*it)->findEndpoint(&cmd_req); + } + } + } + break; + case ESP_ZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: // Coordinator + if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { + if (err_status == ESP_OK) { + if (*(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)) { + log_i("Network(0x%04hx) is open for %d seconds", esp_zb_get_pan_id(), *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); + } else { + log_i("Network(0x%04hx) closed, devices joining not allowed.", esp_zb_get_pan_id()); + } + } + } + break; + default: log_v("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break; + } +} + +void ZigbeeCore::factoryReset() { + log_v("Factory reseting Zigbee stack, device will reboot"); + esp_zb_factory_reset(); +} + +void ZigbeeCore::scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor) { + log_v("Zigbee network scan complete"); + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_v("Found %d networks", count); + //print Zigbee networks + for (int i = 0; i < count; i++) { + log_v( + "Network %d: PAN ID: 0x%04hx, Permit Joining: %s, Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, Channel: %d, Router Capacity: %s, End " + "Device Capacity: %s", + i, nwk_descriptor[i].short_pan_id, nwk_descriptor[i].permit_joining ? "Yes" : "No", nwk_descriptor[i].extended_pan_id[7], + nwk_descriptor[i].extended_pan_id[6], nwk_descriptor[i].extended_pan_id[5], nwk_descriptor[i].extended_pan_id[4], nwk_descriptor[i].extended_pan_id[3], + nwk_descriptor[i].extended_pan_id[2], nwk_descriptor[i].extended_pan_id[1], nwk_descriptor[i].extended_pan_id[0], nwk_descriptor[i].logic_channel, + nwk_descriptor[i].router_capacity ? "Yes" : "No", nwk_descriptor[i].end_device_capacity ? "Yes" : "No" + ); + } + //save scan result and update scan status + //copy network descriptor to _scan_result to keep the data after the callback + Zigbee._scan_result = (esp_zb_network_descriptor_t *)malloc(count * sizeof(esp_zb_network_descriptor_t)); + memcpy(Zigbee._scan_result, nwk_descriptor, count * sizeof(esp_zb_network_descriptor_t)); + Zigbee._scan_status = count; + } else { + log_e("Failed to scan Zigbee network (status: 0x%x)", zdo_status); + Zigbee._scan_status = ZB_SCAN_FAILED; + Zigbee._scan_result = nullptr; + } +} + +void ZigbeeCore::scanNetworks(u_int32_t channel_mask, u_int8_t scan_duration) { + if (!_started) { + log_e("Zigbee stack is not started, cannot scan networks"); + return; + } + log_v("Scanning Zigbee networks"); + esp_zb_zdo_active_scan_request(channel_mask, scan_duration, scanCompleteCallback); + _scan_status = ZB_SCAN_RUNNING; +} + +int16_t ZigbeeCore::scanComplete() { + return _scan_status; +} + +zigbee_scan_result_t *ZigbeeCore::getScanResult() { + return _scan_result; +} + +void ZigbeeCore::scanDelete() { + if (_scan_result != nullptr) { + free(_scan_result); + _scan_result = nullptr; + } + _scan_status = ZB_SCAN_FAILED; +} + +// Function to convert enum value to string +const char *ZigbeeCore::getDeviceTypeString(esp_zb_ha_standard_devices_t deviceId) { + switch (deviceId) { + case ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID: return "General On/Off switch"; + case ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID: return "Level Control Switch"; + case ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID: return "General On/Off output"; + case ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID: return "Level Controllable Output"; + case ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID: return "Scene Selector"; + case ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID: return "Configuration Tool"; + case ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID: return "Remote Control"; + case ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID: return "Combined Interface"; + case ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID: return "Range Extender"; + case ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID: return "Mains Power Outlet"; + case ESP_ZB_HA_DOOR_LOCK_DEVICE_ID: return "Door lock client"; + case ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID: return "Door lock controller"; + case ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID: return "Simple Sensor device"; + case ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID: return "Consumption Awareness Device"; + case ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID: return "Home Gateway"; + case ESP_ZB_HA_SMART_PLUG_DEVICE_ID: return "Smart plug"; + case ESP_ZB_HA_WHITE_GOODS_DEVICE_ID: return "White Goods"; + case ESP_ZB_HA_METER_INTERFACE_DEVICE_ID: return "Meter Interface"; + case ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID: return "On/Off Light Device"; + case ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID: return "Dimmable Light Device"; + case ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID: return "Color Dimmable Light Device"; + case ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID: return "Dimmer Switch Device"; + case ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID: return "Color Dimmer Switch Device"; + case ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID: return "Light Sensor"; + case ESP_ZB_HA_SHADE_DEVICE_ID: return "Shade"; + case ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID: return "Shade controller"; + case ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID: return "Window Covering client"; + case ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID: return "Window Covering controller"; + case ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID: return "Heating/Cooling Unit device"; + case ESP_ZB_HA_THERMOSTAT_DEVICE_ID: return "Thermostat Device"; + case ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID: return "Temperature Sensor"; + case ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID: return "IAS Control and Indicating Equipment"; + case ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID: return "IAS Ancillary Control Equipment"; + case ESP_ZB_HA_IAS_ZONE_ID: return "IAS Zone"; + case ESP_ZB_HA_IAS_WARNING_DEVICE_ID: return "IAS Warning Device"; + case ESP_ZB_HA_TEST_DEVICE_ID: return "Custom HA device for test"; + case ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID: return "Custom Tunnel device"; + case ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID: return "Custom Attributes Device"; + default: return "Unknown device type"; } } + +ZigbeeCore Zigbee = ZigbeeCore(); + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino b/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino index 7a25319c983..1044a9c737c 100644 --- a/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino +++ b/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino @@ -1,107 +1,125 @@ -// 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 Zigbee temperature sensor. - * - * The example demonstrates how to use Zigbee library to create a end device temperature sensor. - * The temperature sensor is a Zigbee end device, which is controlled by a Zigbee coordinator. - * - * 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_ED -#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode" -#endif - -#include "ZigbeeCore.h" -#include "ep/ZigbeeTempSensor.h" - -#define BUTTON_PIN 9 //Boot button for C6/H2 -#define TEMP_SENSOR_ENDPOINT_NUMBER 10 - -ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER); - -/************************ Temp sensor *****************************/ -static void temp_sensor_value_update(void *arg) { - for (;;) { - // Read temperature sensor value - float tsens_value = temperatureRead(); - log_v("Temperature sensor value: %.2f°C", tsens_value); - // Update temperature value in Temperature sensor EP - zbTempSensor.setTemperature(tsens_value); - delay(1000); +/* Zigbee core class */ + +#pragma once + +#include "soc/soc_caps.h" +#if SOC_IEEE802154_SUPPORTED + +#include "esp_zigbee_core.h" +#include "zdo/esp_zigbee_zdo_common.h" +#include +#include +#include "ZigbeeEP.h" +class ZigbeeEP; + +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrArg)(void *); + +typedef esp_zb_network_descriptor_t zigbee_scan_result_t; + +// enum of Zigbee Roles +typedef enum { + ZIGBEE_COORDINATOR = 0, + ZIGBEE_ROUTER = 1, + ZIGBEE_END_DEVICE = 2 +} zigbee_role_t; + +#define ZB_SCAN_RUNNING (-1) +#define ZB_SCAN_FAILED (-2) + +#define ZIGBEE_DEFAULT_ED_CONFIG() \ + { \ + .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED, .install_code_policy = false, \ + .nwk_cfg = { \ + .zed_cfg = \ + { \ + .ed_timeout = ESP_ZB_ED_AGING_TIMEOUT_64MIN, \ + .keep_alive = 3000, \ + }, \ + }, \ } -} - -/********************* Arduino functions **************************/ -void setup() { - - Serial.begin(115200); - while (!Serial) { - delay(10); + +#define ZIGBEE_DEFAULT_ROUTER_CONFIG() \ + { \ + .esp_zb_role = ESP_ZB_DEVICE_TYPE_ROUTER, .install_code_policy = false, .nwk_cfg = { \ + .zczr_cfg = \ + { \ + .max_children = 10, \ + }, \ + } \ + } + +#define ZIGBEE_DEFAULT_COORDINATOR_CONFIG() \ + { \ + .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, .install_code_policy = false, .nwk_cfg = { \ + .zczr_cfg = \ + { \ + .max_children = 10, \ + }, \ + } \ } - // Init button switch - pinMode(BUTTON_PIN, INPUT); - - // Optional: set Zigbee device name and model - zbTempSensor.setManufacturerAndModel("Espressif", "ZigbeeTempSensor"); - - // Set minimum and maximum temperature measurement value (10-50°C is default range for chip temperature measurement) - zbTempSensor.setMinMaxValue(10,50); - - // Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C) - zbTempSensor.setTolerance(1); - - // Add endpoint to Zigbee Core - Zigbee.addEndpoint(&zbTempSensor); - - // When all EPs are registered, start Zigbee in End Device mode - Zigbee.begin(); - - // Start Temperature sensor reading task - xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL); - - // Set reporting interval for temperature measurement in seconds, must be called after Zigbee.begin() - // min_interval and max_interval in seconds, delta (temp change in °C) - // if min = 1 and max = 0, reporting is sent only when temperature changes by delta - // if min = 0 and max = 10, reporting is sent every 10 secods or temperature changes by delta - // if min = 0, max = 10 and delta = 0, reporting is sent every 10 seconds regardless of temperature change - zbTempSensor.setReporting(1, 0, 1); -} - -void loop() { - // Cheking button for factory reset - if (digitalRead(BUTTON_PIN) == LOW) { // Push button pressed - // Key debounce handling - delay(100); - int startTime = millis(); - while (digitalRead(BUTTON_PIN) == LOW) { - delay(50); - if((millis() - startTime) > 3000) { - // If key pressed for more than 3secs, factory reset Zigbee and reboot - Serial.printf("Reseting Zigbee to factory settings, reboot.\n"); - Zigbee.factoryReset(); - } - } - zbTempSensor.reportTemperature(); +class ZigbeeCore { +private: + esp_zb_radio_config_t _radio_config; + esp_zb_host_config_t _host_config; + uint32_t _primary_channel_mask; + int16_t _scan_status; + + esp_zb_ep_list_t *_zb_ep_list; + zigbee_role_t _role; + bool _started; + + uint8_t _open_network; + zigbee_scan_result_t *_scan_result; + + bool zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs); + static void scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor); + const char *getDeviceTypeString(esp_zb_ha_standard_devices_t deviceId); + +public: + ZigbeeCore(); + ~ZigbeeCore(); + + std::list ep_objects; + + bool begin(zigbee_role_t role = ZIGBEE_END_DEVICE, bool erase_nvs = false); + bool begin(esp_zb_cfg_t *role_cfg, bool erase_nvs = false); + // bool end(); + + bool isStarted() { + return _started; } - delay(100); -} + zigbee_role_t getRole() { + return _role; + } + + void addEndpoint(ZigbeeEP *ep); + //void removeEndpoint(ZigbeeEP *ep); + + void setRadioConfig(esp_zb_radio_config_t config); + esp_zb_radio_config_t getRadioConfig(); + + void setHostConfig(esp_zb_host_config_t config); + esp_zb_host_config_t getHostConfig(); + + void setPrimaryChannelMask(uint32_t mask); + void setRebootOpenNetwork(uint8_t time); + void openNetwork(uint8_t time); + + //scan_duration Time spent scanning each channel, in units of ((1 << scan_duration) + 1) * a beacon time. (15.36 microseconds) + void scanNetworks(uint32_t channel_mask = ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK, uint8_t scan_duration = 5); + // Zigbee scan complete status check, -2: failed or not started, -1: running, 0: no networks found, >0: number of networks found + int16_t scanComplete(); + zigbee_scan_result_t *getScanResult(); + void scanDelete(); + + void factoryReset(); + + // Friend function declaration to allow access to private members + friend void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); +}; + +extern ZigbeeCore Zigbee; + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/ci.json b/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/ci.json index 83af1395ee6..af237739327 100644 --- a/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/ci.json +++ b/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/ci.json @@ -1,16 +1,162 @@ -{ - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { - "esp32c6": [ - "espressif:esp32:esp32c6:PartitionScheme=zigbee,ZigbeeMode=ed" - ], - "esp32h2": [ - "espressif:esp32:esp32h2:PartitionScheme=zigbee,ZigbeeMode=ed" - ] +/* Common Class for Zigbee End Point */ + +#include "ZigbeeEP.h" + +#if SOC_IEEE802154_SUPPORTED + +#include "esp_zigbee_cluster.h" + +uint8_t ZigbeeEP::_endpoint = 0; +bool ZigbeeEP::_is_bound = false; +bool ZigbeeEP::_allow_multiple_binding = false; + +/* Zigbee End Device Class */ +ZigbeeEP::ZigbeeEP(uint8_t endpoint) { + _endpoint = endpoint; + _ep_config.endpoint = 0; + _cluster_list = nullptr; +#if !CONFIG_DISABLE_HAL_LOCKS + if (!lock) { + lock = xSemaphoreCreateBinary(); + if (lock == NULL) { + log_e("Semaphore creation failed"); + } } +#endif } + +ZigbeeEP::~ZigbeeEP() {} + +void ZigbeeEP::setVersion(uint8_t version) { + _ep_config.app_device_version = version; +} + +void ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) { + // Convert manufacturer to ZCL string + size_t length = strlen(name); + if (length > 32) { + log_e("Manufacturer name is too long"); + return; + } + // Allocate a new array of size length + 2 (1 for the length, 1 for null terminator) + char *zb_name = new char[length + 2]; + // Store the length as the first element + zb_name[0] = static_cast(length); // Cast size_t to char + // Use memcpy to copy the characters to the result array + memcpy(zb_name + 1, name, length); + // Null-terminate the array + zb_name[length + 1] = '\0'; + + // Convert model to ZCL string + length = strlen(model); + if (length > 32) { + log_e("Model name is too long"); + delete[] zb_name; + return; + } + char *zb_model = new char[length + 2]; + zb_model[0] = static_cast(length); + memcpy(zb_model + 1, model, length); + zb_model[length + 1] = '\0'; + + // Get the basic cluster and update the manufacturer and model attributes + esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)zb_name); + esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)zb_model); +} + +char *ZigbeeEP::readManufacturer(uint8_t endpoint, uint16_t short_addr) { + /* Read peer Manufacture Name & Model Identifier */ + esp_zb_zcl_read_attr_cmd_t read_req; + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + read_req.zcl_basic_cmd.dst_endpoint = endpoint; + read_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BASIC; + + uint16_t attributes[] = { + ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, + }; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + // clear read manufacturer + _read_manufacturer = nullptr; + + esp_zb_zcl_read_attr_cmd_req(&read_req); + + //Wait for response or timeout + if (xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE) { + log_e("Error while reading manufacturer"); + } + return _read_manufacturer; +} + +char *ZigbeeEP::readModel(uint8_t endpoint, uint16_t short_addr) { + /* Read peer Manufacture Name & Model Identifier */ + esp_zb_zcl_read_attr_cmd_t read_req; + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + read_req.zcl_basic_cmd.dst_endpoint = endpoint; + read_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BASIC; + + uint16_t attributes[] = { + ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, + }; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + // clear read model + _read_model = nullptr; + + esp_zb_zcl_read_attr_cmd_req(&read_req); + + //Wait for response or timeout + //Semaphore take + if (xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE) { + log_e("Error while reading model"); + } + return _read_model; +} + +void ZigbeeEP::printBoundDevices() { + log_i("Bound devices:"); + for ([[maybe_unused]] + const auto &device : _bound_devices) { + log_i("Device on endpoint %d, short address: 0x%x", device->endpoint, device->short_addr); + print_ieee_addr(device->ieee_addr); + } +} + +void ZigbeeEP::zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute) { + /* Basic cluster attributes */ + if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { + zbstring_t *zbstr = (zbstring_t *)attribute->data.value; + char *string = (char *)malloc(zbstr->len + 1); + memcpy(string, zbstr->data, zbstr->len); + string[zbstr->len] = '\0'; + log_i("Peer Manufacturer is \"%s\"", string); + _read_manufacturer = string; + xSemaphoreGive(lock); + } + if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { + zbstring_t *zbstr = (zbstring_t *)attribute->data.value; + char *string = (char *)malloc(zbstr->len + 1); + memcpy(string, zbstr->data, zbstr->len); + string[zbstr->len] = '\0'; + log_i("Peer Model is \"%s\"", string); + _read_model = string; + xSemaphoreGive(lock); + } +} + +void ZigbeeEP::zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message) { + if (message->attribute.id == ESP_ZB_ZCL_CMD_IDENTIFY_IDENTIFY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + _on_identify(*(uint16_t *)message->attribute.data.value); + } else { + log_w("Other identify commands are not implemented yet."); + } +} + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino b/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino index 94f7a1aba4e..beb045559b3 100644 --- a/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino +++ b/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino @@ -1,120 +1,124 @@ -// 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 thermostat. - * - * The example demonstrates how to use Zigbee library to get data from temperature - * sensor end device and act as an thermostat. - * The temperature sensor is a Zigbee end device, which is controlled by a Zigbee coordinator (thermostat). - * - * 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 +/* Common Class for Zigbee End point */ + +#pragma once #include "ZigbeeCore.h" -#include "ep/ZigbeeThermostat.h" - -#define BUTTON_PIN 9 // Boot button for C6/H2 -#define THERMOSTAT_ENDPOINT_NUMBER 5 - -ZigbeeThermostat zbThermostat = ZigbeeThermostat(THERMOSTAT_ENDPOINT_NUMBER); - -// Save temperature sensor data -float sensor_temp; -float sensor_max_temp; -float sensor_min_temp; -float sensor_tolerance; - -/****************** Temperature sensor handling *******************/ -void recieveSensorTemp(float temperature) { - Serial.printf("Temperature sensor value: %.2f°C\n", temperature); - sensor_temp = temperature; -} - -void recieveSensorConfig(float min_temp, float max_temp, float tolerance) { - Serial.printf("Temperature sensor settings: min %.2f°C, max %.2f°C, tolerance %.2f°C\n", min_temp, max_temp, tolerance); - sensor_min_temp = min_temp; - sensor_max_temp = max_temp; - sensor_tolerance = tolerance; -} -/********************* Arduino functions **************************/ -void setup() { - Serial.begin(115200); - while (!Serial) { - delay(10); +#if SOC_IEEE802154_SUPPORTED + +#include + +/* Usefull defines */ +#define ZB_ARRAY_LENTH(arr) (sizeof(arr) / sizeof(arr[0])) +#define print_ieee_addr(addr) \ + log_i("IEEE Address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]) +#define XYZ_TO_RGB(X, Y, Z, r, g, b) \ + { \ + r = (float)(3.240479 * (X) - 1.537150 * (Y) - 0.498535 * (Z)); \ + g = (float)(-0.969256 * (X) + 1.875992 * (Y) + 0.041556 * (Z)); \ + b = (float)(0.055648 * (X) - 0.204043 * (Y) + 1.057311 * (Z)); \ + if (r > 1) { \ + r = 1; \ + } \ + if (g > 1) { \ + g = 1; \ + } \ + if (b > 1) { \ + b = 1; \ + } \ } - // Init button switch - pinMode(BUTTON_PIN, INPUT); - - // Set callback functions for temperature and configuration recieve - zbThermostat.onTempRecieve(recieveSensorTemp); - zbThermostat.onConfigRecieve(recieveSensorConfig); - - //Optional: set Zigbee device name and model - zbThermostat.setManufacturerAndModel("Espressif", "ZigbeeThermostat"); +#define RGB_TO_XYZ(r, g, b, X, Y, Z) \ + { \ + X = (float)(0.412453 * (r) + 0.357580 * (g) + 0.180423 * (b)); \ + Y = (float)(0.212671 * (r) + 0.715160 * (g) + 0.072169 * (b)); \ + Z = (float)(0.019334 * (r) + 0.119193 * (g) + 0.950227 * (b)); \ + } - //Add endpoint to Zigbee Core - Zigbee.addEndpoint(&zbThermostat); +typedef struct zbstring_s { + uint8_t len; + char data[]; +} ESP_ZB_PACKED_STRUCT zbstring_t; + +typedef struct zb_device_params_s { + esp_zb_ieee_addr_t ieee_addr; + uint8_t endpoint; + uint16_t short_addr; +} zb_device_params_t; + +typedef enum { + SINGLE_COLOR = 0, + RGB = 1 +} zb_identify_led_type_t; + +/* Zigbee End Device Class */ +class ZigbeeEP { +public: + ZigbeeEP(uint8_t endpoint = 10); + ~ZigbeeEP(); + + // Set ep config and cluster list + void setEpConfig(esp_zb_endpoint_config_t ep_config, esp_zb_cluster_list_t *cluster_list) { + _ep_config = ep_config; + _cluster_list = cluster_list; + } - //Open network for 180 seconds after boot - Zigbee.setRebootOpenNetwork(180); - - // When all EPs are registered, start Zigbee with ZIGBEE_COORDINATOR mode - Zigbee.begin(ZIGBEE_COORDINATOR); - - Serial.println("Waiting for Temperature sensor to bound to the switch"); + void setVersion(uint8_t version); + uint8_t getEndpoint() { + return _endpoint; + } - //Wait for switch to bound to a light: - while(!zbThermostat.isBound()) - { - Serial.printf("."); - delay(500); + void printBoundDevices(); + std::list getBoundDevices() const { + return _bound_devices; } - // Get temperature sensor configuration - zbThermostat.getSensorSettings(); - Serial.println(); -} + static bool isBound() { + return _is_bound; + } + static void allowMultipleBinding(bool bind) { + _allow_multiple_binding = bind; + } -void loop() { - // Handle button switch in loop() - if (digitalRead(BUTTON_PIN) == LOW) { // Push button pressed + // Manufacturer name and model implemented + void setManufacturerAndModel(const char *name, const char *model); - // Key debounce handling - while (digitalRead(BUTTON_PIN) == LOW) { - delay(50); - } + // Methods to read manufacturer and model name from selected endpoint and short address + char *readManufacturer(uint8_t endpoint, uint16_t short_addr); + char *readModel(uint8_t endpoint, uint16_t short_addr); - // Set reporting interval for temperature sensor - zbThermostat.setTemperatureReporting(0, 10, 2); + bool epAllowMultipleBinding() { + return _allow_multiple_binding; } - - // Print temperature sensor data each 10 seconds - static uint32_t last_print = 0; - if (millis() - last_print > 10000) { - last_print = millis(); - int temp_percent = (int)((sensor_temp - sensor_min_temp) / (sensor_max_temp - sensor_min_temp) * 100); - Serial.printf("Loop temperature info: %.2f°C (%d %%)\n", sensor_temp, temp_percent); + + // findEndpoind may be implemented by EPs to find and bind devices + virtual void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) {}; + + //list of all handlers function calls, to be overide by EPs implementation + virtual void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {}; + virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) {}; + virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented + virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message); + + void onIdentify(void (*callback)(uint16_t)) { + _on_identify = callback; } -} + +private: + static bool _allow_multiple_binding; + char *_read_manufacturer; + char *_read_model; + void (*_on_identify)(uint16_t time); + +protected: + static uint8_t _endpoint; + esp_zb_ha_standard_devices_t _device_id; + esp_zb_endpoint_config_t _ep_config; + esp_zb_cluster_list_t *_cluster_list; + static bool _is_bound; + std::list _bound_devices; + SemaphoreHandle_t lock; + + friend class ZigbeeCore; +}; + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/examples/Zigbee_Thermostat/ci.json b/libraries/Zigbee/examples/Zigbee_Thermostat/ci.json index 203fce34b38..9522b0ba1a8 100644 --- a/libraries/Zigbee/examples/Zigbee_Thermostat/ci.json +++ b/libraries/Zigbee/examples/Zigbee_Thermostat/ci.json @@ -1,16 +1,141 @@ -{ - "targets": { - "esp32": false, - "esp32c3": false, - "esp32s2": false, - "esp32s3": false - }, - "fqbn": { - "esp32c6": [ - "espressif:esp32:esp32c6:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" - ], - "esp32h2": [ - "espressif:esp32:esp32h2:PartitionScheme=zigbee_zczr,ZigbeeMode=zczr" - ] +/* Zigbee Common Functions */ +#include "ZigbeeCore.h" +#include "Arduino.h" + +#if SOC_IEEE802154_SUPPORTED + +// forward declaration of all implemented handlers +static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message); +static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message); +static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message); +static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message); +static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message); + +// Zigbee action handlers +[[maybe_unused]] +static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) { + esp_err_t ret = ESP_OK; + switch (callback_id) { + case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: ret = zb_attribute_set_handler((esp_zb_zcl_set_attr_value_message_t *)message); break; + case ESP_ZB_CORE_REPORT_ATTR_CB_ID: ret = zb_attribute_reporting_handler((esp_zb_zcl_report_attr_message_t *)message); break; + case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: ret = zb_cmd_read_attr_resp_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *)message); break; + case ESP_ZB_CORE_CMD_REPORT_CONFIG_RESP_CB_ID: ret = zb_configure_report_resp_handler((esp_zb_zcl_cmd_config_report_resp_message_t *)message); break; + case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break; + default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break; } + return ret; } + +static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message) { + if (!message) { + log_e("Empty message"); + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + } + + log_v( + "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id, + message->attribute.data.size + ); + + // List through all Zigbee EPs and call the callback function, with the message + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (message->info.dst_endpoint == (*it)->getEndpoint()) { + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY) { + (*it)->zbIdentify(message); //method zbIdentify implemented in the common EP class + } else { + (*it)->zbAttributeSet(message); //method zbAttributeSet must be implemented in specific EP class + } + } + } + return ESP_OK; +} + +static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message) { + if (!message) { + log_e("Empty message"); + } + if (message->status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->status); + } + log_v( + "Received report from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->src_address.u.short_addr, message->src_endpoint, + message->dst_endpoint, message->cluster + ); + // List through all Zigbee EPs and call the callback function, with the message + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (message->dst_endpoint == (*it)->getEndpoint()) { + (*it)->zbAttributeRead(message->cluster, &message->attribute); //method zbAttributeRead must be implemented in specific EP class + } + } + return ESP_OK; +} + +static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) { + if (!message) { + log_e("Empty message"); + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + } + log_v( + "Read attribute response: from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->info.src_address.u.short_addr, + message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster + ); + + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (message->info.dst_endpoint == (*it)->getEndpoint()) { + esp_zb_zcl_read_attr_resp_variable_t *variable = message->variables; + while (variable) { + log_v( + "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", variable->status, message->info.cluster, + variable->attribute.id, variable->attribute.data.type, variable->attribute.data.value ? *(uint8_t *)variable->attribute.data.value : 0 + ); + if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) { + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BASIC) { + (*it)->zbReadBasicCluster(&variable->attribute); //method zbReadBasicCluster implemented in the common EP class + } else { + (*it)->zbAttributeRead(message->info.cluster, &variable->attribute); //method zbAttributeRead must be implemented in specific EP class + } + } + variable = variable->next; + } + } + } + return ESP_OK; +} + +static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message) { + if (!message) { + log_e("Empty message"); + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + } + esp_zb_zcl_config_report_resp_variable_t *variable = message->variables; + while (variable) { + log_v( + "Configure report response: status(%d), cluster(0x%x), direction(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->direction, + variable->attribute_id + ); + variable = variable->next; + } + return ESP_OK; +} + +static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message) { + if (!message) { + log_e("Empty message"); + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + } + log_v( + "Received default response: from address(0x%x), src_endpoint(%d) to dst_endpoint(%d), cluster(0x%x) with status 0x%x", + message->info.src_address.u.short_addr, message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster, message->status_code + ); + return ESP_OK; +} + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ZigbeeCore.cpp b/libraries/Zigbee/src/ZigbeeCore.cpp index 737f2459368..b823d3879d1 100644 --- a/libraries/Zigbee/src/ZigbeeCore.cpp +++ b/libraries/Zigbee/src/ZigbeeCore.cpp @@ -1,391 +1,112 @@ -/* Zigbee Core Functions */ - -#include "ZigbeeCore.h" +#include "ZigbeeColorDimmableLight.h" #if SOC_IEEE802154_SUPPORTED -#include "ZigbeeHandlers.cpp" -#include "Arduino.h" - -ZigbeeCore::ZigbeeCore() { - _radio_config.radio_mode = ZB_RADIO_MODE_NATIVE; // Use the native 15.4 radio - _host_config.host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE; // Disable host connection - _zb_ep_list = esp_zb_ep_list_create(); - _primary_channel_mask = ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK; - _open_network = 0; - _scan_status = ZB_SCAN_FAILED; - _started = false; -} -ZigbeeCore::~ZigbeeCore() {} - -//forward declaration -static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message); - -bool ZigbeeCore::begin(esp_zb_cfg_t *role_cfg, bool erase_nvs) { - if (!zigbeeInit(role_cfg, erase_nvs)){ - return false; - } - _role = (zigbee_role_t)role_cfg->esp_zb_role; - return true; -} - -bool ZigbeeCore::begin(zigbee_role_t role, bool erase_nvs) { - bool status = true; - switch (role) - { - case ZIGBEE_COORDINATOR: { - _role = ZIGBEE_COORDINATOR; - esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_COORDINATOR_CONFIG(); - status = zigbeeInit(&zb_nwk_cfg, erase_nvs); - break; - } - case ZIGBEE_ROUTER: { - _role = ZIGBEE_ROUTER; - esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_ROUTER_CONFIG(); - status = zigbeeInit(&zb_nwk_cfg, erase_nvs); - break; - } - case ZIGBEE_END_DEVICE: { - _role = ZIGBEE_END_DEVICE; - esp_zb_cfg_t zb_nwk_cfg = ZIGBEE_DEFAULT_ED_CONFIG(); - status = zigbeeInit(&zb_nwk_cfg, erase_nvs); - break; - } - default: - log_e("Invalid Zigbee Role"); - return false; - } - return status; -} - -void ZigbeeCore::addEndpoint(ZigbeeEP *ep) { - ep_objects.push_back(ep); - - log_d("Endpoint: %d, Device ID: 0x%04x", ep->_endpoint, ep->_device_id); - //Register clusters and ep_list to the ZigbeeCore class's ep_list - if (ep->_ep_config.endpoint == 0 || ep->_cluster_list == nullptr) { - log_e("Endpoint config or Cluster list is not initialized, EP not added to ZigbeeCore's EP list"); - return; - } - - esp_zb_ep_list_add_ep(_zb_ep_list, ep->_cluster_list, ep->_ep_config); -} - -static void esp_zb_task(void *pvParameters) { - /* initialize Zigbee stack */ - ESP_ERROR_CHECK(esp_zb_start(false)); - esp_zb_stack_main_loop(); -} +ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID; -// Zigbee core init function -bool ZigbeeCore::zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs) { - // Zigbee platform configuration - esp_zb_platform_config_t platform_config = { - .radio_config = _radio_config, - .host_config = _host_config, + esp_zb_color_dimmable_light_cfg_t light_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG(); + _cluster_list = esp_zb_color_dimmable_light_clusters_create(&light_cfg); + _ep_config = { + .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID, .app_device_version = 0 }; - esp_err_t err = esp_zb_platform_config(&platform_config); - if (err != ESP_OK) { - log_e("Failed to configure Zigbee platform"); - return false; - } - - // Initialize Zigbee stack - log_d("Initialize Zigbee stack"); - esp_zb_init(zb_cfg); - - // Register all Zigbee EPs in list - if(ep_objects.empty()) { - log_w("No Zigbee EPs to register"); - } else { - log_d("Register all Zigbee EPs in list"); - err = esp_zb_device_register(_zb_ep_list); - if (err != ESP_OK) { - log_e("Failed to register Zigbee EPs"); - return false; - } - - //print the list of Zigbee EPs from ep_objects - log_i("List of registered Zigbee EPs:"); - for (std::list::iterator it = ep_objects.begin(); it != ep_objects.end(); ++it) { - log_i("Device type: %s, Endpoint: %d, Device ID: 0x%04x", getDeviceTypeString((*it)->_device_id), (*it)->_endpoint, (*it)->_device_id); - } - } - // Register Zigbee action handler - esp_zb_core_action_handler_register(zb_action_handler); - err = esp_zb_set_primary_network_channel_set(_primary_channel_mask); - if (err != ESP_OK) { - log_e("Failed to set primary network channel mask"); - return false; - } - - //Erase NVRAM before creating connection to new Coordinator - if (erase_nvs) { - esp_zb_nvram_erase_at_start(true); - } - - // Create Zigbee task and start Zigbee stack - xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL); - - return true; + //set default values + _current_state = false; + _current_level = 255; + _current_red = 255; + _current_green = 255; + _current_blue = 255; } -void ZigbeeCore::setRadioConfig(esp_zb_radio_config_t config) { - _radio_config = config; +uint16_t ZigbeeColorDimmableLight::getCurrentColorX() { + return (*(uint16_t *)esp_zb_zcl_get_attribute( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID + ) + ->data_p); } -esp_zb_radio_config_t ZigbeeCore::getRadioConfig() { - return _radio_config; +uint16_t ZigbeeColorDimmableLight::getCurrentColorY() { + return (*(uint16_t *)esp_zb_zcl_get_attribute( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID + ) + ->data_p); } -void ZigbeeCore::setHostConfig(esp_zb_host_config_t config) { - _host_config = config; -} +void ZigbeeColorDimmableLight::calculateRGB(uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue) { + float r, g, b, color_x, color_y; + color_x = (float)x / 65535; + color_y = (float)y / 65535; -esp_zb_host_config_t ZigbeeCore::getHostConfig() { - return _host_config; -} - -void ZigbeeCore::setPrimaryChannelMask(uint32_t mask) { - _primary_channel_mask = mask; -} - -void ZigbeeCore::setRebootOpenNetwork(uint8_t time) { - _open_network = time; -} + float color_X = color_x / color_y; + float color_Z = (1 - color_x - color_y) / color_y; -void ZigbeeCore::openNetwork(uint8_t time) { - if (_started) { - log_v("Openning network for joining for %d seconds", time); - esp_zb_bdb_open_network(time); - } -} + XYZ_TO_RGB(color_X, 1, color_Z, r, g, b); -static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { - ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask)); + red = (uint8_t)(r * (float)255); + green = (uint8_t)(g * (float)255); + blue = (uint8_t)(b * (float)255); } -void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { - //common variables - uint32_t *p_sg_p = signal_struct->p_app_signal; - esp_err_t err_status = signal_struct->esp_err_status; - esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p; - //coordinator variables - esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL; - - //main switch - switch (sig_type) { - case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: // Common - log_i("Zigbee stack initialized"); - esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION); - break; - case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: // Common - case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: // Common - if (err_status == ESP_OK) { - log_i("Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non"); - if (esp_zb_bdb_is_factory_new()) { - // Role specific code - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { - log_i("Start network formation"); - esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION); - } else { - log_i("Start network steering"); - esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); - } - //----------------- - - } else { - log_i("Device rebooted"); - Zigbee._started = true; - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR && Zigbee._open_network > 0) { - log_i("Openning network for joining for %d seconds", Zigbee._open_network); - esp_zb_bdb_open_network(Zigbee._open_network); - } - } - } else { - /* commissioning failed */ - log_e("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status)); - } - break; - case ESP_ZB_BDB_SIGNAL_FORMATION: // Coordinator - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { - if (err_status == ESP_OK) { - esp_zb_ieee_addr_t extended_pan_id; - esp_zb_get_extended_pan_id(extended_pan_id); - log_i( - "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", - extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], - extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address() - ); - esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); - } else { - log_i("Restart network formation (status: %s)", esp_err_to_name(err_status)); - esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000); - } - } - break; - case ESP_ZB_BDB_SIGNAL_STEERING: // Router and End Device - Zigbee._started = true; - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { - if (err_status == ESP_OK) { - log_i("Network steering started"); - } - } - else { - if (err_status == ESP_OK) { - esp_zb_ieee_addr_t extended_pan_id; - esp_zb_get_extended_pan_id(extended_pan_id); - log_i( - "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", - extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], - extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address() - ); - } else { - log_i("Network steering was not successful (status: %s)", esp_err_to_name(err_status)); - esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000); - } - } - break; - case ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE: // Coordinator - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { - dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p); - log_i("New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr); - esp_zb_zdo_match_desc_req_param_t cmd_req; - cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; - cmd_req.addr_of_interest = dev_annce_params->device_short_addr; - log_v("Device capabilities: 0x%02x", dev_annce_params->capability); - /* - capability: - Bit 0 – Alternate PAN Coordinator - Bit 1 – Device type: 1- ZigBee Router; 0 – End Device - Bit 2 – Power Source: 1 Main powered - Bit 3 – Receiver on when Idle - Bit 4 – Reserved - Bit 5 – Reserved - Bit 6 – Security capability - Bit 7 – Reserved - */ - - // for each endpoint in the list call the findEndpoint function if not bounded or allowed to bind multiple devices - for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { - if (!(*it)->isBound() || (*it)->epAllowMultipleBinding()) { - (*it)->findEndpoint(&cmd_req); - } - } +//set attribude method -> methon overriden in child class +void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { + //check the data and call right method + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { + if (_current_state != *(bool *)message->attribute.data.value) { + _current_state = *(bool *)message->attribute.data.value; + lightChanged(); } - break; - case ESP_ZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: // Coordinator - if ((zigbee_role_t)Zigbee.getRole() == ZIGBEE_COORDINATOR) { - if (err_status == ESP_OK) { - if (*(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)) { - log_i("Network(0x%04hx) is open for %d seconds", esp_zb_get_pan_id(), *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); - } else { - log_i("Network(0x%04hx) closed, devices joining not allowed.", esp_zb_get_pan_id()); - } - } + return; + } else { + log_w("Recieved message ignored. Attribute ID: %d not supported for On/Off Light", message->attribute.id); + } + } else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { + if (_current_level != *(uint8_t *)message->attribute.data.value) { + _current_level = *(uint8_t *)message->attribute.data.value; + lightChanged(); } - break; - default: log_v("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break; - } -} - -void ZigbeeCore::factoryReset() { - log_v("Factory reseting Zigbee stack, device will reboot"); - esp_zb_factory_reset(); -} - -void ZigbeeCore::scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor){ - log_v("Zigbee network scan complete"); - if(zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { - log_v("Found %d networks", count); - //print Zigbee networks - for (int i = 0; i < count; i++) { - log_v("Network %d: PAN ID: 0x%04hx, Permit Joining: %s, Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, Channel: %d, Router Capacity: %s, End Device Capacity: %s", - i, nwk_descriptor[i].short_pan_id, nwk_descriptor[i].permit_joining ? "Yes" : "No", nwk_descriptor[i].extended_pan_id[7], nwk_descriptor[i].extended_pan_id[6], nwk_descriptor[i].extended_pan_id[5], nwk_descriptor[i].extended_pan_id[4], nwk_descriptor[i].extended_pan_id[3], nwk_descriptor[i].extended_pan_id[2], nwk_descriptor[i].extended_pan_id[1], nwk_descriptor[i].extended_pan_id[0], nwk_descriptor[i].logic_channel, nwk_descriptor[i].router_capacity ? "Yes" : "No", nwk_descriptor[i].end_device_capacity ? "Yes" : "No"); + return; + } else { + log_w("Recieved message ignored. Attribute ID: %d not supported for Level Control", message->attribute.id); + //TODO: implement more attributes -> includes/zcl/esp_zigbee_zcl_level.h + } + } else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + uint16_t light_color_x = (*(uint16_t *)message->attribute.data.value); + uint16_t light_color_y = getCurrentColorY(); + //calculate RGB from XY and call setColor() + uint8_t red, green, blue; + calculateRGB(light_color_x, light_color_y, red, green, blue); + _current_blue = blue; + _current_green = green; + _current_red = red; + lightChanged(); + return; + + } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + uint16_t light_color_x = getCurrentColorX(); + uint16_t light_color_y = (*(uint16_t *)message->attribute.data.value); + //calculate RGB from XY and call setColor() + uint8_t red, green, blue; + calculateRGB(light_color_x, light_color_y, red, green, blue); + _current_blue = blue; + _current_green = green; + _current_red = red; + lightChanged(); + return; + } else { + log_w("Recieved message ignored. Attribute ID: %d not supported for Color Control", message->attribute.id); } - //save scan result and update scan status - //copy network descriptor to _scan_result to keep the data after the callback - Zigbee._scan_result = (esp_zb_network_descriptor_t *)malloc(count * sizeof(esp_zb_network_descriptor_t)); - memcpy(Zigbee._scan_result, nwk_descriptor, count * sizeof(esp_zb_network_descriptor_t)); - Zigbee._scan_status = count; } else { - log_e("Failed to scan Zigbee network (status: 0x%x)", zdo_status); - Zigbee._scan_status = ZB_SCAN_FAILED; - Zigbee._scan_result = nullptr; + log_w("Recieved message ignored. Cluster ID: %d not supported for Color dimmable Light", message->info.cluster); } } -void ZigbeeCore::scanNetworks(u_int32_t channel_mask, u_int8_t scan_duration) { - if(!_started) { - log_e("Zigbee stack is not started, cannot scan networks"); - return; +void ZigbeeColorDimmableLight::lightChanged() { + if (_on_light_change) { + _on_light_change(_current_state, _current_red, _current_green, _current_blue, _current_level); } - log_v("Scanning Zigbee networks"); - esp_zb_zdo_active_scan_request(channel_mask, scan_duration, scanCompleteCallback); - _scan_status = ZB_SCAN_RUNNING; -} - -int16_t ZigbeeCore::scanComplete(){ - return _scan_status; -} - -zigbee_scan_result_t* ZigbeeCore::getScanResult() { - return _scan_result; } -void ZigbeeCore::scanDelete() { - if (_scan_result != nullptr) { - free(_scan_result); - _scan_result = nullptr; - } - _scan_status = ZB_SCAN_FAILED; -} - -// Function to convert enum value to string -const char* ZigbeeCore::getDeviceTypeString(esp_zb_ha_standard_devices_t deviceId) { - switch (deviceId) { - case ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID: return "General On/Off switch"; - case ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID: return "Level Control Switch"; - case ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID: return "General On/Off output"; - case ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID: return "Level Controllable Output"; - case ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID: return "Scene Selector"; - case ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID: return "Configuration Tool"; - case ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID: return "Remote Control"; - case ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID: return "Combined Interface"; - case ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID: return "Range Extender"; - case ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID: return "Mains Power Outlet"; - case ESP_ZB_HA_DOOR_LOCK_DEVICE_ID: return "Door lock client"; - case ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID: return "Door lock controller"; - case ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID: return "Simple Sensor device"; - case ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID: return "Consumption Awareness Device"; - case ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID: return "Home Gateway"; - case ESP_ZB_HA_SMART_PLUG_DEVICE_ID: return "Smart plug"; - case ESP_ZB_HA_WHITE_GOODS_DEVICE_ID: return "White Goods"; - case ESP_ZB_HA_METER_INTERFACE_DEVICE_ID: return "Meter Interface"; - case ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID: return "On/Off Light Device"; - case ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID: return "Dimmable Light Device"; - case ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID: return "Color Dimmable Light Device"; - case ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID: return "Dimmer Switch Device"; - case ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID: return "Color Dimmer Switch Device"; - case ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID: return "Light Sensor"; - case ESP_ZB_HA_SHADE_DEVICE_ID: return "Shade"; - case ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID: return "Shade controller"; - case ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID: return "Window Covering client"; - case ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID: return "Window Covering controller"; - case ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID: return "Heating/Cooling Unit device"; - case ESP_ZB_HA_THERMOSTAT_DEVICE_ID: return "Thermostat Device"; - case ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID: return "Temperature Sensor"; - case ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID: return "IAS Control and Indicating Equipment"; - case ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID: return "IAS Ancillary Control Equipment"; - case ESP_ZB_HA_IAS_ZONE_ID: return "IAS Zone"; - case ESP_ZB_HA_IAS_WARNING_DEVICE_ID: return "IAS Warning Device"; - case ESP_ZB_HA_TEST_DEVICE_ID: return "Custom HA device for test"; - case ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID: return "Custom Tunnel device"; - case ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID: return "Custom Attributes Device"; - default: return "Unknown device type"; - } -} - -ZigbeeCore Zigbee = ZigbeeCore(); - -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ZigbeeCore.h b/libraries/Zigbee/src/ZigbeeCore.h index 0a7f52e1b42..992c2573654 100644 --- a/libraries/Zigbee/src/ZigbeeCore.h +++ b/libraries/Zigbee/src/ZigbeeCore.h @@ -1,128 +1,41 @@ -/* Zigbee core class */ +/* Class of Zigbee On/Off Light endpoint inherited from common EP class */ #pragma once #include "soc/soc_caps.h" #if SOC_IEEE802154_SUPPORTED -#include "esp_zigbee_core.h" -#include "zdo/esp_zigbee_zdo_common.h" -#include -#include #include "ZigbeeEP.h" -class ZigbeeEP; +#include "ha/esp_zigbee_ha_standard.h" -typedef void (*voidFuncPtr)(void); -typedef void (*voidFuncPtrArg)(void *); +class ZigbeeColorDimmableLight : public ZigbeeEP { +public: + ZigbeeColorDimmableLight(uint8_t endpoint); + ~ZigbeeColorDimmableLight(); -typedef esp_zb_network_descriptor_t zigbee_scan_result_t; - -// enum of Zigbee Roles -typedef enum { - ZIGBEE_COORDINATOR = 0, - ZIGBEE_ROUTER = 1, - ZIGBEE_END_DEVICE = 2 -} zigbee_role_t; - -#define ZB_SCAN_RUNNING (-1) -#define ZB_SCAN_FAILED (-2) - -#define ZIGBEE_DEFAULT_ED_CONFIG() \ - { \ - .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED, \ - .install_code_policy = false, \ - .nwk_cfg = { \ - .zed_cfg = \ - { \ - .ed_timeout = ESP_ZB_ED_AGING_TIMEOUT_64MIN, \ - .keep_alive = 3000, \ - }, \ - }, \ - } - -#define ZIGBEE_DEFAULT_ROUTER_CONFIG() \ - { \ - .esp_zb_role = ESP_ZB_DEVICE_TYPE_ROUTER, \ - .install_code_policy = false, \ - .nwk_cfg = { \ - .zczr_cfg = \ - { \ - .max_children = 10, \ - }, \ - } \ + void onLightChange(void (*callback)(bool, uint8_t, uint8_t, uint8_t, uint8_t)) { + _on_light_change = callback; } - - -#define ZIGBEE_DEFAULT_COORDINATOR_CONFIG() \ - { \ - .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, \ - .install_code_policy = false, \ - .nwk_cfg = { \ - .zczr_cfg = \ - { \ - .max_children = 10, \ - }, \ - } \ + void restoreLight() { + lightChanged(); } -class ZigbeeCore { - private: - esp_zb_radio_config_t _radio_config; - esp_zb_host_config_t _host_config; - uint32_t _primary_channel_mask; - int16_t _scan_status; - - esp_zb_ep_list_t *_zb_ep_list; - zigbee_role_t _role; - bool _started; - - uint8_t _open_network; - zigbee_scan_result_t *_scan_result; - - bool zigbeeInit(esp_zb_cfg_t *zb_cfg, bool erase_nvs); - static void scanCompleteCallback(esp_zb_zdp_status_t zdo_status, uint8_t count, esp_zb_network_descriptor_t *nwk_descriptor); - const char* getDeviceTypeString(esp_zb_ha_standard_devices_t deviceId); - - public: - ZigbeeCore(); - ~ZigbeeCore(); +private: + void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; + void calculateRGB(uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue); - std::list ep_objects; + uint16_t getCurrentColorX(); + uint16_t getCurrentColorY(); - bool begin(zigbee_role_t role = ZIGBEE_END_DEVICE, bool erase_nvs = false); - bool begin(esp_zb_cfg_t *role_cfg, bool erase_nvs = false); - // bool end(); - - bool isStarted() { return _started; } - zigbee_role_t getRole() { return _role; } - - void addEndpoint(ZigbeeEP *ep); - //void removeEndpoint(ZigbeeEP *ep); - - void setRadioConfig(esp_zb_radio_config_t config); - esp_zb_radio_config_t getRadioConfig(); - - void setHostConfig(esp_zb_host_config_t config); - esp_zb_host_config_t getHostConfig(); - - void setPrimaryChannelMask(uint32_t mask); - void setRebootOpenNetwork(uint8_t time); - void openNetwork(uint8_t time); - - //scan_duration Time spent scanning each channel, in units of ((1 << scan_duration) + 1) * a beacon time. (15.36 microseconds) - void scanNetworks(uint32_t channel_mask = ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK, uint8_t scan_duration = 5); - // Zigbee scan complete status check, -2: failed or not started, -1: running, 0: no networks found, >0: number of networks found - int16_t scanComplete(); - zigbee_scan_result_t* getScanResult(); - void scanDelete(); - - void factoryReset(); - - // Friend function declaration to allow access to private members - friend void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); + void lightChanged(); + //callback function to be called on light change (State, R, G, B, Level) + void (*_on_light_change)(bool, uint8_t, uint8_t, uint8_t, uint8_t); + bool _current_state; + uint8_t _current_level; + uint16_t _current_red; + uint16_t _current_green; + uint16_t _current_blue; }; -extern ZigbeeCore Zigbee; - -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ZigbeeEP.cpp b/libraries/Zigbee/src/ZigbeeEP.cpp index 1f5823a7bfd..c30599aadac 100644 --- a/libraries/Zigbee/src/ZigbeeEP.cpp +++ b/libraries/Zigbee/src/ZigbeeEP.cpp @@ -1,165 +1,403 @@ -/* Common Class for Zigbee End Point */ +#include "ZigbeeColorDimmerSwitch.h" +#if SOC_IEEE802154_SUPPORTED -#include "ZigbeeEP.h" +// Initialize the static instance pointer +ZigbeeColorDimmerSwitch *ZigbeeColorDimmerSwitch::_instance = nullptr; -#if SOC_IEEE802154_SUPPORTED +ZigbeeColorDimmerSwitch::ZigbeeColorDimmerSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID; + _instance = this; // Set the static pointer to this instance + + esp_zb_color_dimmable_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_SWITCH_CONFIG(); + _cluster_list = esp_zb_color_dimmable_switch_clusters_create(&switch_cfg); + + _ep_config = { + .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID, .app_device_version = 0 + }; +} -#include "esp_zigbee_cluster.h" - -uint8_t ZigbeeEP::_endpoint = 0; -bool ZigbeeEP::_is_bound = false; -bool ZigbeeEP::_allow_multiple_binding = false; - -/* Zigbee End Device Class */ -ZigbeeEP::ZigbeeEP(uint8_t endpoint) { - _endpoint = endpoint; - _ep_config.endpoint = 0; - _cluster_list = nullptr; -#if !CONFIG_DISABLE_HAL_LOCKS - if (!lock) { - lock = xSemaphoreCreateBinary(); - if (lock == NULL) { - log_e("Semaphore creation failed"); - } +void ZigbeeColorDimmerSwitch::calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y) { + // Convert RGB to XYZ + float r = (float)red / 255.0f; + float g = (float)green / 255.0f; + float b = (float)blue / 255.0f; + + float X, Y, Z; + RGB_TO_XYZ(r, g, b, X, Y, Z); + + // Convert XYZ to xy chromaticity coordinates + float color_x = X / (X + Y + Z); + float color_y = Y / (X + Y + Z); + + // Convert normalized xy to 16-bit values + x = (uint16_t)(color_x * 65535.0f); + y = (uint16_t)(color_y * 65535.0f); +} + +void ZigbeeColorDimmerSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_i("Bound successfully!"); + if (user_ctx) { + zb_device_params_t *light = (zb_device_params_t *)user_ctx; + log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); + _instance->_bound_devices.push_back(light); } -#endif + _is_bound = true; + } else { + log_e("Binding failed!"); + } } -ZigbeeEP::~ZigbeeEP() { +void ZigbeeColorDimmerSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_d("Found light endpoint"); + esp_zb_zdo_bind_req_param_t bind_req; + zb_device_params_t *light = (zb_device_params_t *)malloc(sizeof(zb_device_params_t)); + light->endpoint = endpoint; + light->short_addr = addr; + esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); + esp_zb_get_long_address(bind_req.src_address); + bind_req.src_endp = _endpoint; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; + bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; + memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); + bind_req.dst_endp = endpoint; + bind_req.req_dst_addr = esp_zb_get_short_address(); + log_v("Try to bind on/off control of dimmable light"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL); + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL; + log_v("Try to bind level control of dimmable light"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL); + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL; + log_v("Try to bind color control of dimmable light"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light); + } else { + log_v("No color dimmable light endpoint found"); + } +} +// find on_off light endpoint +void ZigbeeColorDimmerSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) { + uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, + ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL}; + esp_zb_zdo_match_desc_req_param_t color_dimmable_light_req = { + .dst_nwk_addr = cmd_req->dst_nwk_addr, + .addr_of_interest = cmd_req->addr_of_interest, + .profile_id = ESP_ZB_AF_HA_PROFILE_ID, + .num_in_clusters = 3, + .num_out_clusters = 3, + .cluster_list = cluster_list, + }; + esp_zb_zdo_match_cluster(&color_dimmable_light_req, findCb, NULL); } -void ZigbeeEP::setVersion(uint8_t version) { - _ep_config.app_device_version = version; +// Methods to control the light +void ZigbeeColorDimmerSwitch::lightToggle() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command"); + //esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + //esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -void ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) { - // Convert manufacturer to ZCL string - size_t length = strlen(name); - if (length > 32) { - log_e("Manufacturer name is too long"); - return; - } - // Allocate a new array of size length + 2 (1 for the length, 1 for null terminator) - char* zb_name = new char[length + 2]; - // Store the length as the first element - zb_name[0] = static_cast(length); // Cast size_t to char - // Use memcpy to copy the characters to the result array - memcpy(zb_name + 1, name, length); - // Null-terminate the array - zb_name[length + 1] = '\0'; - - // Convert model to ZCL string - length = strlen(model); - if (length > 32) { - log_e("Model name is too long"); - delete[] zb_name; - return; - } - char* zb_model = new char[length + 2]; - zb_model[0] = static_cast(length); - memcpy(zb_model + 1, model, length); - zb_model[length + 1] = '\0'; +void ZigbeeColorDimmerSwitch::lightToggle(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command to group address 0x%x", group_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - // Get the basic cluster and update the manufacturer and model attributes - esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); - esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)zb_name); - esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)zb_model); +void ZigbeeColorDimmerSwitch::lightToggle(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -char* ZigbeeEP::readManufacturer(uint8_t endpoint, uint16_t short_addr) { - /* Read peer Manufacture Name & Model Identifier */ - esp_zb_zcl_read_attr_cmd_t read_req; - read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - read_req.zcl_basic_cmd.src_endpoint = _endpoint; - read_req.zcl_basic_cmd.dst_endpoint = endpoint; - read_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BASIC; +void ZigbeeColorDimmerSwitch::lightOn() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - uint16_t attributes[] = { - ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, - }; - read_req.attr_number = ZB_ARRAY_LENTH(attributes); - read_req.attr_field = attributes; +void ZigbeeColorDimmerSwitch::lightOn(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command to group address 0x%x", group_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} +void ZigbeeColorDimmerSwitch::lightOn(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - // clear read manufacturer - _read_manufacturer = nullptr; +void ZigbeeColorDimmerSwitch::lightOff() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - esp_zb_zcl_read_attr_cmd_req(&read_req); +void ZigbeeColorDimmerSwitch::lightOff(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command to group address 0x%x", group_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - //Wait for response or timeout - if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){ - log_e("Error while reading manufacturer"); - } - return _read_manufacturer; +void ZigbeeColorDimmerSwitch::lightOff(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} +void ZigbeeColorDimmerSwitch::lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant) { + if (_is_bound) { + esp_zb_zcl_on_off_off_with_effect_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.effect_id = effect_id; + cmd_req.effect_variant = effect_variant; + log_i("Sending 'light off with effect' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_off_with_effect_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -char* ZigbeeEP::readModel(uint8_t endpoint, uint16_t short_addr) { - /* Read peer Manufacture Name & Model Identifier */ - esp_zb_zcl_read_attr_cmd_t read_req; - read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - read_req.zcl_basic_cmd.src_endpoint = _endpoint; - read_req.zcl_basic_cmd.dst_endpoint = endpoint; - read_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_BASIC; +void ZigbeeColorDimmerSwitch::lightOnWithSceneRecall() { + if (_is_bound) { + esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + log_i("Sending 'light on with scene recall' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - uint16_t attributes[] = { - ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, - }; - read_req.attr_number = ZB_ARRAY_LENTH(attributes); - read_req.attr_field = attributes; +void ZigbeeColorDimmerSwitch::lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off) { + if (_is_bound) { + esp_zb_zcl_on_off_on_with_timed_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_control = on_off_control; //TODO: Test how it works, then maybe change API + cmd_req.on_time = time_on; + cmd_req.off_wait_time = time_off; + log_i("Sending 'light on with time off' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_on_off_on_with_timed_off_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - // clear read model - _read_model = nullptr; +void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level) { + if (_is_bound) { + esp_zb_zcl_move_to_level_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.level = level; + cmd_req.transition_time = 0xffff; + log_i("Sending 'set light level' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - esp_zb_zcl_read_attr_cmd_req(&read_req); +void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level, uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_move_to_level_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.level = level; + cmd_req.transition_time = 0xffff; + log_i("Sending 'set light level' command to group address 0x%x", group_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} - //Wait for response or timeout - //Semaphore take - if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){ - log_e("Error while reading model"); - } - return _read_model; +void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level, uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_move_to_level_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.level = level; + cmd_req.transition_time = 0xffff; + log_i("Sending 'set light level' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -void ZigbeeEP::printBoundDevices() { - log_i("Bound devices:"); - for( [[maybe_unused]] const auto& device : _bound_devices) { - log_i("Device on endpoint %d, short address: 0x%x", device->endpoint, device->short_addr); - print_ieee_addr(device->ieee_addr); - } +void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue) { + if (_is_bound) { + //Convert RGB to XY + uint16_t color_x, color_y; + calculateXY(red, green, blue, color_x, color_y); + + esp_zb_zcl_color_move_to_color_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.color_x = color_x; + cmd_req.color_y = color_y; + cmd_req.transition_time = 0; + log_i("Sending 'set light color' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -void ZigbeeEP::zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute) { - /* Basic cluster attributes */ - if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { - zbstring_t *zbstr = (zbstring_t *)attribute->data.value; - char *string = (char *)malloc(zbstr->len + 1); - memcpy(string, zbstr->data, zbstr->len); - string[zbstr->len] = '\0'; - log_i("Peer Manufacturer is \"%s\"", string); - _read_manufacturer = string; - xSemaphoreGive(lock); - } - if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { - zbstring_t *zbstr = (zbstring_t *)attribute->data.value; - char *string = (char *)malloc(zbstr->len + 1); - memcpy(string, zbstr->data, zbstr->len); - string[zbstr->len] = '\0'; - log_i("Peer Model is \"%s\"", string); - _read_model = string; - xSemaphoreGive(lock); - } +void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint16_t group_addr) { + if (_is_bound) { + //Convert RGB to XY + uint16_t color_x, color_y; + calculateXY(red, green, blue, color_x, color_y); + + esp_zb_zcl_color_move_to_color_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.color_x = color_x; + cmd_req.color_y = color_y; + cmd_req.transition_time = 0; + log_i("Sending 'set light color' command to group address 0x%x", group_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } } -void ZigbeeEP::zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message) { - if (message->attribute.id == ESP_ZB_ZCL_CMD_IDENTIFY_IDENTIFY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { - _on_identify(*(uint16_t *)message->attribute.data.value); - } else { - log_w("Other identify commands are not implemented yet."); - } -} +void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + //Convert RGB to XY + uint16_t color_x, color_y; + calculateXY(red, green, blue, color_x, color_y); + + esp_zb_zcl_color_move_to_color_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.color_x = color_x; + cmd_req.color_y = color_y; + cmd_req.transition_time = 0; + log_i("Sending 'set light color' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); + esp_zb_lock_release(); + } else { + log_e("Light not bound"); + } +} -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index cdd52fbcd78..2263f3235ca 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -1,106 +1,60 @@ -/* Common Class for Zigbee End point */ +/* Class of Zigbee On/Off Switch endpoint inherited from common EP class */ #pragma once -#include "ZigbeeCore.h" +#include "soc/soc_caps.h" #if SOC_IEEE802154_SUPPORTED -#include - -/* Usefull defines */ -#define ZB_ARRAY_LENTH(arr) (sizeof(arr)/ sizeof(arr[0])) -#define print_ieee_addr(addr) log_i("IEEE Address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]) -#define XYZ_TO_RGB(X, Y, Z, r, g, b) \ -{ \ - r = (float)( 3.240479*(X) -1.537150*(Y) -0.498535*(Z)); \ - g = (float)(-0.969256*(X) +1.875992*(Y) +0.041556*(Z)); \ - b = (float)( 0.055648*(X) -0.204043*(Y) +1.057311*(Z)); \ - if(r > 1){r = 1;} \ - if(g > 1){g = 1;} \ - if(b > 1){b = 1;} \ -} - -#define RGB_TO_XYZ(r, g, b, X, Y, Z) \ -{ \ - X = (float)(0.412453*(r) + 0.357580*(g) + 0.180423*(b)); \ - Y = (float)(0.212671*(r) + 0.715160*(g) + 0.072169*(b)); \ - Z = (float)(0.019334*(r) + 0.119193*(g) + 0.950227*(b)); \ -} - -typedef struct zbstring_s { - uint8_t len; - char data[]; -} ESP_ZB_PACKED_STRUCT -zbstring_t; - -typedef struct zb_device_params_s { - esp_zb_ieee_addr_t ieee_addr; - uint8_t endpoint; - uint16_t short_addr; -} zb_device_params_t; - -typedef enum { - SINGLE_COLOR = 0, - RGB = 1 -} zb_identify_led_type_t; - -/* Zigbee End Device Class */ -class ZigbeeEP { - public: - ZigbeeEP(uint8_t endpoint = 10); - ~ZigbeeEP(); - - // Set ep config and cluster list - void setEpConfig(esp_zb_endpoint_config_t ep_config, esp_zb_cluster_list_t *cluster_list) { - _ep_config = ep_config; - _cluster_list = cluster_list; - } - - void setVersion(uint8_t version); - uint8_t getEndpoint() { return _endpoint; } - - void printBoundDevices(); - std::list getBoundDevices() const { return _bound_devices;} - - static bool isBound() { return _is_bound; } - static void allowMultipleBinding(bool bind) { _allow_multiple_binding = bind; } - - // Manufacturer name and model implemented - void setManufacturerAndModel(const char *name, const char *model); - - // Methods to read manufacturer and model name from selected endpoint and short address - char* readManufacturer(uint8_t endpoint, uint16_t short_addr); - char* readModel(uint8_t endpoint, uint16_t short_addr); - - bool epAllowMultipleBinding() { return _allow_multiple_binding; } - - // findEndpoind may be implemented by EPs to find and bind devices - virtual void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) {}; - - //list of all handlers function calls, to be overide by EPs implementation - virtual void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {}; - virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) {}; - virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented - virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message); - - void onIdentify(void (*callback)(uint16_t)) { _on_identify = callback; } - - private: - static bool _allow_multiple_binding; - char* _read_manufacturer; - char* _read_model; - void (*_on_identify)(uint16_t time); - - protected: - static uint8_t _endpoint; - esp_zb_ha_standard_devices_t _device_id; - esp_zb_endpoint_config_t _ep_config; - esp_zb_cluster_list_t *_cluster_list; - static bool _is_bound; - std::list _bound_devices; - SemaphoreHandle_t lock; - - friend class ZigbeeCore; +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +class ZigbeeColorDimmerSwitch : public ZigbeeEP { +public: + ZigbeeColorDimmerSwitch(uint8_t endpoint); + ~ZigbeeColorDimmerSwitch(); + + // methods to control the color dimmable light + void lightToggle(); + void lightToggle(uint16_t group_addr); + void lightToggle(uint8_t endpoint, uint16_t short_addr); + + void lightOn(); + void lightOn(uint16_t group_addr); + void lightOn(uint8_t endpoint, uint16_t short_addr); + + void lightOff(); + void lightOff(uint16_t group_addr); + void lightOff(uint8_t endpoint, uint16_t short_addr); + + void lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant); + void lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off); + void lightOnWithSceneRecall(); + + void setLightLevel(uint8_t level); + void setLightLevel(uint8_t level, uint16_t group_addr); + void setLightLevel(uint8_t level, uint8_t endpoint, uint16_t short_addr); + + void setLightColor(uint8_t red, uint8_t green, uint8_t blue); + void setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint16_t group_addr); + void setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t endpoint, uint16_t short_addr); + + void setLightColorSaturation(uint8_t value); + void setLightColorSaturation(uint8_t value, uint16_t group_addr); + void setLightColorSaturation(uint8_t value, uint8_t endpoint, uint16_t short_addr); + + void setLightColorHue(uint8_t value); + void setLightColorHue(uint8_t value, uint16_t group_addr); + void setLightColorHue(uint8_t value, uint8_t endpoint, uint16_t short_addr); + +private: + // save instance of the class in order to use it in static functions + static ZigbeeColorDimmerSwitch *_instance; + + void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req); + static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx); + static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx); + + void calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y); }; -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 692103709c8..108faee5dc3 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -1,142 +1,35 @@ -/* Zigbee Common Functions */ -#include "ZigbeeCore.h" -#include "Arduino.h" - +#include "ZigbeeLight.h" #if SOC_IEEE802154_SUPPORTED -// forward declaration of all implemented handlers -static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message); -static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message); -static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message); -static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message); -static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message); +ZigbeeLight::ZigbeeLight(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID; -// Zigbee action handlers -[[maybe_unused]] static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) { - esp_err_t ret = ESP_OK; - switch (callback_id) { - case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: ret = zb_attribute_set_handler((esp_zb_zcl_set_attr_value_message_t *)message); break; - case ESP_ZB_CORE_REPORT_ATTR_CB_ID: ret = zb_attribute_reporting_handler((esp_zb_zcl_report_attr_message_t *)message); break; - case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: ret = zb_cmd_read_attr_resp_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *)message); break; - case ESP_ZB_CORE_CMD_REPORT_CONFIG_RESP_CB_ID: ret = zb_configure_report_resp_handler((esp_zb_zcl_cmd_config_report_resp_message_t *)message); break; - case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break; - default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break; - } - return ret; + esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG(); + _cluster_list = esp_zb_on_off_light_clusters_create(&light_cfg); // use esp_zb_zcl_cluster_list_create() instead of esp_zb_on_off_light_clusters_create() + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, .app_device_version = 0}; } -static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message) { - if (!message) { - log_e("Empty message"); - } - if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->info.status); - } - - log_v( - "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id, - message->attribute.data.size - ); - - // List through all Zigbee EPs and call the callback function, with the message - for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { - if (message->info.dst_endpoint == (*it)->getEndpoint()){ - if(message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY){ - (*it)->zbIdentify(message); //method zbIdentify implemented in the common EP class - } - else { - (*it)->zbAttributeSet(message); //method zbAttributeSet must be implemented in specific EP class - } +//set attribude method -> methon overriden in child class +void ZigbeeLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { + //check the data and call right method + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { + _current_state = *(bool *)message->attribute.data.value; + lightChanged(); + } else { + log_w("Recieved message ignored. Attribute ID: %d not supported for On/Off Light", message->attribute.id); } + } else { + log_w("Recieved message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster); } - return ESP_OK; } -static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message) { - if (!message) { - log_e("Empty message"); - } - if (message->status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->status); - } - log_v( - "Received report from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->src_address.u.short_addr, message->src_endpoint, - message->dst_endpoint, message->cluster - ); - // List through all Zigbee EPs and call the callback function, with the message - for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { - if (message->dst_endpoint == (*it)->getEndpoint()) { - (*it)->zbAttributeRead(message->cluster, &message->attribute); //method zbAttributeRead must be implemented in specific EP class - } - } - return ESP_OK; -} - -static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) { - if (!message) { - log_e("Empty message"); - } - if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->info.status); - } - log_v( - "Read attribute response: from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->info.src_address.u.short_addr, - message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster - ); - - for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { - if (message->info.dst_endpoint == (*it)->getEndpoint()) { - esp_zb_zcl_read_attr_resp_variable_t *variable = message->variables; - while (variable) { - log_v( - "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", variable->status, message->info.cluster, - variable->attribute.id, variable->attribute.data.type, variable->attribute.data.value ? *(uint8_t *)variable->attribute.data.value : 0 - ); - if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) { - if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BASIC) { - (*it)->zbReadBasicCluster(&variable->attribute); //method zbReadBasicCluster implemented in the common EP class - } - else { - (*it)->zbAttributeRead(message->info.cluster, &variable->attribute); //method zbAttributeRead must be implemented in specific EP class - } - } - variable = variable->next; - } - } - } - return ESP_OK; -} - -static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message) { - if (!message) { - log_e("Empty message"); - } - if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->info.status); - } - esp_zb_zcl_config_report_resp_variable_t *variable = message->variables; - while (variable) { - log_v( - "Configure report response: status(%d), cluster(0x%x), direction(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->direction, - variable->attribute_id - ); - variable = variable->next; - } - return ESP_OK; -} - -static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message) { - if (!message) { - log_e("Empty message"); - } - if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->info.status); +void ZigbeeLight::lightChanged() { + if (_on_light_change) { + _on_light_change(_current_state); + } else { + log_w("No callback function set for light change"); } - log_v( - "Received default response: from address(0x%x), src_endpoint(%d) to dst_endpoint(%d), cluster(0x%x) with status 0x%x", message->info.src_address.u.short_addr, - message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster, message->status_code - ); - return ESP_OK; } -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp index 745b022ea82..32e4e8c9bdc 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp @@ -1,116 +1,33 @@ -#include "ZigbeeColorDimmableLight.h" -#if SOC_IEEE802154_SUPPORTED - -ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP(endpoint) { - _device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID; - - esp_zb_color_dimmable_light_cfg_t light_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG(); - _cluster_list = esp_zb_color_dimmable_light_clusters_create(&light_cfg); - _ep_config = { - .endpoint = _endpoint, - .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .app_device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID, - .app_device_version = 0 - }; +/* Class of Zigbee On/Off Light endpoint inherited from common EP class */ - //set default values - _current_state = false; - _current_level = 255; - _current_red = 255; - _current_green = 255; - _current_blue = 255; -} +#pragma once -uint16_t ZigbeeColorDimmableLight::getCurrentColorX(){ - return (*(uint16_t *)esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID)->data_p); -} - -uint16_t ZigbeeColorDimmableLight::getCurrentColorY(){ - return (*(uint16_t *)esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID)->data_p); -} - -void ZigbeeColorDimmableLight::calculateRGB( uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue){ - float r, g, b, color_x, color_y; - color_x = (float)x / 65535; - color_y = (float)y / 65535; - - float color_X = color_x / color_y; - float color_Z = (1 - color_x - color_y) / color_y; +#include "soc/soc_caps.h" +#if SOC_IEEE802154_SUPPORTED - XYZ_TO_RGB(color_X, 1, color_Z, r, g, b); +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" - red = (uint8_t)(r * (float)255); - green = (uint8_t)(g * (float)255); - blue = (uint8_t)(b * (float)255); -} +class ZigbeeLight : public ZigbeeEP { +public: + ZigbeeLight(uint8_t endpoint); + ~ZigbeeLight(); -//set attribude method -> methon overriden in child class -void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { - //check the data and call right method - if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { - if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { - if(_current_state != *(bool *)message->attribute.data.value) { - _current_state = *(bool *)message->attribute.data.value; - lightChanged(); - } - return; - } - else { - log_w("Recieved message ignored. Attribute ID: %d not supported for On/Off Light", message->attribute.id); - } - } - else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL) { - if (message->attribute.id == ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { - if (_current_level != *(uint8_t *)message->attribute.data.value) { - _current_level = *(uint8_t *)message->attribute.data.value; - lightChanged(); - } - return; - } - else { - log_w("Recieved message ignored. Attribute ID: %d not supported for Level Control", message->attribute.id); - //TODO: implement more attributes -> includes/zcl/esp_zigbee_zcl_level.h - } - } - else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL) { - if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { - uint16_t light_color_x = (*(uint16_t *)message->attribute.data.value); - uint16_t light_color_y = getCurrentColorY(); - //calculate RGB from XY and call setColor() - uint8_t red, green, blue; - calculateRGB(light_color_x, light_color_y, red, green, blue); - _current_blue = blue; - _current_green = green; - _current_red = red; - lightChanged(); - return; + // Use tp set a cb function to be called on light change + void onLightChange(void (*callback)(bool)) { + _on_light_change = callback; + } + void restoreLight() { + lightChanged(); + } - } - else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { - uint16_t light_color_x = getCurrentColorX(); - uint16_t light_color_y = (*(uint16_t *)message->attribute.data.value); - //calculate RGB from XY and call setColor() - uint8_t red, green, blue; - calculateRGB(light_color_x, light_color_y, red, green, blue); - _current_blue = blue; - _current_green = green; - _current_red = red; - lightChanged(); - return; - } - else { - log_w("Recieved message ignored. Attribute ID: %d not supported for Color Control", message->attribute.id); - } - } - else { - log_w("Recieved message ignored. Cluster ID: %d not supported for Color dimmable Light", message->info.cluster); - } -} +private: + void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; + //callback function to be called on light change + void (*_on_light_change)(bool); + void lightChanged(); -void ZigbeeColorDimmableLight::lightChanged() { - if(_on_light_change) { - _on_light_change(_current_state, _current_red, _current_green, _current_blue, _current_level); - } -} + bool _current_state; +}; -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h index b46aff75bee..9152732e376 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h @@ -1,37 +1,233 @@ -/* Class of Zigbee On/Off Light endpoint inherited from common EP class */ +#include "ZigbeeSwitch.h" +#if SOC_IEEE802154_SUPPORTED -#pragma once +// Initialize the static instance pointer +ZigbeeSwitch *ZigbeeSwitch::_instance = nullptr; -#include "soc/soc_caps.h" -#if SOC_IEEE802154_SUPPORTED +ZigbeeSwitch::ZigbeeSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID; + _instance = this; // Set the static pointer to this instance + + esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); + _cluster_list = esp_zb_on_off_switch_clusters_create(&switch_cfg); + + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID, .app_device_version = 0}; +} + +void ZigbeeSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_i("Bound successfully!"); + if (user_ctx) { + zb_device_params_t *light = (zb_device_params_t *)user_ctx; + log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); + _instance->_bound_devices.push_back(light); + } + _is_bound = true; + } +} + +void ZigbeeSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_d("Found light endpoint"); + esp_zb_zdo_bind_req_param_t bind_req; + zb_device_params_t *light = (zb_device_params_t *)malloc(sizeof(zb_device_params_t)); + light->endpoint = endpoint; + light->short_addr = addr; + esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); + esp_zb_get_long_address(bind_req.src_address); + bind_req.src_endp = _endpoint; //_dev_endpoint; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; + bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; + memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); + bind_req.dst_endp = endpoint; + bind_req.req_dst_addr = esp_zb_get_short_address(); + log_i("Try to bind On/Off"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light); + } else { + log_d("No light endpoint found"); + } +} + +// find on_off light endpoint +void ZigbeeSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) { + uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_ON_OFF}; + esp_zb_zdo_match_desc_req_param_t on_off_req = { + .dst_nwk_addr = cmd_req->dst_nwk_addr, + .addr_of_interest = cmd_req->addr_of_interest, + .profile_id = ESP_ZB_AF_HA_PROFILE_ID, + .num_in_clusters = 1, + .num_out_clusters = 1, + .cluster_list = cluster_list, + }; + + esp_zb_zdo_match_cluster(&on_off_req, findCb, NULL); +} + +// Methods to control the light +void ZigbeeSwitch::lightToggle() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command"); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} + +void ZigbeeSwitch::lightToggle(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command to group address 0x%x", group_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} + +void ZigbeeSwitch::lightToggle(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + log_i("Sending 'light toggle' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} + +void ZigbeeSwitch::lightOn() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command"); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} -#include "ZigbeeEP.h" -#include "ha/esp_zigbee_ha_standard.h" +void ZigbeeSwitch::lightOn(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command to group address 0x%x", group_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} -class ZigbeeColorDimmableLight : public ZigbeeEP { - public: - ZigbeeColorDimmableLight(uint8_t endpoint); - ~ZigbeeColorDimmableLight(); +void ZigbeeSwitch::lightOn(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + log_i("Sending 'light on' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} - void onLightChange(void (*callback)(bool, uint8_t, uint8_t, uint8_t, uint8_t)) { _on_light_change = callback; } - void restoreLight() { lightChanged(); } +void ZigbeeSwitch::lightOff() { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command"); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} - private: - void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; - void calculateRGB(uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue); +void ZigbeeSwitch::lightOff(uint16_t group_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command to group address 0x%x", group_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} - uint16_t getCurrentColorX(); - uint16_t getCurrentColorY(); +void ZigbeeSwitch::lightOff(uint8_t endpoint, uint16_t short_addr) { + if (_is_bound) { + esp_zb_zcl_on_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + log_i("Sending 'light off' command to endpoint %d, address 0x%x", endpoint, short_addr); + esp_zb_zcl_on_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} - void lightChanged(); - //callback function to be called on light change (State, R, G, B, Level) - void (*_on_light_change)(bool, uint8_t, uint8_t, uint8_t, uint8_t); +void ZigbeeSwitch::lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant) { + if (_is_bound) { + esp_zb_zcl_on_off_off_with_effect_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.effect_id = effect_id; + cmd_req.effect_variant = effect_variant; + log_i("Sending 'light off with effect' command"); + esp_zb_zcl_on_off_off_with_effect_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} - bool _current_state; - uint8_t _current_level; - uint16_t _current_red; - uint16_t _current_green; - uint16_t _current_blue; -}; +void ZigbeeSwitch::lightOnWithSceneRecall() { + if (_is_bound) { + esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + log_i("Sending 'light on with scene recall' command"); + esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} +void ZigbeeSwitch::lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off) { + if (_is_bound) { + esp_zb_zcl_on_off_on_with_timed_off_cmd_t cmd_req; + cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + cmd_req.on_off_control = on_off_control; //TODO: Test how it works, then maybe change API + cmd_req.on_time = time_on; + cmd_req.off_wait_time = time_off; + log_i("Sending 'light on with time off' command"); + esp_zb_zcl_on_off_on_with_timed_off_cmd_req(&cmd_req); + } else { + log_e("Light not bound"); + } +} -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp index 76a97d8e84c..bbc6c0a91dc 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp @@ -1,406 +1,42 @@ -#include "ZigbeeColorDimmerSwitch.h" -#if SOC_IEEE802154_SUPPORTED - -// Initialize the static instance pointer -ZigbeeColorDimmerSwitch* ZigbeeColorDimmerSwitch::_instance = nullptr; - -ZigbeeColorDimmerSwitch::ZigbeeColorDimmerSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) { - _device_id = ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID; - _instance = this; // Set the static pointer to this instance - - esp_zb_color_dimmable_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_SWITCH_CONFIG(); - _cluster_list = esp_zb_color_dimmable_switch_clusters_create(&switch_cfg); - - _ep_config = { - .endpoint = _endpoint, - .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .app_device_id = ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID, - .app_device_version = 0 - }; -} - -void ZigbeeColorDimmerSwitch::calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y) { - // Convert RGB to XYZ - float r = (float)red / 255.0f; - float g = (float)green / 255.0f; - float b = (float)blue / 255.0f; - - float X, Y, Z; - RGB_TO_XYZ(r, g, b, X, Y, Z); - - // Convert XYZ to xy chromaticity coordinates - float color_x = X / (X + Y + Z); - float color_y = Y / (X + Y + Z); - - // Convert normalized xy to 16-bit values - x = (uint16_t)(color_x * 65535.0f); - y = (uint16_t)(color_y * 65535.0f); -} - -void ZigbeeColorDimmerSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { - if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { - log_i("Bound successfully!"); - if (user_ctx) { - zb_device_params_t *light = (zb_device_params_t *)user_ctx; - log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); - _instance->_bound_devices.push_back(light); - } - _is_bound = true; - } else { - log_e("Binding failed!"); - } -} - -void ZigbeeColorDimmerSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { - if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { - log_d("Found light endpoint"); - esp_zb_zdo_bind_req_param_t bind_req; - zb_device_params_t *light = (zb_device_params_t *)malloc(sizeof(zb_device_params_t)); - light->endpoint = endpoint; - light->short_addr = addr; - esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); - esp_zb_get_long_address(bind_req.src_address); - bind_req.src_endp = _endpoint; - bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; - bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; - memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); - bind_req.dst_endp = endpoint; - bind_req.req_dst_addr = esp_zb_get_short_address(); - log_v("Try to bind on/off control of dimmable light"); - esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL); - bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL; - log_v("Try to bind level control of dimmable light"); - esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL); - bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL; - log_v("Try to bind color control of dimmable light"); - esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light); - } else { - log_v("No color dimmable light endpoint found"); - } -} - -// find on_off light endpoint -void ZigbeeColorDimmerSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) { - uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, - ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL}; - esp_zb_zdo_match_desc_req_param_t color_dimmable_light_req = { - .dst_nwk_addr = cmd_req->dst_nwk_addr, - .addr_of_interest = cmd_req->addr_of_interest, - .profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .num_in_clusters = 3, - .num_out_clusters = 3, - .cluster_list = cluster_list, - }; - esp_zb_zdo_match_cluster(&color_dimmable_light_req, findCb, NULL); -} +/* Class of Zigbee On/Off Switch endpoint inherited from common EP class */ -// Methods to control the light -void ZigbeeColorDimmerSwitch::lightToggle() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command"); - //esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - //esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} +#pragma once -void ZigbeeColorDimmerSwitch::lightToggle(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command to group address 0x%x", group_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightToggle(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOn() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOn(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command to group address 0x%x", group_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOn(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOff() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOff(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command to group address 0x%x", group_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOff(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant) { - if (_is_bound) { - esp_zb_zcl_on_off_off_with_effect_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.effect_id = effect_id; - cmd_req.effect_variant = effect_variant; - log_i("Sending 'light off with effect' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_off_with_effect_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOnWithSceneRecall() { - if (_is_bound) { - esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - log_i("Sending 'light on with scene recall' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off) { - if (_is_bound) { - esp_zb_zcl_on_off_on_with_timed_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_control = on_off_control; //TODO: Test how it works, then maybe change API - cmd_req.on_time = time_on; - cmd_req.off_wait_time = time_off; - log_i("Sending 'light on with time off' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_on_off_on_with_timed_off_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level) { - if (_is_bound) { - esp_zb_zcl_move_to_level_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.level = level; - cmd_req.transition_time = 0xffff; - log_i("Sending 'set light level' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} +#include "soc/soc_caps.h" +#if SOC_IEEE802154_SUPPORTED -void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level, uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_move_to_level_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.level = level; - cmd_req.transition_time = 0xffff; - log_i("Sending 'set light level' command to group address 0x%x", group_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" -void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level, uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_move_to_level_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.level = level; - cmd_req.transition_time = 0xffff; - log_i("Sending 'set light level' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_level_move_to_level_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} +class ZigbeeSwitch : public ZigbeeEP { +public: + ZigbeeSwitch(uint8_t endpoint); + ~ZigbeeSwitch(); -void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue) { - if (_is_bound) { - //Convert RGB to XY - uint16_t color_x, color_y; - calculateXY(red, green, blue, color_x, color_y); + // methods to control the on/off light + void lightToggle(); + void lightToggle(uint16_t group_addr); + void lightToggle(uint8_t endpoint, uint16_t short_addr); - esp_zb_zcl_color_move_to_color_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.color_x = color_x; - cmd_req.color_y = color_y; - cmd_req.transition_time = 0; - log_i("Sending 'set light color' command"); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} + void lightOn(); + void lightOn(uint16_t group_addr); + void lightOn(uint8_t endpoint, uint16_t short_addr); -void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint16_t group_addr) { - if (_is_bound) { - //Convert RGB to XY - uint16_t color_x, color_y; - calculateXY(red, green, blue, color_x, color_y); + void lightOff(); + void lightOff(uint16_t group_addr); + void lightOff(uint8_t endpoint, uint16_t short_addr); - esp_zb_zcl_color_move_to_color_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.color_x = color_x; - cmd_req.color_y = color_y; - cmd_req.transition_time = 0; - log_i("Sending 'set light color' command to group address 0x%x", group_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} + void lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant); + void lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off); + void lightOnWithSceneRecall(); -void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - //Convert RGB to XY - uint16_t color_x, color_y; - calculateXY(red, green, blue, color_x, color_y); +private: + // save instance of the class in order to use it in static functions + static ZigbeeSwitch *_instance; - esp_zb_zcl_color_move_to_color_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.color_x = color_x; - cmd_req.color_y = color_y; - cmd_req.transition_time = 0; - log_i("Sending 'set light color' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_lock_acquire(portMAX_DELAY); - esp_zb_zcl_color_move_to_color_cmd_req(&cmd_req); - esp_zb_lock_release(); - } else { - log_e("Light not bound"); - } -} + void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req); + static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx); + static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx); +}; -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h index fb75e2125c8..e0dba03da5c 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h @@ -1,60 +1,93 @@ -/* Class of Zigbee On/Off Switch endpoint inherited from common EP class */ - -#pragma once - -#include "soc/soc_caps.h" +#include "ZigbeeTempSensor.h" #if SOC_IEEE802154_SUPPORTED -#include "ZigbeeEP.h" -#include "ha/esp_zigbee_ha_standard.h" - -class ZigbeeColorDimmerSwitch : public ZigbeeEP { - public: - ZigbeeColorDimmerSwitch(uint8_t endpoint); - ~ZigbeeColorDimmerSwitch(); - - // methods to control the color dimmable light - void lightToggle(); - void lightToggle(uint16_t group_addr); - void lightToggle(uint8_t endpoint, uint16_t short_addr); - - void lightOn(); - void lightOn(uint16_t group_addr); - void lightOn(uint8_t endpoint, uint16_t short_addr); - - void lightOff(); - void lightOff(uint16_t group_addr); - void lightOff(uint8_t endpoint, uint16_t short_addr); - - void lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant); - void lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off); - void lightOnWithSceneRecall(); - - void setLightLevel(uint8_t level); - void setLightLevel(uint8_t level, uint16_t group_addr); - void setLightLevel(uint8_t level, uint8_t endpoint, uint16_t short_addr); - - void setLightColor(uint8_t red, uint8_t green, uint8_t blue); - void setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint16_t group_addr); - void setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t endpoint, uint16_t short_addr); - - void setLightColorSaturation(uint8_t value); - void setLightColorSaturation(uint8_t value, uint16_t group_addr); - void setLightColorSaturation(uint8_t value, uint8_t endpoint, uint16_t short_addr); - - void setLightColorHue(uint8_t value); - void setLightColorHue(uint8_t value, uint16_t group_addr); - void setLightColorHue(uint8_t value, uint8_t endpoint, uint16_t short_addr); - - private: - // save instance of the class in order to use it in static functions - static ZigbeeColorDimmerSwitch* _instance; - - void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req); - static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx); - static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx); - - void calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y); -}; - -#endif //SOC_IEEE802154_SUPPORTED +ZigbeeTempSensor::ZigbeeTempSensor(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID; + + esp_zb_temperature_sensor_cfg_t temp_sensor_cfg = ESP_ZB_DEFAULT_TEMPERATURE_SENSOR_CONFIG(); + _cluster_list = esp_zb_temperature_sensor_clusters_create(&temp_sensor_cfg); + + _ep_config = { + .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID, .app_device_version = 0 + }; +} + +static int16_t zb_temperature_to_s16(float temp) { + return (int16_t)(temp * 100); +} + +void ZigbeeTempSensor::setMinMaxValue(float min, float max) { + int16_t zb_min = zb_temperature_to_s16(min); + int16_t zb_max = zb_temperature_to_s16(max); + esp_zb_attribute_list_t *temp_measure_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_update_attr(temp_measure_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID, (void *)&zb_min); + esp_zb_cluster_update_attr(temp_measure_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID, (void *)&zb_max); +} + +void ZigbeeTempSensor::setTolerance(float tolerance) { + // Convert tolerance to ZCL uint16_t + uint16_t zb_tolerance = (uint16_t)(tolerance * 100); + esp_zb_attribute_list_t *temp_measure_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_temperature_meas_cluster_add_attr(temp_measure_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_TOLERANCE_ID, (void *)&zb_tolerance); +} + +void ZigbeeTempSensor::setReporting(uint16_t min_interval, uint16_t max_interval, float delta) { + esp_zb_zcl_reporting_info_t reporting_info = { + .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV, + .ep = _endpoint, + .cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, + .cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, + .attr_id = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, + .u = + { + .send_info = + { + .min_interval = min_interval, + .max_interval = max_interval, + .delta = + { + .u16 = (uint16_t)(delta * 100), // Convert delta to ZCL uint16_t + }, + .def_min_interval = min_interval, + .def_max_interval = max_interval, + }, + }, + .dst = + { + .profile_id = ESP_ZB_AF_HA_PROFILE_ID, + }, + .manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC, + }; + esp_zb_zcl_update_reporting_info(&reporting_info); +} + +void ZigbeeTempSensor::setTemperature(float temperature) { + int16_t zb_temperature = zb_temperature_to_s16(temperature); + log_v("Updating temperature sensor value..."); + /* Update temperature sensor measured value */ + log_d("Setting temperature to %d", zb_temperature); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &zb_temperature, false + ); + esp_zb_lock_release(); +} + +void ZigbeeTempSensor::reportTemperature() { + /* Send report attributes command */ + esp_zb_zcl_report_attr_cmd_t report_attr_cmd; + report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID; + report_attr_cmd.cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE; + report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; + report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint; + + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd); + esp_zb_lock_release(); + log_v("Temperature report sent"); +} + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeLight.cpp b/libraries/Zigbee/src/ep/ZigbeeLight.cpp index 75e72bbac35..22317721fc4 100644 --- a/libraries/Zigbee/src/ep/ZigbeeLight.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeLight.cpp @@ -1,42 +1,30 @@ -#include "ZigbeeLight.h" +/* Class of Zigbee Temperature sensor endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" #if SOC_IEEE802154_SUPPORTED -ZigbeeLight::ZigbeeLight(uint8_t endpoint) : ZigbeeEP(endpoint) { - _device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID; - - esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG(); - _cluster_list = esp_zb_on_off_light_clusters_create(&light_cfg); // use esp_zb_zcl_cluster_list_create() instead of esp_zb_on_off_light_clusters_create() - _ep_config = { - .endpoint = _endpoint, - .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, - .app_device_version = 0 - }; -} - -//set attribude method -> methon overriden in child class -void ZigbeeLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { - //check the data and call right method - if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) { - if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { - _current_state = *(bool *)message->attribute.data.value; - lightChanged(); - } - else { - log_w("Recieved message ignored. Attribute ID: %d not supported for On/Off Light", message->attribute.id); - } - } - else { - log_w("Recieved message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster); - } -} - -void ZigbeeLight::lightChanged() { - if(_on_light_change) { - _on_light_change(_current_state); - } else { - log_w("No callback function set for light change"); - } -} - -#endif //SOC_IEEE802154_SUPPORTED +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +class ZigbeeTempSensor : public ZigbeeEP { +public: + ZigbeeTempSensor(uint8_t endpoint); + ~ZigbeeTempSensor(); + + // Set the temperature value in 0,01°C + void setTemperature(float value); + + // Set the min and max value for the temperature sensor in 0,01°C + void setMinMaxValue(float min, float max); + + // Set the tolerance value for the temperature sensor in 0,01°C + void setTolerance(float tolerance); + + // Set the reporting interval for temperature measurement in seconds and delta (temp change in 0,01 °C) + void setReporting(uint16_t min_interval, uint16_t max_interval, float delta); + void reportTemperature(); +}; + +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeLight.h b/libraries/Zigbee/src/ep/ZigbeeLight.h index 20b1e71ea9b..28ed2a70cd2 100644 --- a/libraries/Zigbee/src/ep/ZigbeeLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeLight.h @@ -1,29 +1,205 @@ -/* Class of Zigbee On/Off Light endpoint inherited from common EP class */ +#include "ZigbeeThermostat.h" +#if SOC_IEEE802154_SUPPORTED -#pragma once +static float zb_s16_to_temperature(int16_t value) { + return 1.0 * value / 100; +} -#include "soc/soc_caps.h" -#if SOC_IEEE802154_SUPPORTED +// Initialize the static instance of the class +ZigbeeThermostat *ZigbeeThermostat::_instance = nullptr; + +ZigbeeThermostat::ZigbeeThermostat(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_THERMOSTAT_DEVICE_ID; + _instance = this; // Set the static pointer to this instance + + //use custom config to avoid narrowing error -> must be fixed in zigbee-sdk + esp_zb_thermostat_cfg_t thermostat_cfg = ZB_DEFAULT_THERMOSTAT_CONFIG(); + + //use custom cluster creating to accept reportings from temperature sensor + _cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_attribute_list_t *basic_cluster = esp_zb_basic_cluster_create(&(thermostat_cfg.basic_cfg)); + esp_zb_cluster_list_add_basic_cluster(_cluster_list, basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(_cluster_list, esp_zb_identify_cluster_create(&(thermostat_cfg.identify_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(_cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE); + esp_zb_cluster_list_add_thermostat_cluster(_cluster_list, esp_zb_thermostat_cluster_create(&(thermostat_cfg.thermostat_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + /* Add temperature measurement cluster for attribute reporting */ + esp_zb_cluster_list_add_temperature_meas_cluster(_cluster_list, esp_zb_temperature_meas_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE); + + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_THERMOSTAT_DEVICE_ID, .app_device_version = 0}; +} + +void ZigbeeThermostat::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + if (user_ctx) { + zb_device_params_t *sensor = (zb_device_params_t *)user_ctx; + log_i("The temperature sensor originating from address(0x%x) on endpoint(%d)", sensor->short_addr, sensor->endpoint); + _instance->_bound_devices.push_back(sensor); + } else { + log_v("Local binding success"); + } + _is_bound = true; + } else { + log_e("Binding failed!"); + } +} + +void ZigbeeThermostat::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + log_i("Found temperature sensor"); + esp_zb_zdo_bind_req_param_t bind_req; + /* Store the information of the remote device */ + zb_device_params_t *sensor = (zb_device_params_t *)malloc(sizeof(zb_device_params_t)); + sensor->endpoint = endpoint; + sensor->short_addr = addr; + esp_zb_ieee_address_by_short(sensor->short_addr, sensor->ieee_addr); + log_d("Temperature sensor found: short address(0x%x), endpoint(%d)", sensor->short_addr, sensor->endpoint); + + /* 1. Send binding request to the sensor */ + bind_req.req_dst_addr = addr; + log_d("Request temperature sensor to bind us"); + + /* populate the src information of the binding */ + memcpy(bind_req.src_address, sensor->ieee_addr, sizeof(esp_zb_ieee_addr_t)); + bind_req.src_endp = endpoint; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; + log_d("Bind temperature sensor"); + + /* populate the dst information of the binding */ + bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; + esp_zb_get_long_address(bind_req.dst_address_u.addr_long); + bind_req.dst_endp = _endpoint; + + log_i("Request temperature sensor to bind us"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, NULL); + + /* 2. Send binding request to self */ + bind_req.req_dst_addr = esp_zb_get_short_address(); + + /* populate the src information of the binding */ + esp_zb_get_long_address(bind_req.src_address); + bind_req.src_endp = _endpoint; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; + + /* populate the dst information of the binding */ + bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; + memcpy(bind_req.dst_address_u.addr_long, sensor->ieee_addr, sizeof(esp_zb_ieee_addr_t)); + bind_req.dst_endp = endpoint; + + log_i("Bind temperature sensor"); + esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)sensor); + } +} + +void ZigbeeThermostat::findEndpoint(esp_zb_zdo_match_desc_req_param_t *param) { + uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT}; + param->profile_id = ESP_ZB_AF_HA_PROFILE_ID; + param->num_in_clusters = 1; + param->num_out_clusters = 0; + param->cluster_list = cluster_list; + esp_zb_zdo_match_cluster(param, findCb, NULL); +} + +void ZigbeeThermostat::zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) { + static uint8_t read_config = 0; + if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT) { + if (attribute->id == ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S16) { + int16_t value = attribute->data.value ? *(int16_t *)attribute->data.value : 0; + if (_on_temp_recieve) { + _on_temp_recieve(zb_s16_to_temperature(value)); + } + } + if (attribute->id == ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S16) { + int16_t min_value = attribute->data.value ? *(int16_t *)attribute->data.value : 0; + _min_temp = zb_s16_to_temperature(min_value); + read_config++; + } + if (attribute->id == ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S16) { + int16_t max_value = attribute->data.value ? *(int16_t *)attribute->data.value : 0; + _max_temp = zb_s16_to_temperature(max_value); + read_config++; + } + if (attribute->id == ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_TOLERANCE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + uint16_t tolerance = attribute->data.value ? *(uint16_t *)attribute->data.value : 0; + _tolerance = 1.0 * tolerance / 100; + read_config++; + } + if (read_config == 3) { + read_config = 0; + xSemaphoreGive(lock); + } + } +} + +void ZigbeeThermostat::getTemperature() { + /* Send "read attributes" command to the bound sensor */ + esp_zb_zcl_read_attr_cmd_t read_req; + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; + + uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID}; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + log_i("Sending 'read temperature' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_read_attr_cmd_req(&read_req); + esp_zb_lock_release(); +} + +void ZigbeeThermostat::getSensorSettings() { + /* Send "read attributes" command to the bound sensor */ + esp_zb_zcl_read_attr_cmd_t read_req; + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; + + uint16_t attributes[] = { + ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_TOLERANCE_ID + }; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + log_i("Sending 'read temperature' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_read_attr_cmd_req(&read_req); + esp_zb_lock_release(); -#include "ZigbeeEP.h" -#include "ha/esp_zigbee_ha_standard.h" + //Take semaphore to wait for response of all attributes + if (xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE) { + log_e("Error while reading attributes"); + return; + } else { + //Call the callback function when all attributes are read + _on_config_recieve(_min_temp, _max_temp, _tolerance); + } +} -class ZigbeeLight : public ZigbeeEP { - public: - ZigbeeLight(uint8_t endpoint); - ~ZigbeeLight(); +void ZigbeeThermostat::setTemperatureReporting(uint16_t min_interval, uint16_t max_interval, float delta) { + /* Send "configure report attribute" command to the bound sensor */ + esp_zb_zcl_config_report_cmd_t report_cmd; + report_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + report_cmd.zcl_basic_cmd.src_endpoint = _endpoint; + report_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT; - // Use tp set a cb function to be called on light change - void onLightChange(void (*callback)(bool)) { _on_light_change = callback; } - void restoreLight() { lightChanged(); } + int16_t report_change = (int16_t)delta * 100; + esp_zb_zcl_config_report_record_t records[] = { + { + .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV, + .attributeID = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, + .attrType = ESP_ZB_ZCL_ATTR_TYPE_S16, + .min_interval = min_interval, + .max_interval = max_interval, + .reportable_change = &report_change, + }, + }; + report_cmd.record_number = ZB_ARRAY_LENTH(records); + report_cmd.record_field = records; - private: - void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; - //callback function to be called on light change - void (*_on_light_change)(bool); - void lightChanged(); - - bool _current_state; -}; + log_i("Sending 'configure reporting' command"); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_config_report_cmd_req(&report_cmd); + esp_zb_lock_release(); +} -#endif //SOC_IEEE802154_SUPPORTED +#endif //SOC_IEEE802154_SUPPORTED diff --git a/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp index 15b1ce89de0..7d63cd9f726 100644 --- a/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeSwitch.cpp @@ -1,238 +1,64 @@ -#include "ZigbeeSwitch.h" -#if SOC_IEEE802154_SUPPORTED - -// Initialize the static instance pointer -ZigbeeSwitch* ZigbeeSwitch::_instance = nullptr; - -ZigbeeSwitch::ZigbeeSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) { - _device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID; - _instance = this; // Set the static pointer to this instance - - esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); - _cluster_list = esp_zb_on_off_switch_clusters_create(&switch_cfg); - - _ep_config = { - .endpoint = _endpoint, - .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .app_device_id = ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID, - .app_device_version = 0 - }; -} - -void ZigbeeSwitch::bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { - if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { - log_i("Bound successfully!"); - if (user_ctx) { - zb_device_params_t *light = (zb_device_params_t *)user_ctx; - log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint); - _instance->_bound_devices.push_back(light); - } - _is_bound = true; - } -} - -void ZigbeeSwitch::findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { - if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { - log_d("Found light endpoint"); - esp_zb_zdo_bind_req_param_t bind_req; - zb_device_params_t *light = (zb_device_params_t *)malloc(sizeof(zb_device_params_t)); - light->endpoint = endpoint; - light->short_addr = addr; - esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); - esp_zb_get_long_address(bind_req.src_address); - bind_req.src_endp = _endpoint; //_dev_endpoint; - bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; - bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; - memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); - bind_req.dst_endp = endpoint; - bind_req.req_dst_addr = esp_zb_get_short_address(); - log_i("Try to bind On/Off"); - esp_zb_zdo_device_bind_req(&bind_req, bindCb, (void *)light); - } else { - log_d("No light endpoint found"); - } -} - -// find on_off light endpoint -void ZigbeeSwitch::findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) { - uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_ID_ON_OFF}; - esp_zb_zdo_match_desc_req_param_t on_off_req = { - .dst_nwk_addr = cmd_req->dst_nwk_addr, - .addr_of_interest = cmd_req->addr_of_interest, - .profile_id = ESP_ZB_AF_HA_PROFILE_ID, - .num_in_clusters = 1, - .num_out_clusters = 1, - .cluster_list = cluster_list, - }; - - esp_zb_zdo_match_cluster(&on_off_req, findCb, NULL); -} - -// Methods to control the light -void ZigbeeSwitch::lightToggle() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command"); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} +/* Class of Zigbee Temperature sensor endpoint inherited from common EP class */ -void ZigbeeSwitch::lightToggle(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command to group address 0x%x", group_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} +#pragma once -void ZigbeeSwitch::lightToggle(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; - log_i("Sending 'light toggle' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOn() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command"); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOn(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command to group address 0x%x", group_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOn(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; - log_i("Sending 'light on' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOff() { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command"); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOff(uint16_t group_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = group_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command to group address 0x%x", group_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOff(uint8_t endpoint, uint16_t short_addr) { - if (_is_bound) { - esp_zb_zcl_on_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.zcl_basic_cmd.dst_endpoint = endpoint; - cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = short_addr; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; - log_i("Sending 'light off' command to endpoint %d, address 0x%x", endpoint, short_addr); - esp_zb_zcl_on_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOffWithEffect(uint8_t effect_id, uint8_t effect_variant) { - if (_is_bound) { - esp_zb_zcl_on_off_off_with_effect_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.effect_id = effect_id; - cmd_req.effect_variant = effect_variant; - log_i("Sending 'light off with effect' command"); - esp_zb_zcl_on_off_off_with_effect_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} - -void ZigbeeSwitch::lightOnWithSceneRecall() { - if (_is_bound) { - esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - log_i("Sending 'light on with scene recall' command"); - esp_zb_zcl_on_off_on_with_recall_global_scene_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} -void ZigbeeSwitch::lightOnWithTimedOff(uint8_t on_off_control, uint16_t time_on, uint16_t time_off) { - if (_is_bound) { - esp_zb_zcl_on_off_on_with_timed_off_cmd_t cmd_req; - cmd_req.zcl_basic_cmd.src_endpoint = _endpoint; - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; - cmd_req.on_off_control = on_off_control; //TODO: Test how it works, then maybe change API - cmd_req.on_time = time_on; - cmd_req.off_wait_time = time_off; - log_i("Sending 'light on with time off' command"); - esp_zb_zcl_on_off_on_with_timed_off_cmd_req(&cmd_req); - } else { - log_e("Light not bound"); - } -} +#include "soc/soc_caps.h" +#if SOC_IEEE802154_SUPPORTED -#endif //SOC_IEEE802154_SUPPORTED +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +//define the thermostat configuration to avoid narrowing conversion issue in zigbee-sdk +#define ZB_DEFAULT_THERMOSTAT_CONFIG() \ + { \ + .basic_cfg = \ + { \ + .zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, \ + .power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_DEFAULT_VALUE, \ + }, \ + .identify_cfg = \ + { \ + .identify_time = ESP_ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, \ + }, \ + .thermostat_cfg = { \ + .local_temperature = (int16_t)ESP_ZB_ZCL_THERMOSTAT_LOCAL_TEMPERATURE_DEFAULT_VALUE, \ + .occupied_cooling_setpoint = ESP_ZB_ZCL_THERMOSTAT_OCCUPIED_COOLING_SETPOINT_DEFAULT_VALUE, \ + .occupied_heating_setpoint = ESP_ZB_ZCL_THERMOSTAT_OCCUPIED_HEATING_SETPOINT_DEFAULT_VALUE, \ + .control_sequence_of_operation = ESP_ZB_ZCL_THERMOSTAT_CONTROL_SEQ_OF_OPERATION_DEFAULT_VALUE, \ + .system_mode = ESP_ZB_ZCL_THERMOSTAT_CONTROL_SYSTEM_MODE_DEFAULT_VALUE, \ + }, \ + } +class ZigbeeThermostat : public ZigbeeEP { +public: + ZigbeeThermostat(uint8_t endpoint); + ~ZigbeeThermostat(); + + void onTempRecieve(void (*callback)(float)) { + _on_temp_recieve = callback; + } + void onConfigRecieve(void (*callback)(float, float, float)) { + _on_config_recieve = callback; + } + + void getTemperature(); + void getSensorSettings(); + void setTemperatureReporting(uint16_t min_interval, uint16_t max_interval, float delta); + +private: + // save instance of the class in order to use it in static functions + static ZigbeeThermostat *_instance; + + void (*_on_temp_recieve)(float); + void (*_on_config_recieve)(float, float, float); + float _min_temp; + float _max_temp; + float _tolerance; + + void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req); + static void bindCb(esp_zb_zdp_status_t zdo_status, void *user_ctx); + static void findCb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx); + + void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute) override; +}; + +#endif //SOC_IEEE802154_SUPPORTED