From 814371726d15c059062473d293672f56b3905680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:04:39 +0100 Subject: [PATCH 01/33] fix(zigbee): Fixes in handlers, destructors and co2 sensor delta reporting (#10834) * fix(zigbee): Fix co2 sensor delta in reporting * fix(zigbee): Declare default empty destructors * fix(zigbee): Return on error in zigbee handlers --- libraries/Zigbee/src/ZigbeeCore.cpp | 1 - libraries/Zigbee/src/ZigbeeCore.h | 2 +- libraries/Zigbee/src/ZigbeeEP.cpp | 2 -- libraries/Zigbee/src/ZigbeeEP.h | 2 +- libraries/Zigbee/src/ZigbeeHandlers.cpp | 10 ++++++++++ libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp | 4 +++- libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.h | 2 +- libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h | 2 +- libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h | 2 +- libraries/Zigbee/src/ep/ZigbeeDimmableLight.h | 2 +- libraries/Zigbee/src/ep/ZigbeeFlowSensor.h | 2 +- libraries/Zigbee/src/ep/ZigbeeLight.h | 2 +- libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h | 2 +- libraries/Zigbee/src/ep/ZigbeePressureSensor.h | 2 +- libraries/Zigbee/src/ep/ZigbeeSwitch.h | 2 +- libraries/Zigbee/src/ep/ZigbeeTempSensor.h | 2 +- libraries/Zigbee/src/ep/ZigbeeThermostat.h | 2 +- 17 files changed, 26 insertions(+), 17 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeCore.cpp b/libraries/Zigbee/src/ZigbeeCore.cpp index 0a3177919da..19f4d0872b8 100644 --- a/libraries/Zigbee/src/ZigbeeCore.cpp +++ b/libraries/Zigbee/src/ZigbeeCore.cpp @@ -29,7 +29,6 @@ ZigbeeCore::ZigbeeCore() { } } } -ZigbeeCore::~ZigbeeCore() {} //forward declaration static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message); diff --git a/libraries/Zigbee/src/ZigbeeCore.h b/libraries/Zigbee/src/ZigbeeCore.h index c72a60d81c4..02dce54e5ff 100644 --- a/libraries/Zigbee/src/ZigbeeCore.h +++ b/libraries/Zigbee/src/ZigbeeCore.h @@ -86,7 +86,7 @@ class ZigbeeCore { public: ZigbeeCore(); - ~ZigbeeCore(); + ~ZigbeeCore() {} std::list ep_objects; diff --git a/libraries/Zigbee/src/ZigbeeEP.cpp b/libraries/Zigbee/src/ZigbeeEP.cpp index 61f2fa8d2de..adc87540b83 100644 --- a/libraries/Zigbee/src/ZigbeeEP.cpp +++ b/libraries/Zigbee/src/ZigbeeEP.cpp @@ -27,8 +27,6 @@ ZigbeeEP::ZigbeeEP(uint8_t endpoint) { } } -ZigbeeEP::~ZigbeeEP() {} - void ZigbeeEP::setVersion(uint8_t version) { _ep_config.app_device_version = version; } diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index a5e9efa4283..d8ed900a7e6 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -55,7 +55,7 @@ typedef enum { class ZigbeeEP { public: ZigbeeEP(uint8_t endpoint = 10); - ~ZigbeeEP(); + ~ZigbeeEP() {} // Set ep config and cluster list void setEpConfig(esp_zb_endpoint_config_t ep_config, esp_zb_cluster_list_t *cluster_list) { diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 881d7ca0c37..e387b7ce432 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -29,9 +29,11 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message) { if (!message) { log_e("Empty message"); + return ESP_FAIL; } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; } log_v( @@ -55,9 +57,11 @@ static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_messag static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message) { if (!message) { log_e("Empty message"); + return ESP_FAIL; } if (message->status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->status); + return ESP_ERR_INVALID_ARG; } 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, @@ -75,9 +79,11 @@ static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_mes 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"); + return ESP_FAIL; } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; } 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, @@ -109,9 +115,11 @@ static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_re 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"); + return ESP_FAIL; } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; } esp_zb_zcl_config_report_resp_variable_t *variable = message->variables; while (variable) { @@ -127,9 +135,11 @@ static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_re 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"); + return ESP_FAIL; } if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; } log_v( "Received default response: from address(0x%x), src_endpoint(%d) to dst_endpoint(%d), cluster(0x%x) with status 0x%x", diff --git a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp index eabc3c7c194..5def43f4199 100644 --- a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp @@ -56,9 +56,11 @@ void ZigbeeCarbonDioxideSensor::setReporting(uint16_t min_interval, uint16_t max reporting_info.u.send_info.max_interval = max_interval; reporting_info.u.send_info.def_min_interval = min_interval; reporting_info.u.send_info.def_max_interval = max_interval; - reporting_info.u.send_info.delta.u16 = delta; reporting_info.dst.profile_id = ESP_ZB_AF_HA_PROFILE_ID; reporting_info.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC; + float delta_f = delta / 1000000.0f; + memcpy(&reporting_info.u.send_info.delta.s32, &delta_f, sizeof(float)); + esp_zb_lock_acquire(portMAX_DELAY); esp_zb_zcl_update_reporting_info(&reporting_info); esp_zb_lock_release(); diff --git a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.h b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.h index 71c353d4695..7744fd02f00 100644 --- a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.h +++ b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.h @@ -39,7 +39,7 @@ typedef struct zigbee_carbon_dioxide_sensor_cfg_s { class ZigbeeCarbonDioxideSensor : public ZigbeeEP { public: ZigbeeCarbonDioxideSensor(uint8_t endpoint); - ~ZigbeeCarbonDioxideSensor(); + ~ZigbeeCarbonDioxideSensor() {} // Set the carbon dioxide value in ppm void setCarbonDioxide(float carbon_dioxide); diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h index 9fa59dcfffc..dad267c7b39 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h @@ -12,7 +12,7 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { public: ZigbeeColorDimmableLight(uint8_t endpoint); - ~ZigbeeColorDimmableLight(); + ~ZigbeeColorDimmableLight() {} void onLightChange(void (*callback)(bool, uint8_t, uint8_t, uint8_t, uint8_t)) { _on_light_change = callback; diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h index 8e2a4d9e1a3..ca67fb4ba62 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.h @@ -12,7 +12,7 @@ class ZigbeeColorDimmerSwitch : public ZigbeeEP { public: ZigbeeColorDimmerSwitch(uint8_t endpoint); - ~ZigbeeColorDimmerSwitch(); + ~ZigbeeColorDimmerSwitch() {} // methods to control the color dimmable light void lightToggle(); diff --git a/libraries/Zigbee/src/ep/ZigbeeDimmableLight.h b/libraries/Zigbee/src/ep/ZigbeeDimmableLight.h index 034c34899b4..6f92c3315e4 100644 --- a/libraries/Zigbee/src/ep/ZigbeeDimmableLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeDimmableLight.h @@ -67,7 +67,7 @@ typedef struct zigbee_dimmable_light_cfg_s { class ZigbeeDimmableLight : public ZigbeeEP { public: ZigbeeDimmableLight(uint8_t endpoint); - ~ZigbeeDimmableLight(); + ~ZigbeeDimmableLight() {} void onLightChange(void (*callback)(bool, uint8_t)) { _on_light_change = callback; diff --git a/libraries/Zigbee/src/ep/ZigbeeFlowSensor.h b/libraries/Zigbee/src/ep/ZigbeeFlowSensor.h index 3514e2fcc1b..7d5ec26f7ec 100644 --- a/libraries/Zigbee/src/ep/ZigbeeFlowSensor.h +++ b/libraries/Zigbee/src/ep/ZigbeeFlowSensor.h @@ -39,7 +39,7 @@ typedef struct zigbee_flow_sensor_cfg_s { class ZigbeeFlowSensor : public ZigbeeEP { public: ZigbeeFlowSensor(uint8_t endpoint); - ~ZigbeeFlowSensor(); + ~ZigbeeFlowSensor() {} // Set the flow value in 0,1 m3/h void setFlow(float value); diff --git a/libraries/Zigbee/src/ep/ZigbeeLight.h b/libraries/Zigbee/src/ep/ZigbeeLight.h index 9b8fc409d4a..8cf8c35f781 100644 --- a/libraries/Zigbee/src/ep/ZigbeeLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeLight.h @@ -12,7 +12,7 @@ class ZigbeeLight : public ZigbeeEP { public: ZigbeeLight(uint8_t endpoint); - ~ZigbeeLight(); + ~ZigbeeLight() {} // Use to set a cb function to be called on light change void onLightChange(void (*callback)(bool)) { diff --git a/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h b/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h index e4b25c063e5..40c5eddbbdd 100644 --- a/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h +++ b/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h @@ -39,7 +39,7 @@ typedef struct zigbee_occupancy_sensor_cfg_s { class ZigbeeOccupancySensor : public ZigbeeEP { public: ZigbeeOccupancySensor(uint8_t endpoint); - ~ZigbeeOccupancySensor(); + ~ZigbeeOccupancySensor() {} // Set the occupancy value. True for occupied, false for unoccupied void setOccupancy(bool occupied); diff --git a/libraries/Zigbee/src/ep/ZigbeePressureSensor.h b/libraries/Zigbee/src/ep/ZigbeePressureSensor.h index f088c033bb9..2d72ef04c06 100644 --- a/libraries/Zigbee/src/ep/ZigbeePressureSensor.h +++ b/libraries/Zigbee/src/ep/ZigbeePressureSensor.h @@ -39,7 +39,7 @@ typedef struct zigbee_pressure_sensor_cfg_s { class ZigbeePressureSensor : public ZigbeeEP { public: ZigbeePressureSensor(uint8_t endpoint); - ~ZigbeePressureSensor(); + ~ZigbeePressureSensor() {} // Set the pressure value in 1 hPa void setPressure(int16_t value); diff --git a/libraries/Zigbee/src/ep/ZigbeeSwitch.h b/libraries/Zigbee/src/ep/ZigbeeSwitch.h index 62264641378..b638bfe823a 100644 --- a/libraries/Zigbee/src/ep/ZigbeeSwitch.h +++ b/libraries/Zigbee/src/ep/ZigbeeSwitch.h @@ -12,7 +12,7 @@ class ZigbeeSwitch : public ZigbeeEP { public: ZigbeeSwitch(uint8_t endpoint); - ~ZigbeeSwitch(); + ~ZigbeeSwitch() {} // methods to control the on/off light void lightToggle(); diff --git a/libraries/Zigbee/src/ep/ZigbeeTempSensor.h b/libraries/Zigbee/src/ep/ZigbeeTempSensor.h index 2951d4b7628..e610ff8d356 100644 --- a/libraries/Zigbee/src/ep/ZigbeeTempSensor.h +++ b/libraries/Zigbee/src/ep/ZigbeeTempSensor.h @@ -12,7 +12,7 @@ class ZigbeeTempSensor : public ZigbeeEP { public: ZigbeeTempSensor(uint8_t endpoint); - ~ZigbeeTempSensor(); + ~ZigbeeTempSensor() {} // Set the temperature value in 0,01°C void setTemperature(float value); diff --git a/libraries/Zigbee/src/ep/ZigbeeThermostat.h b/libraries/Zigbee/src/ep/ZigbeeThermostat.h index fe797ffd7b6..669ed9ab50a 100644 --- a/libraries/Zigbee/src/ep/ZigbeeThermostat.h +++ b/libraries/Zigbee/src/ep/ZigbeeThermostat.h @@ -32,7 +32,7 @@ class ZigbeeThermostat : public ZigbeeEP { public: ZigbeeThermostat(uint8_t endpoint); - ~ZigbeeThermostat(); + ~ZigbeeThermostat() {} void onTempRecieve(void (*callback)(float)) { _on_temp_recieve = callback; From a81e2d48f57c0a4c0b90c4a06596904d0905805a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:05:16 +0100 Subject: [PATCH 02/33] fix(i2s): Check if pin is used before clearing bus (#10833) * fix(i2s): Check if pin is used before clearing bus * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- libraries/ESP_I2S/src/ESP_I2S.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/ESP_I2S/src/ESP_I2S.cpp b/libraries/ESP_I2S/src/ESP_I2S.cpp index 0ac1e176dc8..d0ceb0c4b4c 100644 --- a/libraries/ESP_I2S/src/ESP_I2S.cpp +++ b/libraries/ESP_I2S/src/ESP_I2S.cpp @@ -719,9 +719,15 @@ bool I2SClass::end() { #if SOC_I2S_SUPPORTS_TDM case I2S_MODE_TDM: #endif - perimanClearPinBus(_mclk); - perimanClearPinBus(_bclk); - perimanClearPinBus(_ws); + if (_mclk >= 0) { + perimanClearPinBus(_mclk); + } + if (_bclk >= 0) { + perimanClearPinBus(_bclk); + } + if (_ws >= 0) { + perimanClearPinBus(_ws); + } if (_dout >= 0) { perimanClearPinBus(_dout); } From 4c36c897686ddca4dc6c0778c48cd368713e4f00 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 13 Jan 2025 07:36:03 -0300 Subject: [PATCH 03/33] fix(matter): serial print instead of log_i (#10842) * fix(matter): serial print instead of log_i * fix(example): adds a white space to the message --- .../examples/MatterMinimum/MatterMinimum.ino | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/Matter/examples/MatterMinimum/MatterMinimum.ino b/libraries/Matter/examples/MatterMinimum/MatterMinimum.ino index fa2599df6dd..db591ee2226 100644 --- a/libraries/Matter/examples/MatterMinimum/MatterMinimum.ino +++ b/libraries/Matter/examples/MatterMinimum/MatterMinimum.ino @@ -55,6 +55,8 @@ bool onOffLightCallback(bool state) { } void setup() { + Serial.begin(115200); + // Initialize the USER BUTTON (Boot button) that will be used to decommission the Matter Node pinMode(buttonPin, INPUT_PULLUP); // Initialize the LED GPIO @@ -63,9 +65,14 @@ void setup() { // Manually connect to WiFi WiFi.begin(ssid, password); // Wait for connection + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); while (WiFi.status() != WL_CONNECTED) { + Serial.print('.'); delay(500); } + Serial.println(); // Initialize at least one Matter EndPoint OnOffLight.begin(); @@ -77,11 +84,11 @@ void setup() { Matter.begin(); if (!Matter.isDeviceCommissioned()) { - log_i("Matter Node is not commissioned yet."); - log_i("Initiate the device discovery in your Matter environment."); - log_i("Commission it to your Matter hub with the manual pairing code or QR code"); - log_i("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str()); - log_i("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str()); + Serial.println("Matter Node is not commissioned yet."); + Serial.println("Initiate the device discovery in your Matter environment."); + Serial.println("Commission it to your Matter hub with the manual pairing code or QR code"); + Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str()); + Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str()); } } From 0773dd7619b1117bd59a068b69a76cdb22484f7c Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 13 Jan 2025 07:40:14 -0300 Subject: [PATCH 04/33] Bugfix/include order (#10841) * fix(arduino): include order pins_arduino first * fix(arduino): include order pins_arduino first * fix(arduino): include order pins_arduino first --- cores/esp32/Arduino.h | 9 ++++----- cores/esp32/FirmwareMSC.cpp | 2 +- cores/esp32/esp32-hal-gpio.h | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index ab7e497dcf6..d21089cc3fe 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -33,7 +33,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" -#include "esp32-hal.h" #include "esp8266-compat.h" #include "soc/gpio_reg.h" @@ -41,6 +40,10 @@ #include "binary.h" #include "extra_attr.h" +#include "pins_arduino.h" +#include "io_pin_remap.h" +#include "esp32-hal.h" + #define PI 3.1415926535897932384626433832795 #define HALF_PI 1.5707963267948966192313216916398 #define TWO_PI 6.283185307179586476925286766559 @@ -248,8 +251,4 @@ void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0); void noTone(uint8_t _pin); #endif /* __cplusplus */ - -#include "pins_arduino.h" -#include "io_pin_remap.h" - #endif /* _ESP32_CORE_ARDUINO_H_ */ diff --git a/cores/esp32/FirmwareMSC.cpp b/cores/esp32/FirmwareMSC.cpp index c408f52fcdb..2e944ad4df3 100644 --- a/cores/esp32/FirmwareMSC.cpp +++ b/cores/esp32/FirmwareMSC.cpp @@ -19,8 +19,8 @@ #include "esp_partition.h" #include "esp_ota_ops.h" #include "esp_image_format.h" -#include "esp32-hal.h" #include "pins_arduino.h" +#include "esp32-hal.h" #include "firmware_msc_fat.h" #include "spi_flash_mmap.h" diff --git a/cores/esp32/esp32-hal-gpio.h b/cores/esp32/esp32-hal-gpio.h index 4547d90cc56..9fce4368c22 100644 --- a/cores/esp32/esp32-hal-gpio.h +++ b/cores/esp32/esp32-hal-gpio.h @@ -24,9 +24,9 @@ extern "C" { #endif +#include "pins_arduino.h" #include "esp32-hal.h" #include "soc/soc_caps.h" -#include "pins_arduino.h" #include "driver/gpio.h" #if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) From fcce93a02949c50ce7ab565884b933fb474b3437 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:14:41 -0300 Subject: [PATCH 05/33] fix(example): Fix partition for PathArgServer --- libraries/WebServer/examples/PathArgServer/ci.json | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/WebServer/examples/PathArgServer/ci.json b/libraries/WebServer/examples/PathArgServer/ci.json index 618e46bd244..cbdd28f773d 100644 --- a/libraries/WebServer/examples/PathArgServer/ci.json +++ b/libraries/WebServer/examples/PathArgServer/ci.json @@ -1,4 +1,5 @@ { + "fqbn_append": "PartitionScheme=huge_app", "requires_any": [ "CONFIG_SOC_WIFI_SUPPORTED=y", "CONFIG_ESP_WIFI_REMOTE_ENABLED=y" From 01d9726609cbb1425431dd26f5dda2ca4418b4e6 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:46:31 -0300 Subject: [PATCH 06/33] docs(discord): Replace Gitter links with Discord (#10852) * docs(discord): Replace Gitter links with Discord * docs(links): Add Developer Portal link --- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- README.md | 4 +++- docs/en/getting_started.rst | 8 +++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 03b3a76df1e..e879b09bec2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Arduino ESP32 Gitter Channel - url: https://gitter.im/espressif/arduino-esp32 - about: Community channel for questions and help + - name: Arduino Core for Espressif Discord Server + url: https://discord.gg/8xY6e9crwv + about: Community Discord server for questions and help - name: ESP32 Forum - Arduino url: https://esp32.com/viewforum.php?f=19 about: Official Forum for questions diff --git a/README.md b/README.md index e610229fc60..0a9a6d0440c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ [![External Libraries Test](https://img.shields.io/github/actions/workflow/status/espressif/arduino-esp32/lib.yml?branch=master&event=schedule&label=External%20Libraries%20Test)](https://github.com/espressif/arduino-esp32/blob/gh-pages/LIBRARIES_TEST.md) [![Runtime Tests](https://github.com/espressif/arduino-esp32/blob/gh-pages/runtime-tests-results/badge.svg)](https://github.com/espressif/arduino-esp32/actions/workflows/tests_results.yml) -### Need help or have a question? Join the chat at [Gitter](https://gitter.im/espressif/arduino-esp32) or [open a new Discussion](https://github.com/espressif/arduino-esp32/discussions) +### Need help or have a question? Join the chat at [Discord](https://discord.gg/8xY6e9crwv) or [open a new Discussion](https://github.com/espressif/arduino-esp32/discussions) + +[![Discord invite](https://img.shields.io/discord/1327272229427216425?logo=discord&logoColor=white&logoSize=auto&label=Discord)](https://discord.gg/8xY6e9crwv) ## Contents diff --git a/docs/en/getting_started.rst b/docs/en/getting_started.rst index 8d312317bdf..cabb2ddc805 100644 --- a/docs/en/getting_started.rst +++ b/docs/en/getting_started.rst @@ -102,7 +102,8 @@ Here are some community channels where you may find information and ask for some - `ESP32 Forum`_: Official Espressif Forum. - `ESP32 Forum - Arduino`_: Official Espressif Forum for Arduino related discussions. - `ESP32 Forum - Hardware`_: Official Espressif Forum for Hardware related discussions. -- `Gitter`_ +- `Espressif Developer Portal`_: Official Espressif Developer Portal with tutorials, examples, workshops, and more. +- `Arduino Core for Espressif (Discord)`_: Official Espressif Discord channel for the Arduino Core. - `Espressif MCUs (Discord)`_ - `ESP32 on Reddit`_ @@ -148,12 +149,13 @@ Resources .. _Espressif Systems: https://www.espressif.com .. _Espressif Product Selector: https://products.espressif.com/ +.. _Espressif Developer Portal: https://developer.espressif.com/ .. _Arduino.cc: https://www.arduino.cc/en/Main/Software .. _Arduino Reference: https://www.arduino.cc/reference/en/ .. _ESP32 Forum: https://esp32.com .. _ESP32 Forum - Arduino: https://esp32.com/viewforum.php?f=19 .. _ESP32 Forum - Hardware: https://esp32.com/viewforum.php?f=12 -.. _Gitter: https://gitter.im/espressif/arduino-esp32 +.. _Arduino Core for Espressif (Discord): https://discord.gg/8xY6e9crwv .. _Adafruit (Discord): https://discord.gg/adafruit -.. _Espressif MCUs (Discord): https://discord.gg/nKxMTnkD +.. _Espressif MCUs (Discord): https://discord.com/invite/XqnZPbF .. _ESP32 on Reddit: https://www.reddit.com/r/esp32 From 2a2b81ad6fe6318badbe8751fae7b64df484711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:35:27 +0100 Subject: [PATCH 07/33] feat(zigbee): Add 2MB + 8MB partitions (#10864) --- boards.txt | 30 ++++++++++++++++++++++------ tools/partitions/zigbee_2MB.csv | 2 +- tools/partitions/zigbee_8MB.csv | 9 +++++++++ tools/partitions/zigbee_zczr_2MB.csv | 8 ++++++++ tools/partitions/zigbee_zczr_8MB.csv | 10 ++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 tools/partitions/zigbee_8MB.csv create mode 100644 tools/partitions/zigbee_zczr_2MB.csv create mode 100644 tools/partitions/zigbee_zczr_8MB.csv diff --git a/boards.txt b/boards.txt index 909874fc6d4..8e9655cda48 100644 --- a/boards.txt +++ b/boards.txt @@ -450,15 +450,24 @@ esp32h2.menu.PartitionScheme.app3M_fat9M_16MB.upload.maximum_size=3145728 #esp32h2.menu.PartitionScheme.rainmaker_8MB=RainMaker 8MB #esp32h2.menu.PartitionScheme.rainmaker_8MB.build.partitions=rainmaker_8MB #esp32h2.menu.PartitionScheme.rainmaker_8MB.upload.maximum_size=4116480 -esp32h2.menu.PartitionScheme.zigbee=Zigbee 4MB with spiffs -esp32h2.menu.PartitionScheme.zigbee.build.partitions=zigbee -esp32h2.menu.PartitionScheme.zigbee.upload.maximum_size=1310720 esp32h2.menu.PartitionScheme.zigbee_2MB=Zigbee 2MB with spiffs esp32h2.menu.PartitionScheme.zigbee_2MB.build.partitions=zigbee_2MB esp32h2.menu.PartitionScheme.zigbee_2MB.upload.maximum_size=1310720 +esp32h2.menu.PartitionScheme.zigbee=Zigbee 4MB with spiffs +esp32h2.menu.PartitionScheme.zigbee.build.partitions=zigbee +esp32h2.menu.PartitionScheme.zigbee.upload.maximum_size=1310720 +esp32h2.menu.PartitionScheme.zigbee_8MB=Zigbee 8MB with spiffs +esp32h2.menu.PartitionScheme.zigbee_8MB.build.partitions=zigbee_8MB +esp32h2.menu.PartitionScheme.zigbee_8MB.upload.maximum_size=3407872 +esp32h2.menu.PartitionScheme.zigbee_zczr_2MB=Zigbee ZCZR 2MB with spiffs +esp32h2.menu.PartitionScheme.zigbee_zczr_2MB.build.partitions=zigbee_zczr_2MB +esp32h2.menu.PartitionScheme.zigbee_zczr_2MB.upload.maximum_size=1310720 esp32h2.menu.PartitionScheme.zigbee_zczr=Zigbee ZCZR 4MB with spiffs esp32h2.menu.PartitionScheme.zigbee_zczr.build.partitions=zigbee_zczr esp32h2.menu.PartitionScheme.zigbee_zczr.upload.maximum_size=1310720 +esp32h2.menu.PartitionScheme.zigbee_zczr_8MB=Zigbee ZCZR 8MB with spiffs +esp32h2.menu.PartitionScheme.zigbee_zczr_8MB.build.partitions=zigbee_zczr_8MB +esp32h2.menu.PartitionScheme.zigbee_zczr_8MB.upload.maximum_size=3407872 esp32h2.menu.PartitionScheme.custom=Custom esp32h2.menu.PartitionScheme.custom.build.partitions= esp32h2.menu.PartitionScheme.custom.upload.maximum_size=16777216 @@ -645,15 +654,24 @@ esp32c6.menu.PartitionScheme.rainmaker_4MB.upload.maximum_size=4038656 esp32c6.menu.PartitionScheme.rainmaker_8MB=RainMaker 8MB esp32c6.menu.PartitionScheme.rainmaker_8MB.build.partitions=rainmaker_8MB esp32c6.menu.PartitionScheme.rainmaker_8MB.upload.maximum_size=4116480 -esp32c6.menu.PartitionScheme.zigbee=Zigbee 4MB with spiffs -esp32c6.menu.PartitionScheme.zigbee.build.partitions=zigbee -esp32c6.menu.PartitionScheme.zigbee.upload.maximum_size=1310720 esp32c6.menu.PartitionScheme.zigbee_2MB=Zigbee 2MB with spiffs esp32c6.menu.PartitionScheme.zigbee_2MB.build.partitions=zigbee_2MB esp32c6.menu.PartitionScheme.zigbee_2MB.upload.maximum_size=1310720 +esp32c6.menu.PartitionScheme.zigbee=Zigbee 4MB with spiffs +esp32c6.menu.PartitionScheme.zigbee.build.partitions=zigbee +esp32c6.menu.PartitionScheme.zigbee.upload.maximum_size=1310720 +esp32c6.menu.PartitionScheme.zigbee_8MB=Zigbee 8MB with spiffs +esp32c6.menu.PartitionScheme.zigbee_8MB.build.partitions=zigbee_8MB +esp32c6.menu.PartitionScheme.zigbee_8MB.upload.maximum_size=3407872 +esp32c6.menu.PartitionScheme.zigbee_zczr_2MB=Zigbee ZCZR 2MB with spiffs +esp32c6.menu.PartitionScheme.zigbee_zczr_2MB.build.partitions=zigbee_zczr_2MB +esp32c6.menu.PartitionScheme.zigbee_zczr_2MB.upload.maximum_size=1310720 esp32c6.menu.PartitionScheme.zigbee_zczr=Zigbee ZCZR 4MB with spiffs esp32c6.menu.PartitionScheme.zigbee_zczr.build.partitions=zigbee_zczr esp32c6.menu.PartitionScheme.zigbee_zczr.upload.maximum_size=1310720 +esp32c6.menu.PartitionScheme.zigbee_zczr_8MB=Zigbee ZCZR 8MB with spiffs +esp32c6.menu.PartitionScheme.zigbee_zczr_8MB.build.partitions=zigbee_zczr_8MB +esp32c6.menu.PartitionScheme.zigbee_zczr_8MB.upload.maximum_size=3407872 esp32c6.menu.PartitionScheme.custom=Custom esp32c6.menu.PartitionScheme.custom.build.partitions= esp32c6.menu.PartitionScheme.custom.upload.maximum_size=16777216 diff --git a/tools/partitions/zigbee_2MB.csv b/tools/partitions/zigbee_2MB.csv index 18adb6358bd..7034f9bd49b 100644 --- a/tools/partitions/zigbee_2MB.csv +++ b/tools/partitions/zigbee_2MB.csv @@ -1,6 +1,6 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, -factory, app, factory, 0x10000, 0x140000, +factory, app, factory, 0x10000, 0x140000, spiffs, data, spiffs, 0x150000,0x9B000, zb_storage, data, fat, 0x1EB000,0x4000, zb_fct, data, fat, 0x1EF000,0x1000, diff --git a/tools/partitions/zigbee_8MB.csv b/tools/partitions/zigbee_8MB.csv new file mode 100644 index 00000000000..fdf46fb59d1 --- /dev/null +++ b/tools/partitions/zigbee_8MB.csv @@ -0,0 +1,9 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x340000, +app1, app, ota_1, 0x350000,0x340000, +spiffs, data, spiffs, 0x690000,0x15B000, +zb_storage, data, fat, 0x7EB000,0x4000, +zb_fct, data, fat, 0x7EF000,0x1000, +coredump, data, coredump,0x7F0000,0x10000, diff --git a/tools/partitions/zigbee_zczr_2MB.csv b/tools/partitions/zigbee_zczr_2MB.csv new file mode 100644 index 00000000000..10484eeed87 --- /dev/null +++ b/tools/partitions/zigbee_zczr_2MB.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +factory, app, factory, 0x10000, 0x140000, +spiffs, data, spiffs, 0x150000,0x9A000, +zb_storage, data, fat, 0x1EA000,0x4000, +zb_fct, data, fat, 0x1EE000,0x1000, +rcp_fw, data, spiffs, 0x1EF000,0x1000, +coredump, data, coredump,0x1F0000,0x10000, diff --git a/tools/partitions/zigbee_zczr_8MB.csv b/tools/partitions/zigbee_zczr_8MB.csv new file mode 100644 index 00000000000..70dd680dc1a --- /dev/null +++ b/tools/partitions/zigbee_zczr_8MB.csv @@ -0,0 +1,10 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x340000, +app1, app, ota_1, 0x350000,0x340000, +spiffs, data, spiffs, 0x690000,0x15A000, +zb_storage, data, fat, 0x7EA000,0x4000, +zb_fct, data, fat, 0x7EE000,0x1000, +rcp_fw, data, spiffs, 0x7EF000,0x1000, +coredump, data, coredump,0x7F0000,0x10000, From 496b8411773243e1ad88a68652d6982ba2366d6b Mon Sep 17 00:00:00 2001 From: is-qian <2716275834@qq.com> Date: Tue, 21 Jan 2025 19:24:00 +0800 Subject: [PATCH 08/33] feat(esp32): Added a new device(xiao esp32s3 plus) (#10768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(esp32): Added a new device(xiao esp32s3 plus) * Update boards.txt * Apply suggestions from code review * Fix filename error. * Fix filename error. * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: Jan Procházka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- boards.txt | 178 ++++++++++++++++++ .../XIAO_ESP32S3_Plus/bootloader-tinyuf2.bin | Bin 0 -> 21408 bytes variants/XIAO_ESP32S3_Plus/partitions-8MB.csv | 10 + variants/XIAO_ESP32S3_Plus/pins_arduino.h | 90 +++++++++ variants/XIAO_ESP32S3_Plus/tinyuf2.bin | Bin 0 -> 141936 bytes 5 files changed, 278 insertions(+) create mode 100644 variants/XIAO_ESP32S3_Plus/bootloader-tinyuf2.bin create mode 100644 variants/XIAO_ESP32S3_Plus/partitions-8MB.csv create mode 100644 variants/XIAO_ESP32S3_Plus/pins_arduino.h create mode 100644 variants/XIAO_ESP32S3_Plus/tinyuf2.bin diff --git a/boards.txt b/boards.txt index 8e9655cda48..9ecd6359759 100644 --- a/boards.txt +++ b/boards.txt @@ -35211,6 +35211,184 @@ XIAO_ESP32S3.menu.EraseFlash.all.upload.erase_cmd=-e ############################################################## +XIAO_ESP32S3_Plus.name=XIAO_ESP32S3_PLUS +XIAO_ESP32S3_Plus.vid.0=0x2886 +XIAO_ESP32S3_Plus.pid.0=0x0063 +XIAO_ESP32S3_Plus.vid.1=0x2886 +XIAO_ESP32S3_Plus.pid.1=0x8063 + +XIAO_ESP32S3_Plus.bootloader.tool=esptool_py +XIAO_ESP32S3_Plus.bootloader.tool.default=esptool_py + +XIAO_ESP32S3_Plus.upload.tool=esptool_py +XIAO_ESP32S3_Plus.upload.tool.default=esptool_py +XIAO_ESP32S3_Plus.upload.tool.network=esp_ota + +XIAO_ESP32S3_Plus.upload.maximum_size=1310720 +XIAO_ESP32S3_Plus.upload.maximum_data_size=327680 +XIAO_ESP32S3_Plus.upload.flags= +XIAO_ESP32S3_Plus.upload.extra_flags= +XIAO_ESP32S3_Plus.upload.use_1200bps_touch=false +XIAO_ESP32S3_Plus.upload.wait_for_upload_port=false + +XIAO_ESP32S3_Plus.serial.disableDTR=false +XIAO_ESP32S3_Plus.serial.disableRTS=false + +XIAO_ESP32S3_Plus.build.tarch=xtensa +XIAO_ESP32S3_Plus.build.bootloader_addr=0x0 +XIAO_ESP32S3_Plus.build.target=esp32s3 +XIAO_ESP32S3_Plus.build.mcu=esp32s3 +XIAO_ESP32S3_Plus.build.core=esp32 +XIAO_ESP32S3_Plus.build.variant=XIAO_ESP32S3_Plus +XIAO_ESP32S3_Plus.build.board=XIAO_ESP32S3_PLUS + +XIAO_ESP32S3_Plus.build.usb_mode=0 +XIAO_ESP32S3_Plus.build.cdc_on_boot=1 +XIAO_ESP32S3_Plus.build.msc_on_boot=0 +XIAO_ESP32S3_Plus.build.dfu_on_boot=0 +XIAO_ESP32S3_Plus.build.f_cpu=240000000L +XIAO_ESP32S3_Plus.build.flash_size=8MB +XIAO_ESP32S3_Plus.build.flash_freq=80m +XIAO_ESP32S3_Plus.build.flash_mode=dio +XIAO_ESP32S3_Plus.build.boot=qio +XIAO_ESP32S3_Plus.build.boot_freq=80m +XIAO_ESP32S3_Plus.build.partitions=default_8MB +XIAO_ESP32S3_Plus.build.defines= +XIAO_ESP32S3_Plus.build.loop_core= +XIAO_ESP32S3_Plus.build.event_core= +XIAO_ESP32S3_Plus.build.psram_type=qspi +XIAO_ESP32S3_Plus.build.memory_type={build.boot}_{build.psram_type} + +XIAO_ESP32S3_Plus.menu.JTAGAdapter.default=Disabled +XIAO_ESP32S3_Plus.menu.JTAGAdapter.default.build.copy_jtag_files=0 +XIAO_ESP32S3_Plus.menu.JTAGAdapter.builtin=Integrated USB JTAG +XIAO_ESP32S3_Plus.menu.JTAGAdapter.builtin.build.openocdscript=esp32s3-builtin.cfg +XIAO_ESP32S3_Plus.menu.JTAGAdapter.builtin.build.copy_jtag_files=1 +XIAO_ESP32S3_Plus.menu.JTAGAdapter.external=FTDI Adapter +XIAO_ESP32S3_Plus.menu.JTAGAdapter.external.build.openocdscript=esp32s3-ftdi.cfg +XIAO_ESP32S3_Plus.menu.JTAGAdapter.external.build.copy_jtag_files=1 +XIAO_ESP32S3_Plus.menu.JTAGAdapter.bridge=ESP USB Bridge +XIAO_ESP32S3_Plus.menu.JTAGAdapter.bridge.build.openocdscript=esp32s3-bridge.cfg +XIAO_ESP32S3_Plus.menu.JTAGAdapter.bridge.build.copy_jtag_files=1 + +XIAO_ESP32S3_Plus.menu.PSRAM.disabled=Disabled +XIAO_ESP32S3_Plus.menu.PSRAM.disabled.build.defines= +XIAO_ESP32S3_Plus.menu.PSRAM.disabled.build.psram_type=qspi +XIAO_ESP32S3_Plus.menu.PSRAM.opi=OPI PSRAM +XIAO_ESP32S3_Plus.menu.PSRAM.opi.build.defines=-DBOARD_HAS_PSRAM +XIAO_ESP32S3_Plus.menu.PSRAM.opi.build.psram_type=opi + +XIAO_ESP32S3_Plus.menu.FlashMode.qio=QIO 80MHz +XIAO_ESP32S3_Plus.menu.FlashMode.qio.build.flash_mode=dio +XIAO_ESP32S3_Plus.menu.FlashMode.qio.build.boot=qio +XIAO_ESP32S3_Plus.menu.FlashMode.qio.build.boot_freq=80m +XIAO_ESP32S3_Plus.menu.FlashMode.qio.build.flash_freq=80m +XIAO_ESP32S3_Plus.menu.FlashMode.dio=DIO 80MHz +XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.flash_mode=dio +XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.boot=dio +XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.boot_freq=80m +XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.flash_freq=80m + +XIAO_ESP32S3_Plus.menu.FlashSize.8M=8MB (64Mb) +XIAO_ESP32S3_Plus.menu.FlashSize.8M.build.flash_size=8MB + +XIAO_ESP32S3_Plus.menu.LoopCore.1=Core 1 +XIAO_ESP32S3_Plus.menu.LoopCore.1.build.loop_core=-DARDUINO_RUNNING_CORE=1 +XIAO_ESP32S3_Plus.menu.LoopCore.0=Core 0 +XIAO_ESP32S3_Plus.menu.LoopCore.0.build.loop_core=-DARDUINO_RUNNING_CORE=0 + +XIAO_ESP32S3_Plus.menu.EventsCore.1=Core 1 +XIAO_ESP32S3_Plus.menu.EventsCore.1.build.event_core=-DARDUINO_EVENT_RUNNING_CORE=1 +XIAO_ESP32S3_Plus.menu.EventsCore.0=Core 0 +XIAO_ESP32S3_Plus.menu.EventsCore.0.build.event_core=-DARDUINO_EVENT_RUNNING_CORE=0 + +XIAO_ESP32S3_Plus.menu.USBMode.hwcdc=Hardware CDC and JTAG +XIAO_ESP32S3_Plus.menu.USBMode.hwcdc.build.usb_mode=1 +XIAO_ESP32S3_Plus.menu.USBMode.default=USB-OTG (TinyUSB) +XIAO_ESP32S3_Plus.menu.USBMode.default.build.usb_mode=0 + +XIAO_ESP32S3_Plus.menu.CDCOnBoot.default=Enabled +XIAO_ESP32S3_Plus.menu.CDCOnBoot.default.build.cdc_on_boot=1 +XIAO_ESP32S3_Plus.menu.CDCOnBoot.cdc=Disabled +XIAO_ESP32S3_Plus.menu.CDCOnBoot.cdc.build.cdc_on_boot=0 + +XIAO_ESP32S3_Plus.menu.MSCOnBoot.default=Disabled +XIAO_ESP32S3_Plus.menu.MSCOnBoot.default.build.msc_on_boot=0 +XIAO_ESP32S3_Plus.menu.MSCOnBoot.msc=Enabled (Requires USB-OTG Mode) +XIAO_ESP32S3_Plus.menu.MSCOnBoot.msc.build.msc_on_boot=1 + +XIAO_ESP32S3_Plus.menu.DFUOnBoot.default=Disabled +XIAO_ESP32S3_Plus.menu.DFUOnBoot.default.build.dfu_on_boot=0 +XIAO_ESP32S3_Plus.menu.DFUOnBoot.dfu=Enabled (Requires USB-OTG Mode) +XIAO_ESP32S3_Plus.menu.DFUOnBoot.dfu.build.dfu_on_boot=1 + +XIAO_ESP32S3_Plus.menu.UploadMode.default=UART0 / Hardware CDC +XIAO_ESP32S3_Plus.menu.UploadMode.default.upload.use_1200bps_touch=false +XIAO_ESP32S3_Plus.menu.UploadMode.default.upload.wait_for_upload_port=false +XIAO_ESP32S3_Plus.menu.UploadMode.cdc=USB-OTG CDC (TinyUSB) +XIAO_ESP32S3_Plus.menu.UploadMode.cdc.upload.use_1200bps_touch=true +XIAO_ESP32S3_Plus.menu.UploadMode.cdc.upload.wait_for_upload_port=true + +XIAO_ESP32S3_Plus.menu.PartitionScheme.default_8MB=Default with spiffs (3MB APP/1.5MB SPIFFS) +XIAO_ESP32S3_Plus.menu.PartitionScheme.default_8MB.build.partitions=default_8MB +XIAO_ESP32S3_Plus.menu.PartitionScheme.default_8MB.upload.maximum_size=3342336 +XIAO_ESP32S3_Plus.menu.PartitionScheme.max_app_8MB=Maximum APP (7.9MB APP No OTA/No FS) +XIAO_ESP32S3_Plus.menu.PartitionScheme.max_app_8MB.build.partitions=max_app_8MB +XIAO_ESP32S3_Plus.menu.PartitionScheme.max_app_8MB.upload.maximum_size=8257536 +XIAO_ESP32S3_Plus.menu.PartitionScheme.tinyuf2=TinyUF2 8MB (2MB APP/3.7MB FFAT) +XIAO_ESP32S3_Plus.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader-tinyuf2 +XIAO_ESP32S3_Plus.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions-8MB +XIAO_ESP32S3_Plus.menu.PartitionScheme.tinyuf2.upload.maximum_size=2097152 +XIAO_ESP32S3_Plus.menu.PartitionScheme.tinyuf2.upload.extra_flags=0x410000 "{runtime.platform.path}/variants/{build.variant}/tinyuf2.bin" + +XIAO_ESP32S3_Plus.menu.CPUFreq.240=240MHz (WiFi) +XIAO_ESP32S3_Plus.menu.CPUFreq.240.build.f_cpu=240000000L +XIAO_ESP32S3_Plus.menu.CPUFreq.160=160MHz (WiFi) +XIAO_ESP32S3_Plus.menu.CPUFreq.160.build.f_cpu=160000000L +XIAO_ESP32S3_Plus.menu.CPUFreq.80=80MHz (WiFi) +XIAO_ESP32S3_Plus.menu.CPUFreq.80.build.f_cpu=80000000L +XIAO_ESP32S3_Plus.menu.CPUFreq.40=40MHz +XIAO_ESP32S3_Plus.menu.CPUFreq.40.build.f_cpu=40000000L +XIAO_ESP32S3_Plus.menu.CPUFreq.20=20MHz +XIAO_ESP32S3_Plus.menu.CPUFreq.20.build.f_cpu=20000000L +XIAO_ESP32S3_Plus.menu.CPUFreq.10=10MHz +XIAO_ESP32S3_Plus.menu.CPUFreq.10.build.f_cpu=10000000L + +XIAO_ESP32S3_Plus.menu.UploadSpeed.921600=921600 +XIAO_ESP32S3_Plus.menu.UploadSpeed.921600.upload.speed=921600 +XIAO_ESP32S3_Plus.menu.UploadSpeed.115200=115200 +XIAO_ESP32S3_Plus.menu.UploadSpeed.115200.upload.speed=115200 +XIAO_ESP32S3_Plus.menu.UploadSpeed.256000.windows=256000 +XIAO_ESP32S3_Plus.menu.UploadSpeed.256000.upload.speed=256000 +XIAO_ESP32S3_Plus.menu.UploadSpeed.230400.windows.upload.speed=256000 +XIAO_ESP32S3_Plus.menu.UploadSpeed.230400=230400 +XIAO_ESP32S3_Plus.menu.UploadSpeed.230400.upload.speed=230400 +XIAO_ESP32S3_Plus.menu.UploadSpeed.460800.linux=460800 +XIAO_ESP32S3_Plus.menu.UploadSpeed.460800.macosx=460800 +XIAO_ESP32S3_Plus.menu.UploadSpeed.460800.upload.speed=460800 +XIAO_ESP32S3_Plus.menu.UploadSpeed.512000.windows=512000 +XIAO_ESP32S3_Plus.menu.UploadSpeed.512000.upload.speed=512000 + +XIAO_ESP32S3_Plus.menu.DebugLevel.none=None +XIAO_ESP32S3_Plus.menu.DebugLevel.none.build.code_debug=0 +XIAO_ESP32S3_Plus.menu.DebugLevel.error=Error +XIAO_ESP32S3_Plus.menu.DebugLevel.error.build.code_debug=1 +XIAO_ESP32S3_Plus.menu.DebugLevel.warn=Warn +XIAO_ESP32S3_Plus.menu.DebugLevel.warn.build.code_debug=2 +XIAO_ESP32S3_Plus.menu.DebugLevel.info=Info +XIAO_ESP32S3_Plus.menu.DebugLevel.info.build.code_debug=3 +XIAO_ESP32S3_Plus.menu.DebugLevel.debug=Debug +XIAO_ESP32S3_Plus.menu.DebugLevel.debug.build.code_debug=4 +XIAO_ESP32S3_Plus.menu.DebugLevel.verbose=Verbose +XIAO_ESP32S3_Plus.menu.DebugLevel.verbose.build.code_debug=5 + +XIAO_ESP32S3_Plus.menu.EraseFlash.none=Disabled +XIAO_ESP32S3_Plus.menu.EraseFlash.none.upload.erase_cmd= +XIAO_ESP32S3_Plus.menu.EraseFlash.all=Enabled +XIAO_ESP32S3_Plus.menu.EraseFlash.all.upload.erase_cmd=-e + +############################################################## + connaxio_espoir.name=Connaxio's Espoir connaxio_espoir.vid.0=0x10C4 connaxio_espoir.pid.0=0x8D9A diff --git a/variants/XIAO_ESP32S3_Plus/bootloader-tinyuf2.bin b/variants/XIAO_ESP32S3_Plus/bootloader-tinyuf2.bin new file mode 100644 index 0000000000000000000000000000000000000000..b11e5b236f0992d8351b579c8283c03a5e32a85c GIT binary patch literal 21408 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 SVR0lq@zytgJh)Ff;r{>(gA@M% literal 0 HcmV?d00001 diff --git a/variants/XIAO_ESP32S3_Plus/partitions-8MB.csv b/variants/XIAO_ESP32S3_Plus/partitions-8MB.csv new file mode 100644 index 00000000000..4026378b6fb --- /dev/null +++ b/variants/XIAO_ESP32S3_Plus/partitions-8MB.csv @@ -0,0 +1,10 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +# bootloader.bin,, 0x1000, 32K +# partition table,, 0x8000, 4K +nvs, data, nvs, 0x9000, 20K, +otadata, data, ota, 0xe000, 8K, +ota_0, 0, ota_0, 0x10000, 2048K, +ota_1, 0, ota_1, 0x210000, 2048K, +uf2, app, factory,0x410000, 256K, +ffat, data, fat, 0x450000, 3776K, diff --git a/variants/XIAO_ESP32S3_Plus/pins_arduino.h b/variants/XIAO_ESP32S3_Plus/pins_arduino.h new file mode 100644 index 00000000000..fb887287e35 --- /dev/null +++ b/variants/XIAO_ESP32S3_Plus/pins_arduino.h @@ -0,0 +1,90 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0056 + +static const uint8_t LED_BUILTIN = 21; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t TX1 = 42; +static const uint8_t RX1 = 41; + +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +static const uint8_t SS = 44; +static const uint8_t MOSI = 9; +static const uint8_t MISO = 8; +static const uint8_t SCK = 7; + +static const uint8_t MOSI1 = 11; +static const uint8_t MISO1 = 12; +static const uint8_t SCK1 = 13; + +static const uint8_t I2S_SCK = 39; +static const uint8_t I2S_SD = 38; +static const uint8_t I2S_WS = 40; + +static const uint8_t MTCK = 39; +static const uint8_t MTDO = 40; +static const uint8_t MTDI = 41; +static const uint8_t MTMS = 42; + +static const uint8_t DVP_Y8 = 11; +static const uint8_t DVP_YP = 12; +static const uint8_t DVP_PCLK = 13; +static const uint8_t DVP_VSYNC = 38; +static const uint8_t CAM_SCL = 39; +static const uint8_t CAM_SDA = 40; +static const uint8_t XMCLK = 10; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A8 = 7; +static const uint8_t A9 = 8; +static const uint8_t A10 = 9; +static const uint8_t ADC_BAT = 10; + +static const uint8_t D0 = 1; +static const uint8_t D1 = 2; +static const uint8_t D2 = 3; +static const uint8_t D3 = 4; +static const uint8_t D4 = 5; +static const uint8_t D5 = 6; +static const uint8_t D6 = 43; +static const uint8_t D7 = 44; +static const uint8_t D8 = 7; +static const uint8_t D9 = 8; +static const uint8_t D10 = 9; +static const uint8_t D11 = 38; +static const uint8_t D12 = 39; +static const uint8_t D13 = 40; +static const uint8_t D14 = 41; +static const uint8_t D15 = 42; +static const uint8_t D16 = 10; +static const uint8_t D17 = 13; +static const uint8_t D18 = 12; +static const uint8_t D19 = 11; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; + +#endif /* Pins_Arduino_h */ diff --git a/variants/XIAO_ESP32S3_Plus/tinyuf2.bin b/variants/XIAO_ESP32S3_Plus/tinyuf2.bin new file mode 100644 index 0000000000000000000000000000000000000000..86d981f8019368e49c7412d49731a0199b1722bd GIT binary patch literal 141936 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 literal 0 HcmV?d00001 From 15cbb1e857c6a0b4f6a88a611ff929e80ad5fe72 Mon Sep 17 00:00:00 2001 From: lsroka76 <95999074+lsroka76@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:38:50 +0100 Subject: [PATCH 09/33] Add IAS Zone Notification Message service to ZigbeeHandlers and ZigbeeEP.h (#10821) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update ZigbeeHandlers.cpp * Update ZigbeeEP.h * Update ZigbeeEP.h make addBoundDevice virtual for override * fix(zigbee): Update and reorder handlers * fix(zigbee): Place default handler to the end * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: Jan Procházka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- libraries/Zigbee/src/ZigbeeEP.h | 11 +++++---- libraries/Zigbee/src/ZigbeeHandlers.cpp | 30 +++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index d8ed900a7e6..ac22143c4d7 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -105,6 +105,13 @@ class ZigbeeEP { 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); + virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {}; + + virtual void addBoundDevice(zb_device_params_t *device) { + _bound_devices.push_back(device); + _is_bound = true; + } + void onIdentify(void (*callback)(uint16_t)) { _on_identify = callback; } @@ -125,10 +132,6 @@ class ZigbeeEP { SemaphoreHandle_t lock; zb_power_source_t _power_source; - void addBoundDevice(zb_device_params_t *device) { - _bound_devices.push_back(device); - _is_bound = true; - } friend class ZigbeeCore; }; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index e387b7ce432..bccb27da7f8 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -9,6 +9,7 @@ static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_messag 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_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_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 @@ -20,8 +21,11 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, 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; + case ESP_ZB_CORE_CMD_IAS_ZONE_ZONE_STATUS_CHANGE_NOT_ID: + ret = zb_cmd_ias_zone_status_change_handler((esp_zb_zcl_ias_zone_status_change_notification_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; } @@ -132,6 +136,28 @@ static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_re return ESP_OK; } +static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) { + if (!message) { + log_e("Empty message"); + return ESP_FAIL; + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; + } + log_v( + "IAS Zone Status Notification: 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()) { + (*it)->zbIASZoneStatusChangeNotification(message); + } + } + 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"); From 2fecc482b721b30085f2a9983aaf45f9d38cb064 Mon Sep 17 00:00:00 2001 From: lbernstone Date: Mon, 27 Jan 2025 00:39:15 -1000 Subject: [PATCH 10/33] fix(littlefs): Converted core disableWDT functions to bool (#10896) * fix(littlefs): Converted core disableWDT functions to bool * Missed the returns on core1 * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- cores/esp32/esp32-hal-misc.c | 12 ++++++++---- cores/esp32/esp32-hal.h | 4 ++-- libraries/LittleFS/src/LittleFS.cpp | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cores/esp32/esp32-hal-misc.c b/cores/esp32/esp32-hal-misc.c index 0bce548bdd2..02871872f83 100644 --- a/cores/esp32/esp32-hal-misc.c +++ b/cores/esp32/esp32-hal-misc.c @@ -156,11 +156,13 @@ void enableCore0WDT() { } } -void disableCore0WDT() { +bool disableCore0WDT() { TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCore(0); - if (idle_0 == NULL || esp_task_wdt_delete(idle_0) != ESP_OK) { + if (idle_0 == NULL || esp_task_wdt_status(idle_0) || esp_task_wdt_delete(idle_0) != ESP_OK) { log_e("Failed to remove Core 0 IDLE task from WDT"); + return false; } + return true; } #ifndef CONFIG_FREERTOS_UNICORE @@ -171,11 +173,13 @@ void enableCore1WDT() { } } -void disableCore1WDT() { +bool disableCore1WDT() { TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCore(1); - if (idle_1 == NULL || esp_task_wdt_delete(idle_1) != ESP_OK) { + if (idle_1 == NULL || esp_task_wdt_status(idle_1) || esp_task_wdt_delete(idle_1) != ESP_OK) { log_e("Failed to remove Core 1 IDLE task from WDT"); + return false; } + return true; } #endif diff --git a/cores/esp32/esp32-hal.h b/cores/esp32/esp32-hal.h index d0bd4b8bc93..5ed99aeb205 100644 --- a/cores/esp32/esp32-hal.h +++ b/cores/esp32/esp32-hal.h @@ -121,11 +121,11 @@ void feedLoopWDT(); //enable/disable WDT for the IDLE task on Core 0 (SYSTEM) void enableCore0WDT(); -void disableCore0WDT(); +bool disableCore0WDT(); #ifndef CONFIG_FREERTOS_UNICORE //enable/disable WDT for the IDLE task on Core 1 (Arduino) void enableCore1WDT(); -void disableCore1WDT(); +bool disableCore1WDT(); #endif //if xCoreID < 0 or CPU is unicore, it will use xTaskCreate, else xTaskCreatePinnedToCore diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp index e86caeb74cc..761d1ba4c24 100644 --- a/libraries/LittleFS/src/LittleFS.cpp +++ b/libraries/LittleFS/src/LittleFS.cpp @@ -95,9 +95,11 @@ void LittleFSFS::end() { } bool LittleFSFS::format() { - disableCore0WDT(); + bool wdt_active = disableCore0WDT(); esp_err_t err = esp_littlefs_format(partitionLabel_); - enableCore0WDT(); + if (wdt_active) { + enableCore0WDT(); + } if (err) { log_e("Formatting LittleFS failed! Error: %d", err); return false; From 732a7cb4ee4ae4c5a3bcf07f15ac2526f3cefc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 30 Jan 2025 18:11:40 +0100 Subject: [PATCH 11/33] feat(zigbee): Add Time cluster support + fix of duplicate indentify cluster (#10863) * feat(zigbee): Add Time cluster support * fix(zigbee): Remove duplicate of identify cluster * feat(zigbee): Remove unused variables in addTimeCluster * feat(zigbee): Update examples with optional Time cluster * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../Zigbee_Temperature_Sensor.ino | 21 +++ .../Zigbee_Thermostat/Zigbee_Thermostat.ino | 15 +++ libraries/Zigbee/src/ZigbeeEP.cpp | 125 ++++++++++++++++++ libraries/Zigbee/src/ZigbeeEP.h | 22 ++- libraries/Zigbee/src/ZigbeeHandlers.cpp | 2 + .../src/ep/ZigbeeCarbonDioxideSensor.cpp | 1 - libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp | 1 - .../Zigbee/src/ep/ZigbeeOccupancySensor.cpp | 1 - .../Zigbee/src/ep/ZigbeePressureSensor.cpp | 1 - 9 files changed, 181 insertions(+), 8 deletions(-) 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 27aa2db97bd..ad007abbbaa 100644 --- a/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino +++ b/libraries/Zigbee/examples/Zigbee_Temperature_Sensor/Zigbee_Temperature_Sensor.ino @@ -36,6 +36,11 @@ #define TEMP_SENSOR_ENDPOINT_NUMBER 10 uint8_t button = BOOT_PIN; +// Optional Time cluster variables +struct tm timeinfo; +struct tm *localTime; +int32_t timezone; + ZigbeeTempSensor zbTempSensor = ZigbeeTempSensor(TEMP_SENSOR_ENDPOINT_NUMBER); /************************ Temp sensor *****************************/ @@ -66,6 +71,9 @@ void setup() { // Optional: Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C) zbTempSensor.setTolerance(1); + // Optional: Time cluster configuration (default params, as this device will revieve time from coordinator) + zbTempSensor.addTimeCluster(); + // Add endpoint to Zigbee Core Zigbee.addEndpoint(&zbTempSensor); @@ -85,6 +93,19 @@ void setup() { } Serial.println(); + // Optional: If time cluster is added, time can be read from the coordinator + timeinfo = zbTempSensor.getTime(); + timezone = zbTempSensor.getTimezone(); + + Serial.println("UTC time:"); + Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); + + time_t local = mktime(&timeinfo) + timezone; + localTime = localtime(&local); + + Serial.println("Local time with timezone:"); + Serial.println(localTime, "%A, %B %d %Y %H:%M:%S"); + // Start Temperature sensor reading task xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL); diff --git a/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino b/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino index a4720feeba4..7cdf45ef711 100644 --- a/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino +++ b/libraries/Zigbee/examples/Zigbee_Thermostat/Zigbee_Thermostat.ino @@ -45,6 +45,8 @@ float sensor_max_temp; float sensor_min_temp; float sensor_tolerance; +struct tm timeinfo = {}; // Time structure for Time cluster + /****************** Temperature sensor handling *******************/ void recieveSensorTemp(float temperature) { Serial.printf("Temperature sensor value: %.2f°C\n", temperature); @@ -71,6 +73,19 @@ void setup() { //Optional: set Zigbee device name and model zbThermostat.setManufacturerAndModel("Espressif", "ZigbeeThermostat"); + //Optional Time cluster configuration + //example time January 13, 2025 13:30:30 CET + timeinfo.tm_year = 2025 - 1900; // = 2025 + timeinfo.tm_mon = 0; // January + timeinfo.tm_mday = 13; // 13th + timeinfo.tm_hour = 12; // 12 hours - 1 hour (CET) + timeinfo.tm_min = 30; // 30 minutes + timeinfo.tm_sec = 30; // 30 seconds + timeinfo.tm_isdst = -1; + + // Set time and gmt offset (timezone in seconds -> CET = +3600 seconds) + zbThermostat.addTimeCluster(timeinfo, 3600); + //Add endpoint to Zigbee Core Zigbee.addEndpoint(&zbThermostat); diff --git a/libraries/Zigbee/src/ZigbeeEP.cpp b/libraries/Zigbee/src/ZigbeeEP.cpp index adc87540b83..6a2ace0b90c 100644 --- a/libraries/Zigbee/src/ZigbeeEP.cpp +++ b/libraries/Zigbee/src/ZigbeeEP.cpp @@ -238,4 +238,129 @@ void ZigbeeEP::zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message) { } } +void ZigbeeEP::addTimeCluster(tm time, int32_t gmt_offset) { + time_t utc_time = 0; + + // Check if time is set + if (time.tm_year > 0) { + // Convert time to UTC + utc_time = mktime(&time); + } + + // Create time cluster server attributes + esp_zb_attribute_list_t *time_cluster_server = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME); + esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID, (void *)&gmt_offset); + esp_zb_time_cluster_add_attr(time_cluster_server, ESP_ZB_ZCL_ATTR_TIME_TIME_ID, (void *)&utc_time); + // Create time cluster client attributes + esp_zb_attribute_list_t *time_cluster_client = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME); + // Add time clusters to cluster list + esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_server, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_time_cluster(_cluster_list, time_cluster_client, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE); +} + +void ZigbeeEP::setTime(tm time) { + time_t utc_time = mktime(&time); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_TIME, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TIME_TIME_ID, &utc_time, false); + esp_zb_lock_release(); +} + +void ZigbeeEP::setTimezone(int32_t gmt_offset) { + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_TIME, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID, &gmt_offset, false); + esp_zb_lock_release(); +} + +tm ZigbeeEP::getTime(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) { + /* Read peer time */ + esp_zb_zcl_read_attr_cmd_t read_req; + + if (short_addr >= 0) { + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr; + } else { + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; + memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t)); + } + + uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ID}; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME; + + read_req.zcl_basic_cmd.dst_endpoint = endpoint; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + + // clear read time + _read_time = 0; + + log_v("Reading time from endpoint %d", endpoint); + esp_zb_zcl_read_attr_cmd_req(&read_req); + + //Wait for response or timeout + if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) { + log_e("Error while reading time"); + return tm(); + } + + struct tm *timeinfo = localtime(&_read_time); + if (timeinfo) { + return *timeinfo; + } else { + log_e("Error while converting time"); + return tm(); + } +} + +int32_t ZigbeeEP::getTimezone(uint8_t endpoint, int32_t short_addr, esp_zb_ieee_addr_t ieee_addr) { + /* Read peer timezone */ + esp_zb_zcl_read_attr_cmd_t read_req; + + if (short_addr >= 0) { + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + read_req.zcl_basic_cmd.dst_addr_u.addr_short = (uint16_t)short_addr; + } else { + read_req.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; + memcpy(read_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t)); + } + + uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID}; + read_req.attr_number = ZB_ARRAY_LENTH(attributes); + read_req.attr_field = attributes; + + read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME; + + read_req.zcl_basic_cmd.dst_endpoint = endpoint; + read_req.zcl_basic_cmd.src_endpoint = _endpoint; + + // clear read timezone + _read_timezone = 0; + + log_v("Reading timezone from endpoint %d", endpoint); + esp_zb_zcl_read_attr_cmd_req(&read_req); + + //Wait for response or timeout + if (xSemaphoreTake(lock, ZB_CMD_TIMEOUT) != pdTRUE) { + log_e("Error while reading timezone"); + } + + return _read_timezone; +} + +void ZigbeeEP::zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute) { + /* Time cluster attributes */ + if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME) { + log_v("Time attribute received"); + log_v("Time: %lld", *(uint32_t *)attribute->data.value); + _read_time = *(uint32_t *)attribute->data.value; + xSemaphoreGive(lock); + } else if (attribute->id == ESP_ZB_ZCL_ATTR_TIME_TIME_ZONE_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_S32) { + log_v("Timezone attribute received"); + log_v("Timezone: %d", *(int32_t *)attribute->data.value); + _read_timezone = *(int32_t *)attribute->data.value; + xSemaphoreGive(lock); + } +} + #endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index ac22143c4d7..eae81fafbc3 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -82,16 +82,27 @@ class ZigbeeEP { _allow_multiple_binding = bind; } - // Manufacturer name and model implemented + // Set Manufacturer name and model void setManufacturerAndModel(const char *name, const char *model); - void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255); - void setBatteryPercentage(uint8_t percentage); - void reportBatteryPercentage(); // Methods to read manufacturer and model name from selected endpoint and short address char *readManufacturer(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr); char *readModel(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_addr_t ieee_addr); + // Set Power source and battery percentage for battery powered devices + void setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255); + void setBatteryPercentage(uint8_t percentage); + void reportBatteryPercentage(); + + // Set time + void addTimeCluster(tm time = {0}, int32_t gmt_offset = 0); // gmt offset in seconds + void setTime(tm time); + void setTimezone(int32_t gmt_offset); + + // Get time from Coordinator or specific endpoint (blocking until response) + struct tm getTime(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0}); + int32_t getTimezone(uint8_t endpoint = 1, int32_t short_addr = 0x0000, esp_zb_ieee_addr_t ieee_addr = {0}); // gmt offset in seconds + bool epAllowMultipleBinding() { return _allow_multiple_binding; } @@ -104,6 +115,7 @@ class ZigbeeEP { 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); + virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {}; @@ -120,6 +132,8 @@ class ZigbeeEP { char *_read_manufacturer; char *_read_model; void (*_on_identify)(uint16_t time); + time_t _read_time; + int32_t _read_timezone; protected: uint8_t _endpoint; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index bccb27da7f8..3b4992d81db 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -105,6 +105,8 @@ static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_re 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 if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_TIME) { + (*it)->zbReadTimeCluster(&variable->attribute); //method zbReadTimeCluster implemented in the common EP class } else { (*it)->zbAttributeRead(message->info.cluster, &variable->attribute); //method zbAttributeRead must be implemented in specific EP class } diff --git a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp index 5def43f4199..b4386b80386 100644 --- a/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp @@ -11,7 +11,6 @@ esp_zb_cluster_list_t *zigbee_carbon_dioxide_sensor_clusters_create(zigbee_carbo esp_zb_cluster_list_add_carbon_dioxide_measurement_cluster( cluster_list, esp_zb_carbon_dioxide_measurement_cluster_create(carbon_dioxide_meas_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_SERVER_ROLE); return cluster_list; } diff --git a/libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp b/libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp index 75196e78543..36d66840967 100644 --- a/libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeFlowSensor.cpp @@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_flow_sensor_clusters_create(zigbee_flow_sensor_cfg esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_flow_meas_cluster(cluster_list, esp_zb_flow_meas_cluster_create(flow_meas_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_SERVER_ROLE); return cluster_list; } diff --git a/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp b/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp index 3a7acee040c..dec7910ac03 100644 --- a/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp @@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_occupancy_sensor_clusters_create(zigbee_occupancy_ esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_occupancy_sensing_cluster(cluster_list, esp_zb_occupancy_sensing_cluster_create(occupancy_meas_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_SERVER_ROLE); return cluster_list; } diff --git a/libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp b/libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp index 24b4efb127e..b887680076d 100644 --- a/libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp +++ b/libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp @@ -9,7 +9,6 @@ esp_zb_cluster_list_t *zigbee_pressure_sensor_clusters_create(zigbee_pressure_se esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_cluster_list_add_pressure_meas_cluster(cluster_list, esp_zb_pressure_meas_cluster_create(pressure_meas_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_SERVER_ROLE); return cluster_list; } From 402ab56bf1d68f8fbc9788a168837b1534551390 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 30 Jan 2025 18:12:07 +0100 Subject: [PATCH 12/33] fix(SPIFFS): Use new disableWDT bool return value (#10909) Analogue to this PR, but for SPIFFS: https://github.com/espressif/arduino-esp32/pull/10896/files --- libraries/SPIFFS/src/SPIFFS.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/SPIFFS/src/SPIFFS.cpp b/libraries/SPIFFS/src/SPIFFS.cpp index a77b32990bf..0b2cc0d462d 100644 --- a/libraries/SPIFFS/src/SPIFFS.cpp +++ b/libraries/SPIFFS/src/SPIFFS.cpp @@ -91,9 +91,11 @@ void SPIFFSFS::end() { } bool SPIFFSFS::format() { - disableCore0WDT(); + bool wdt_active = disableCore0WDT(); esp_err_t err = esp_spiffs_format(partitionLabel_); - enableCore0WDT(); + if (wdt_active) { + enableCore0WDT(); + } if (err) { log_e("Formatting SPIFFS failed! Error: %d", err); return false; From f22866f888b713fa09c4b29b854a734478b407ad Mon Sep 17 00:00:00 2001 From: Jack Lin Date: Thu, 30 Jan 2025 09:14:08 -0800 Subject: [PATCH 13/33] Update HTTPS certificate in BasicHttpsClient.ino (#10911) The certificate is outdated. The current certificate can be found using `openssl s_client -showcerts -connect jigsaw.w3.org:443` --- .../BasicHttpsClient/BasicHttpsClient.ino | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino b/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino index eb6caef0724..73e127d1261 100644 --- a/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino +++ b/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino @@ -14,30 +14,32 @@ #include -// This is a Baltimore CyberTrust cert, the root Certificate Authority that +// This is a Google Trust Services cert, the root Certificate Authority that // signed the server certificate for the demo server https://jigsaw.w3.org in this -// example. This certificate is valid until Mon, 12 May 2025 23:59:00 GMT -const char *rootCACertificate = "-----BEGIN CERTIFICATE-----\n" - "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" - "RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" - "VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" - "DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" - "ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" - "VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" - "mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" - "IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" - "mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" - "XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" - "dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" - "jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" - "BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" - "DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" - "9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" - "jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" - "Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" - "ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" - "R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" - "-----END CERTIFICATE-----\n"; +// example. This certificate is valid until Jan 28 00:00:42 2028 GMT +const char *rootCACertificate = R"string_literal( +-----BEGIN CERTIFICATE----- +MIIDejCCAmKgAwIBAgIQf+UwvzMTQ77dghYQST2KGzANBgkqhkiG9w0BAQsFADBX +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE +CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIzMTEx +NTAzNDMyMVoXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT +GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFI0 +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE83Rzp2iLYK5DuDXFgTB7S0md+8Fhzube +Rr1r1WEYNa5A3XP3iZEwWus87oV8okB2O6nGuEfYKueSkWpz6bFyOZ8pn6KY019e +WIZlD6GEZQbR3IvJx3PIjGov5cSr0R2Ko4H/MIH8MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgEzW63T/STaj1dj8tT7FavCUHYwwHwYDVR0jBBgwFoAUYHtmGkUN +l8qJUC99BM00qP/8/UswNgYIKwYBBQUHAQEEKjAoMCYGCCsGAQUFBzAChhpodHRw +Oi8vaS5wa2kuZ29vZy9nc3IxLmNydDAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8v +Yy5wa2kuZ29vZy9yL2dzcjEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMA0GCSqG +SIb3DQEBCwUAA4IBAQAYQrsPBtYDh5bjP2OBDwmkoWhIDDkic574y04tfzHpn+cJ +odI2D4SseesQ6bDrarZ7C30ddLibZatoKiws3UL9xnELz4ct92vID24FfVbiI1hY ++SW6FoVHkNeWIP0GCbaM4C6uVdF5dTUsMVs/ZbzNnIdCp5Gxmx5ejvEau8otR/Cs +kGN+hr/W5GvT1tMBjgWKZ1i4//emhA1JG1BbPzoLJQvyEotc03lXjTaCzv8mEbep +8RqZ7a2CPsgRbuvTPBwcOMBBmuFeU88+FSBX6+7iP0il8b4Z0QFqIwwMHfs/L6K1 +vepuoxtGzi4CZ68zJpiq1UvSqTbFJjtbD4seiMHl +-----END CERTIFICATE----- +)string_literal"; // Not sure if NetworkClientSecure checks the validity date of the certificate. // Setting clock just to be sure... From 0302b4db4741ae51ff45a3b1b533175ee0c0d92c Mon Sep 17 00:00:00 2001 From: "Sayed (Tashfi) Nowroz" Date: Fri, 31 Jan 2025 16:25:45 -0500 Subject: [PATCH 14/33] fix(logging): incorrect FPS logging (#10921) * fix(logging): Corrected FPS calculation Previously, last_frame was only updated once at the beginning of stream_handler, leading to incorrect FPS and avg_frame_time computation. This commit ensures last_frame is updated on each iteration after last FPS computation, resulting in accurate FPS logging. Fixes #10920 * Revert "fix(logging): Corrected FPS calculation" This reverts commit 0bb7b9555e7661c72dc3376cf8a001c6fd3758c8. * fix(loggin): Incorrect FPS computation fixed Corrected and tested change in FPS computation, suggested by @me-no-dev and found working with correct numbers. Previously, last_frame was only updated once at the beginning of stream_handler, leading to incorrect FPS and avg_frame_time computation. This commit ensures last_frame is updated on each iteration after last FPS computation, resulting in accurate FPS logging. Fixes #10920 --- libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp b/libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp index 6b62ee9b6cf..81d643e37ac 100644 --- a/libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp +++ b/libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp @@ -281,6 +281,8 @@ static esp_err_t stream_handler(httpd_req_t *req) { int64_t fr_end = esp_timer_get_time(); int64_t frame_time = fr_end - last_frame; + last_frame = fr_end; + frame_time /= 1000; #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); From 1c01fcd196ad8278feafe35bc785999db72a5425 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Tue, 4 Feb 2025 06:04:58 -0300 Subject: [PATCH 15/33] fix(uart): fixed esp32s2 uart ci test case (#10926) Wrong pins were used for baud rate detection test case, therefore no data was reaching UART1 RX FIFO from UART0 TX loopback. --- tests/validation/uart/uart.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/validation/uart/uart.ino b/tests/validation/uart/uart.ino index 27bd95da7f8..358276c00b4 100644 --- a/tests/validation/uart/uart.ino +++ b/tests/validation/uart/uart.ino @@ -399,7 +399,8 @@ void auto_baudrate_test(void) { if (TEST_UART_NUM == 1) { selected_serial = &Serial1; - uart_internal_loopback(0, RX1); + // UART1 pins were swapped because of ESP32-P4 + uart_internal_loopback(0, /*RX1*/ TX1); } else { #ifdef RX2 selected_serial = &Serial2; From 2040cbad51b7e9917729244f8781b66786ddd50c Mon Sep 17 00:00:00 2001 From: Akashdeep Deb Date: Tue, 4 Feb 2025 09:24:49 +0000 Subject: [PATCH 16/33] Update README.md to add ESP-SR (#10925) Adding ESP-SR to the documentation Co-authored-by: Me No Dev --- libraries/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/README.md b/libraries/README.md index 38765f99b52..aee5bb07614 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -88,6 +88,9 @@ arduino-esp32 includes libraries for Arduino compatibility along with some objec ### SPIFFS SPI Flash Filesystem (see [spiffs-plugin](https://github.com/me-no-dev/arduino-esp32fs-plugin) to upload to device) +### SR + ESP-SR helps users build AI speech solutions based on ESP32-S3 or ESP32-P4 chips + ### Ticker A timer to call functions on an interval From 6eb99d3ae32966bb985853fdf73d6da2e34fd565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:25:11 +0100 Subject: [PATCH 17/33] feat(zigbee): Add IAS Zone endpoints (Contact Switch + Door/Window Handle) (#10918) * feat(zigbee): Add IAS Zone endpoints * ci(pre-commit): Apply automatic fixes * fix(ci): Typo fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CMakeLists.txt | 2 + .../examples/Zigbee_Contact_Switch/README.md | 58 ++++++++++ .../Zigbee_Contact_Switch.ino | 100 ++++++++++++++++ .../examples/Zigbee_Contact_Switch/ci.json | 6 + libraries/Zigbee/src/Zigbee.h | 2 + libraries/Zigbee/src/ZigbeeEP.h | 1 + libraries/Zigbee/src/ZigbeeHandlers.cpp | 22 ++++ .../Zigbee/src/ep/ZigbeeContactSwitch.cpp | 96 ++++++++++++++++ libraries/Zigbee/src/ep/ZigbeeContactSwitch.h | 67 +++++++++++ .../Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp | 108 ++++++++++++++++++ .../Zigbee/src/ep/ZigbeeDoorWindowHandle.h | 71 ++++++++++++ 11 files changed, 533 insertions(+) create mode 100644 libraries/Zigbee/examples/Zigbee_Contact_Switch/README.md create mode 100644 libraries/Zigbee/examples/Zigbee_Contact_Switch/Zigbee_Contact_Switch.ino create mode 100644 libraries/Zigbee/examples/Zigbee_Contact_Switch/ci.json create mode 100644 libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp create mode 100644 libraries/Zigbee/src/ep/ZigbeeContactSwitch.h create mode 100644 libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp create mode 100644 libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fd23d14d77..b3cb6c06cdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,6 +290,8 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS libraries/Zigbee/src/ep/ZigbeePressureSensor.cpp libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp + libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp + libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp ) set(ARDUINO_LIBRARY_BLE_SRCS diff --git a/libraries/Zigbee/examples/Zigbee_Contact_Switch/README.md b/libraries/Zigbee/examples/Zigbee_Contact_Switch/README.md new file mode 100644 index 00000000000..a5a32358a7c --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Contact_Switch/README.md @@ -0,0 +1,58 @@ +# Arduino-ESP32 Zigbee Contact Switch Example + +This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) contact switch (IAS Zone), +that can be used for example as window/door sensor having 2 states - closed/open. + +# Supported Targets + +Currently, this example supports the following targets. + +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +## Hardware Required + +* A USB cable for power supply and programming + +### Configure the Project + +Set the Button GPIO by changing the `button` variable. By default, it's the pin `BOOT_PIN` (BOOT button on ESP32-C6 and ESP32-H2). +Set the Sensor GPIO by changing the `sensor_pin` variable. + +#### Using Arduino IDE + +To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits). + +* Before Compile/Verify, select the correct board: `Tools -> Board`. +* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ED (end device)` +* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs` +* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port. +* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`. + +## Troubleshooting + +If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. + +***Important: Make sure you are using a good quality USB cable and that you have a reliable power source*** + +* **LED not blinking:** Check the wiring connection and the IO selection. +* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed. +* **COM port not detected:** Check the USB cable and the USB to Serial driver installation. + +If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute). + +## Contribute + +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) + +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! + +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. + +## Resources + +* Official ESP32 Forum: [Link](https://esp32.com) +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) +* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf) +* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf) +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) diff --git a/libraries/Zigbee/examples/Zigbee_Contact_Switch/Zigbee_Contact_Switch.ino b/libraries/Zigbee/examples/Zigbee_Contact_Switch/Zigbee_Contact_Switch.ino new file mode 100644 index 00000000000..ce9eedb683d --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Contact_Switch/Zigbee_Contact_Switch.ino @@ -0,0 +1,100 @@ +// 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 contact switch (IAS Zone). + * + * The example demonstrates how to use Zigbee library to create a end device contact switch. + * The contact switch is a Zigbee end device, which is reporting data to the Zigbee network. + * + * 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 end device mode is not selected in Tools->Zigbee mode" +#endif + +#include "Zigbee.h" + +/* Zigbee contact sensor configuration */ +#define CONTACT_SWITCH_ENDPOINT_NUMBER 10 +uint8_t button = BOOT_PIN; +uint8_t sensor_pin = 4; + +ZigbeeContactSwitch zbContactSwitch = ZigbeeContactSwitch(CONTACT_SWITCH_ENDPOINT_NUMBER); + +void setup() { + Serial.begin(115200); + + // Init button + switch + pinMode(button, INPUT_PULLUP); + pinMode(sensor_pin, INPUT_PULLUP); + + // Optional: set Zigbee device name and model + zbContactSwitch.setManufacturerAndModel("Espressif", "ZigbeeContactSwitch"); + + // Add endpoint to Zigbee Core + Zigbee.addEndpoint(&zbContactSwitch); + + Serial.println("Starting Zigbee..."); + // When all EPs are registered, start Zigbee in End Device mode + if (!Zigbee.begin()) { + Serial.println("Zigbee failed to start!"); + Serial.println("Rebooting..."); + ESP.restart(); + } else { + Serial.println("Zigbee started successfully!"); + } + Serial.println("Connecting to network"); + while (!Zigbee.connected()) { + Serial.print("."); + delay(100); + } + Serial.println(); +} + +void loop() { + // Checking pin for contact change + static bool contact = false; + if (digitalRead(sensor_pin) == HIGH && !contact) { + // Update contact sensor value + zbContactSwitch.setOpen(); + contact = true; + } else if (digitalRead(sensor_pin) == LOW && contact) { + zbContactSwitch.setClosed(); + contact = false; + } + + // Checking button for factory reset + if (digitalRead(button) == LOW) { // Push button pressed + // Key debounce handling + delay(100); + int startTime = millis(); + while (digitalRead(button) == LOW) { + delay(50); + if ((millis() - startTime) > 3000) { + // If key pressed for more than 3secs, factory reset Zigbee and reboot + Serial.println("Resetting Zigbee to factory and rebooting in 1s."); + delay(1000); + Zigbee.factoryReset(); + } + } + } + delay(100); +} diff --git a/libraries/Zigbee/examples/Zigbee_Contact_Switch/ci.json b/libraries/Zigbee/examples/Zigbee_Contact_Switch/ci.json new file mode 100644 index 00000000000..7b7ccef8ed7 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Contact_Switch/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed", + "requires": [ + "CONFIG_SOC_IEEE802154_SUPPORTED=y" + ] +} diff --git a/libraries/Zigbee/src/Zigbee.h b/libraries/Zigbee/src/Zigbee.h index cf58dc8d5d8..c97be9378a8 100644 --- a/libraries/Zigbee/src/Zigbee.h +++ b/libraries/Zigbee/src/Zigbee.h @@ -18,3 +18,5 @@ #include "ep/ZigbeeFlowSensor.h" #include "ep/ZigbeeOccupancySensor.h" #include "ep/ZigbeeCarbonDioxideSensor.h" +#include "ep/ZigbeeContactSwitch.h" +#include "ep/ZigbeeDoorWindowHandle.h" diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index eae81fafbc3..069db95aac0 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -118,6 +118,7 @@ class ZigbeeEP { virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {}; + virtual void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {}; virtual void addBoundDevice(zb_device_params_t *device) { _bound_devices.push_back(device); diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 3b4992d81db..52c38eb0633 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -10,6 +10,7 @@ static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_mes 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_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message); +static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_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 @@ -24,6 +25,9 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, case ESP_ZB_CORE_CMD_IAS_ZONE_ZONE_STATUS_CHANGE_NOT_ID: ret = zb_cmd_ias_zone_status_change_handler((esp_zb_zcl_ias_zone_status_change_notification_message_t *)message); break; + case ESP_ZB_CORE_IAS_ZONE_ENROLL_RESPONSE_VALUE_CB_ID: + ret = zb_cmd_ias_zone_enroll_response_handler((esp_zb_zcl_ias_zone_enroll_response_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; } @@ -160,6 +164,24 @@ static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone return ESP_OK; } +static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) { + if (!message) { + log_e("Empty message"); + return ESP_FAIL; + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; + } + log_v("IAS Zone Enroll Response received"); + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (message->info.dst_endpoint == (*it)->getEndpoint()) { + (*it)->zbIASZoneEnrollResponse(message); + } + } + 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"); diff --git a/libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp new file mode 100644 index 00000000000..9a3551b0581 --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp @@ -0,0 +1,96 @@ +#include "ZigbeeContactSwitch.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +esp_zb_cluster_list_t *zigbee_contact_switch_clusters_create(zigbee_contact_switch_cfg_t *contact_switch) { + esp_zb_basic_cluster_cfg_t *basic_cfg = contact_switch ? &(contact_switch->basic_cfg) : NULL; + esp_zb_identify_cluster_cfg_t *identify_cfg = contact_switch ? &(contact_switch->identify_cfg) : NULL; + esp_zb_ias_zone_cluster_cfg_t *ias_zone_cfg = contact_switch ? &(contact_switch->ias_zone_cfg) : NULL; + esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, esp_zb_ias_zone_cluster_create(ias_zone_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + return cluster_list; +} + +ZigbeeContactSwitch::ZigbeeContactSwitch(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_IAS_ZONE_ID; + _zone_status = 0; + _zone_id = 0xff; + _ias_cie_endpoint = 1; + + //Create custom contact switch configuration + zigbee_contact_switch_cfg_t contact_switch_cfg = ZIGBEE_DEFAULT_CONTACT_SWITCH_CONFIG(); + _cluster_list = zigbee_contact_switch_clusters_create(&contact_switch_cfg); + + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_IAS_ZONE_ID, .app_device_version = 0}; +} + +void ZigbeeContactSwitch::setIASClientEndpoint(uint8_t ep_number) { + _ias_cie_endpoint = ep_number; +} + +void ZigbeeContactSwitch::setClosed() { + log_v("Setting Contact switch to closed"); + uint8_t closed = 0; // ALARM1 = 0, ALARM2 = 0 + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &closed, false + ); + esp_zb_lock_release(); + _zone_status = closed; + report(); +} + +void ZigbeeContactSwitch::setOpen() { + log_v("Setting Contact switch to open"); + uint8_t open = ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1 | ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM2; // ALARM1 = 1, ALARM2 = 1 + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &open, false); + esp_zb_lock_release(); + _zone_status = open; + report(); +} + +void ZigbeeContactSwitch::report() { + /* Send IAS Zone status changed notification command */ + + esp_zb_zcl_ias_zone_status_change_notif_cmd_t status_change_notif_cmd; + status_change_notif_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; + status_change_notif_cmd.zcl_basic_cmd.src_endpoint = _endpoint; + status_change_notif_cmd.zcl_basic_cmd.dst_endpoint = _ias_cie_endpoint; //default is 1 + memcpy(status_change_notif_cmd.zcl_basic_cmd.dst_addr_u.addr_long, _ias_cie_addr, sizeof(esp_zb_ieee_addr_t)); + + status_change_notif_cmd.zone_status = _zone_status; + status_change_notif_cmd.extend_status = 0; + status_change_notif_cmd.zone_id = _zone_id; + status_change_notif_cmd.delay = 0; + + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_ias_zone_status_change_notif_cmd_req(&status_change_notif_cmd); + esp_zb_lock_release(); + log_v("IAS Zone status changed notification sent"); +} + +void ZigbeeContactSwitch::zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) { + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE) { + log_v("IAS Zone Enroll Response: zone id(%d), status(%d)", message->zone_id, message->response_code); + if (message->response_code == ESP_ZB_ZCL_IAS_ZONE_ENROLL_RESPONSE_CODE_SUCCESS) { + log_v("IAS Zone Enroll Response: success"); + esp_zb_lock_acquire(portMAX_DELAY); + memcpy( + _ias_cie_addr, + (*(esp_zb_ieee_addr_t *) + esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_IAS_CIE_ADDRESS_ID) + ->data_p), + sizeof(esp_zb_ieee_addr_t) + ); + esp_zb_lock_release(); + _zone_id = message->zone_id; + } + + } else { + log_w("Received message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster); + } +} + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeContactSwitch.h b/libraries/Zigbee/src/ep/ZigbeeContactSwitch.h new file mode 100644 index 00000000000..692caf092ba --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeContactSwitch.h @@ -0,0 +1,67 @@ +/* Class of Zigbee contact switch (IAS Zone) endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +// clang-format off +#define ZIGBEE_DEFAULT_CONTACT_SWITCH_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, \ + }, \ + .ias_zone_cfg = \ + { \ + .zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, \ + .zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_CONTACT_SWITCH, \ + .zone_status = 0, \ + .ias_cie_addr = ESP_ZB_ZCL_ZONE_IAS_CIE_ADDR_DEFAULT, \ + .zone_id = 0xff, \ + .zone_ctx = {0, 0, 0, 0}, \ + }, \ + } +// clang-format on + +typedef struct zigbee_contact_switch_cfg_s { + esp_zb_basic_cluster_cfg_t basic_cfg; + esp_zb_identify_cluster_cfg_t identify_cfg; + esp_zb_ias_zone_cluster_cfg_t ias_zone_cfg; +} zigbee_contact_switch_cfg_t; + +class ZigbeeContactSwitch : public ZigbeeEP { +public: + ZigbeeContactSwitch(uint8_t endpoint); + ~ZigbeeContactSwitch() {} + + // Set the IAS Client endpoint number (default is 1) + void setIASClientEndpoint(uint8_t ep_number); + + // Set the contact switch value to closed + void setClosed(); + + // Set the contact switch value to open + void setOpen(); + + // Report the contact switch value, done automatically after setting the position + void report(); + +private: + void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) override; + uint8_t _zone_status; + uint8_t _zone_id; + esp_zb_ieee_addr_t _ias_cie_addr; + uint8_t _ias_cie_endpoint; +}; + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp b/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp new file mode 100644 index 00000000000..364b3ef6d9d --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp @@ -0,0 +1,108 @@ +#include "ZigbeeDoorWindowHandle.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +esp_zb_cluster_list_t *zigbee_door_window_handle_clusters_create(zigbee_door_window_handle_cfg_t *door_window_handle) { + esp_zb_basic_cluster_cfg_t *basic_cfg = door_window_handle ? &(door_window_handle->basic_cfg) : NULL; + esp_zb_identify_cluster_cfg_t *identify_cfg = door_window_handle ? &(door_window_handle->identify_cfg) : NULL; + esp_zb_ias_zone_cluster_cfg_t *ias_zone_cfg = door_window_handle ? &(door_window_handle->ias_zone_cfg) : NULL; + esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, esp_zb_ias_zone_cluster_create(ias_zone_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + return cluster_list; +} + +ZigbeeDoorWindowHandle::ZigbeeDoorWindowHandle(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_IAS_ZONE_ID; + _zone_status = 0; + _zone_id = 0xff; + _ias_cie_endpoint = 1; + + //Create custom door window handle configuration + zigbee_door_window_handle_cfg_t door_window_handle_cfg = ZIGBEE_DEFAULT_DOOR_WINDOW_HANDLE_CONFIG(); + _cluster_list = zigbee_door_window_handle_clusters_create(&door_window_handle_cfg); + + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_IAS_ZONE_ID, .app_device_version = 0}; +} + +void ZigbeeDoorWindowHandle::setIASClientEndpoint(uint8_t ep_number) { + _ias_cie_endpoint = ep_number; +} + +void ZigbeeDoorWindowHandle::setClosed() { + log_v("Setting Door/Window handle to closed"); + uint8_t closed = 0; // ALARM1 = 0, ALARM2 = 0 + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &closed, false + ); + esp_zb_lock_release(); + _zone_status = closed; + report(); +} + +void ZigbeeDoorWindowHandle::setOpen() { + log_v("Setting Door/Window handle to open"); + uint8_t open = ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1 | ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM2; // ALARM1 = 1, ALARM2 = 1 + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &open, false); + esp_zb_lock_release(); + _zone_status = open; + report(); +} + +void ZigbeeDoorWindowHandle::setTilted() { + log_v("Setting Door/Window handle to tilted"); + uint8_t tilted = ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1; // ALARM1 = 1, ALARM2 = 0 + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &tilted, false + ); + esp_zb_lock_release(); + _zone_status = tilted; + report(); +} + +void ZigbeeDoorWindowHandle::report() { + /* Send IAS Zone status changed notification command */ + + esp_zb_zcl_ias_zone_status_change_notif_cmd_t status_change_notif_cmd; + status_change_notif_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; + status_change_notif_cmd.zcl_basic_cmd.src_endpoint = _endpoint; + status_change_notif_cmd.zcl_basic_cmd.dst_endpoint = _ias_cie_endpoint; //default is 1 + memcpy(status_change_notif_cmd.zcl_basic_cmd.dst_addr_u.addr_long, _ias_cie_addr, sizeof(esp_zb_ieee_addr_t)); + + status_change_notif_cmd.zone_status = _zone_status; + status_change_notif_cmd.extend_status = 0; + status_change_notif_cmd.zone_id = _zone_id; + status_change_notif_cmd.delay = 0; + + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_ias_zone_status_change_notif_cmd_req(&status_change_notif_cmd); + esp_zb_lock_release(); + log_v("IAS Zone status changed notification sent"); +} + +void ZigbeeDoorWindowHandle::zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) { + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE) { + log_v("IAS Zone Enroll Response: zone id(%d), status(%d)", message->zone_id, message->response_code); + if (message->response_code == ESP_ZB_ZCL_IAS_ZONE_ENROLL_RESPONSE_CODE_SUCCESS) { + log_v("IAS Zone Enroll Response: success"); + esp_zb_lock_acquire(portMAX_DELAY); + memcpy( + _ias_cie_addr, + (*(esp_zb_ieee_addr_t *) + esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_IAS_CIE_ADDRESS_ID) + ->data_p), + sizeof(esp_zb_ieee_addr_t) + ); + esp_zb_lock_release(); + _zone_id = message->zone_id; + } + + } else { + log_w("Received message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster); + } +} + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.h b/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.h new file mode 100644 index 00000000000..e60316f8764 --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.h @@ -0,0 +1,71 @@ +/* Class of Zigbee door window handle (IAS Zone) endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +#define ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_DOOR_WINDOW_HANDLE 0x0016 +// clang-format off +#define ZIGBEE_DEFAULT_DOOR_WINDOW_HANDLE_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, \ + }, \ + .ias_zone_cfg = \ + { \ + .zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, \ + .zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_DOOR_WINDOW_HANDLE, \ + .zone_status = 0, \ + .ias_cie_addr = ESP_ZB_ZCL_ZONE_IAS_CIE_ADDR_DEFAULT, \ + .zone_id = 0xff, \ + .zone_ctx = {0, 0, 0, 0}, \ + }, \ + } +// clang-format on + +typedef struct zigbee_door_window_handle_cfg_s { + esp_zb_basic_cluster_cfg_t basic_cfg; + esp_zb_identify_cluster_cfg_t identify_cfg; + esp_zb_ias_zone_cluster_cfg_t ias_zone_cfg; +} zigbee_door_window_handle_cfg_t; + +class ZigbeeDoorWindowHandle : public ZigbeeEP { +public: + ZigbeeDoorWindowHandle(uint8_t endpoint); + ~ZigbeeDoorWindowHandle() {} + + // Set the IAS Client endpoint number (default is 1) + void setIASClientEndpoint(uint8_t ep_number); + + // Set the door/window handle value to closed + void setClosed(); + + // Set the door/window handle value to open + void setOpen(); + + // Set the door/window handle value to tilted + void setTilted(); + + // Report the door/window handle value, done automatically after setting the position + void report(); + +private: + void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) override; + uint8_t _zone_status; + uint8_t _zone_id; + esp_zb_ieee_addr_t _ias_cie_addr; + uint8_t _ias_cie_endpoint; +}; + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED From b385562fa601d80633d5d0389e059a5b8ba0f2b7 Mon Sep 17 00:00:00 2001 From: thekurtovic <40248206+thekurtovic@users.noreply.github.com> Date: Tue, 4 Feb 2025 04:52:45 -0500 Subject: [PATCH 18/33] NetworkEvents allow stack size to be changed. (#10805) * feat(net): Allow for event task custom stack size * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: Me No Dev Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- libraries/Network/src/NetworkEvents.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/Network/src/NetworkEvents.cpp b/libraries/Network/src/NetworkEvents.cpp index 1a7177bae61..161e55c5d01 100644 --- a/libraries/Network/src/NetworkEvents.cpp +++ b/libraries/Network/src/NetworkEvents.cpp @@ -8,6 +8,10 @@ #include "esp_task.h" #include "esp32-hal.h" +#ifndef ARDUINO_NETWORK_EVENT_TASK_STACK_SIZE +#define ARDUINO_NETWORK_EVENT_TASK_STACK_SIZE 4096 +#endif + NetworkEvents::NetworkEvents() : _arduino_event_group(NULL), _arduino_event_queue(NULL), _arduino_event_task_handle(NULL) {} NetworkEvents::~NetworkEvents() { @@ -61,8 +65,8 @@ bool NetworkEvents::initNetworkEvents() { [](void *self) { static_cast(self)->_checkForEvent(); }, - "arduino_events", // label - 4096, // event task's stack size + "arduino_events", // label + ARDUINO_NETWORK_EVENT_TASK_STACK_SIZE, // event task's stack size this, ESP_TASKD_EVENT_PRIO - 1, &_arduino_event_task_handle, ARDUINO_EVENT_RUNNING_CORE ); if (!_arduino_event_task_handle) { From 8b31d1e1a86c0b3bacc59ab1dacc22ee3c6507c0 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Tue, 4 Feb 2025 12:53:29 +0200 Subject: [PATCH 19/33] fix(ci): Disable some RainMaker examples on ESP32 (#10931) --- libraries/RainMaker/examples/RMakerCustomAirCooler/ci.json | 3 +++ libraries/RainMaker/examples/RMakerSonoffDualR3/ci.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/libraries/RainMaker/examples/RMakerCustomAirCooler/ci.json b/libraries/RainMaker/examples/RMakerCustomAirCooler/ci.json index 1c80eda1d90..ce63fe9ccf0 100644 --- a/libraries/RainMaker/examples/RMakerCustomAirCooler/ci.json +++ b/libraries/RainMaker/examples/RMakerCustomAirCooler/ci.json @@ -1,4 +1,7 @@ { + "targets": { + "esp32": false + }, "fqbn_append": "PartitionScheme=rainmaker_4MB", "requires": [ "CONFIG_ESP_RMAKER_WORK_QUEUE_TASK_STACK=[1-9][0-9]*" diff --git a/libraries/RainMaker/examples/RMakerSonoffDualR3/ci.json b/libraries/RainMaker/examples/RMakerSonoffDualR3/ci.json index 1c80eda1d90..ce63fe9ccf0 100644 --- a/libraries/RainMaker/examples/RMakerSonoffDualR3/ci.json +++ b/libraries/RainMaker/examples/RMakerSonoffDualR3/ci.json @@ -1,4 +1,7 @@ { + "targets": { + "esp32": false + }, "fqbn_append": "PartitionScheme=rainmaker_4MB", "requires": [ "CONFIG_ESP_RMAKER_WORK_QUEUE_TASK_STACK=[1-9][0-9]*" From 6a1127625c1e12f222435dafbfa56aacc2098c97 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Tue, 4 Feb 2025 13:22:20 +0200 Subject: [PATCH 20/33] fix(ota): Make sure that ArduinoOTA.end() is called in the destructor (#10932) --- libraries/ArduinoOTA/src/ArduinoOTA.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.cpp b/libraries/ArduinoOTA/src/ArduinoOTA.cpp index 0ab5466ab20..cb3ddc1e797 100644 --- a/libraries/ArduinoOTA/src/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/src/ArduinoOTA.cpp @@ -29,7 +29,7 @@ ArduinoOTAClass::ArduinoOTAClass() _start_callback(NULL), _end_callback(NULL), _error_callback(NULL), _progress_callback(NULL) {} ArduinoOTAClass::~ArduinoOTAClass() { - _udp_ota.stop(); + end(); } ArduinoOTAClass &ArduinoOTAClass::onStart(THandlerFunction fn) { From db0bbad9348f7d9bd0ce79152f004c61daf8793a Mon Sep 17 00:00:00 2001 From: Henning Kulander Date: Tue, 4 Feb 2025 16:52:02 +0100 Subject: [PATCH 21/33] Created Zigbee Endpoint for Window Covering. (#10914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(zigbee): Added Endpoint for Window Covering (#10913) * Added example Sketch. * Added window covering to CMakeLists.txt. * feat(zigbee): Window covering tilt support and fixes * fix(zigbee): Fix typos * ci(pre-commit): Apply automatic fixes * fix(ci): Fixes of typos --------- Co-authored-by: Jan Procházka <90197375+P-R-O-C-H-Y@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CMakeLists.txt | 1 + .../examples/Zigbee_Window_Covering/README.md | 69 ++++ .../Zigbee_Window_Covering.ino | 198 +++++++++++ .../examples/Zigbee_Window_Covering/ci.json | 6 + libraries/Zigbee/src/Zigbee.h | 1 + libraries/Zigbee/src/ZigbeeEP.h | 2 +- libraries/Zigbee/src/ZigbeeHandlers.cpp | 26 ++ .../Zigbee/src/ep/ZigbeeWindowCovering.cpp | 308 ++++++++++++++++++ .../Zigbee/src/ep/ZigbeeWindowCovering.h | 147 +++++++++ 9 files changed, 757 insertions(+), 1 deletion(-) create mode 100644 libraries/Zigbee/examples/Zigbee_Window_Covering/README.md create mode 100644 libraries/Zigbee/examples/Zigbee_Window_Covering/Zigbee_Window_Covering.ino create mode 100644 libraries/Zigbee/examples/Zigbee_Window_Covering/ci.json create mode 100644 libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp create mode 100644 libraries/Zigbee/src/ep/ZigbeeWindowCovering.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b3cb6c06cdc..40567d1b45c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -292,6 +292,7 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS libraries/Zigbee/src/ep/ZigbeeCarbonDioxideSensor.cpp libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp + libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp ) set(ARDUINO_LIBRARY_BLE_SRCS diff --git a/libraries/Zigbee/examples/Zigbee_Window_Covering/README.md b/libraries/Zigbee/examples/Zigbee_Window_Covering/README.md new file mode 100644 index 00000000000..469560a0e0c --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Window_Covering/README.md @@ -0,0 +1,69 @@ +# Arduino-ESP32 Window Covering Example + +This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) window covering device. + +To see if the communication with your Zigbee network works, use the Serial monitor and watch for output there. + +# Supported Targets + +Currently, this example supports the following targets. + +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +## Hardware Required + +* A USB cable for power supply and programming +* Board (ESP32-H2 or ESP32-C6) as Zigbee end device and upload the Zigbee_Window_Covering example +* Zigbee network / coordinator (Other board with switch examples or Zigbee2mqtt or ZigbeeHomeAssistant like application) + +### Configure the Project + +#### Using Arduino IDE + +To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits). + +* Before Compile/Verify, select the correct board: `Tools -> Board`. +* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ED (end device)` +* Select Tools / USB CDC On Boot: "Enabled" +* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs` +* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port. +* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`. + +## Troubleshooting + +If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. It is recommended to do this if you re-flash the coordinator. +You can do the following: + +* In the Arduino IDE go to the Tools menu and set `Erase All Flash Before Sketch Upload` to `Enabled`. +* Add to the sketch `Zigbee.factoryReset();` to reset the device and Zigbee stack. + +By default, the coordinator network is closed after rebooting or flashing new firmware. +To open the network you have 2 options: + +* Open network after reboot by setting `Zigbee.setRebootOpenNetwork(time);` before calling `Zigbee.begin();`. +* In application you can anytime call `Zigbee.openNetwork(time);` to open the network for devices to join. + +***Important: Make sure you are using a good quality USB cable and that you have a reliable power source*** + +* **LED not blinking:** Check the wiring connection and the IO selection. +* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed. +* **COM port not detected:** Check the USB cable and the USB to Serial driver installation. + +If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute). + +## Contribute + +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) + +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! + +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. + +## Resources + +* Official ESP32 Forum: [Link](https://esp32.com) +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) +* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf) +* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf) +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) diff --git a/libraries/Zigbee/examples/Zigbee_Window_Covering/Zigbee_Window_Covering.ino b/libraries/Zigbee/examples/Zigbee_Window_Covering/Zigbee_Window_Covering.ino new file mode 100644 index 00000000000..c7f9cf84f74 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Window_Covering/Zigbee_Window_Covering.ino @@ -0,0 +1,198 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrates Zigbee Window Covering. + * + * The example demonstrates how to use Zigbee library to create a end device window covering device. + * The window covering is a Zigbee end device, which is moving the blinds (lift+tilt) and reporting + * its current position to the Zigbee network. + * + * Use setCoveringType() to set the type of covering (blind, shade, etc.). + * + * The example also demonstrates how to use the button to manually control the lift position. + * + * 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 hennikul and Jan Procházka (https://github.com/P-R-O-C-H-Y/) + */ + +#ifndef ZIGBEE_MODE_ED +#error "Zigbee end device mode is not selected in Tools->Zigbee mode" +#endif + +#include "ZigbeeCore.h" +#include "ep/ZigbeeWindowCovering.h" + +#define ZIGBEE_COVERING_ENDPOINT 10 +#define BUTTON_PIN 9 // ESP32-C6/H2 Boot button + +#define MAX_LIFT 200 // centimeters from open position (0-900) +#define MIN_LIFT 0 + +#define MAX_TILT 40 // centimeters from open position (0-900) +#define MIN_TILT 0 + +uint16_t currentLift = MAX_LIFT; +uint8_t currentLiftPercentage = 100; + +uint16_t currentTilt = MAX_TILT; +uint8_t currentTiltPercentage = 100; + +ZigbeeWindowCovering zbCovering = ZigbeeWindowCovering(ZIGBEE_COVERING_ENDPOINT); + +void setup() { + Serial.begin(115200); + + // Init button for factory reset + pinMode(BUTTON_PIN, INPUT_PULLUP); + + // Optional: set Zigbee device name and model + zbCovering.setManufacturerAndModel("Espressif", "WindowBlinds"); + + // Set proper covering type, it defines which attributes are available + zbCovering.setCoveringType(BLIND_LIFT_AND_TILT); + + // Set configuration: operational, online, not commands_reversed, lift / tilt closed_loop, lift / tilt encoder_controlled + zbCovering.setConfigStatus(true, true, false, true, true, true, true); + + // Set mode: not motor_reversed, calibration_mode, not maintenance_mode, not leds_on + zbCovering.setMode(false, true, false, false); + + // Set limits of motion + zbCovering.setLimits(MIN_LIFT, MAX_LIFT, MIN_TILT, MAX_TILT); + + // Set callback function for open, close, filt and tilt change, stop + zbCovering.onOpen(fullOpen); + zbCovering.onClose(fullClose); + zbCovering.onGoToLiftPercentage(goToLiftPercentage); + zbCovering.onGoToTiltPercentage(goToTiltPercentage); + zbCovering.onStop(stopMotor); + + // Add endpoint to Zigbee Core + Serial.println("Adding ZigbeeWindowCovering endpoint to Zigbee Core"); + Zigbee.addEndpoint(&zbCovering); + + // When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE + Serial.println("Calling Zigbee.begin()"); + if (!Zigbee.begin()) { + Serial.println("Zigbee failed to start!"); + Serial.println("Rebooting..."); + ESP.restart(); + } + Serial.println("Connecting to network"); + while (!Zigbee.connected()) { + Serial.print("."); + delay(100); + } + Serial.println(); + + // Set initial position + zbCovering.setLiftPercentage(currentLiftPercentage); + zbCovering.setTiltPercentage(currentTiltPercentage); +} + +void loop() { + // Checking 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("Resetting Zigbee to factory settings, reboot.\n"); + Zigbee.factoryReset(); + delay(30000); + } + } + // Manual lift control simulation by pressing button + manualControl(); + } + delay(500); +} + +void fullOpen() { + /* This is where you would trigger your motor to go to full open state, currentLift should + be updated until full open has been reached in order to provide feedback to controller of actual position + The stop can be always called, so the movement can be stopped at any time */ + + // Our cover updates instantly! + currentLift = MAX_LIFT; + currentLiftPercentage = 100; + Serial.println("Opening cover"); + // Update the current position + zbCovering.setLiftPercentage(currentLiftPercentage); +} + +void fullClose() { + /* This is where you would trigger your motor to go to full close state, currentLift should + be updated until full close has been reached in order to provide feedback to controller of actual position + The stop can be always called, so the movement can be stopped at any time */ + + // Our cover updates instantly! + currentLift = MIN_LIFT; + currentLiftPercentage = 0; + Serial.println("Closing cover"); + // Update the current position + zbCovering.setLiftPercentage(currentLiftPercentage); +} + +void goToLiftPercentage(uint8_t liftPercentage) { + /* This is where you would trigger your motor to go towards liftPercentage, currentLift should + be updated until liftPercentage has been reached in order to provide feedback to controller */ + + // Our simulated cover updates instantly! + currentLift = (liftPercentage * MAX_LIFT) / 100; + currentLiftPercentage = liftPercentage; + Serial.printf("New requested lift from Zigbee: %d (%d)\n", currentLift, liftPercentage); + + // Update the current position + zbCovering.setLiftPercentage(currentLiftPercentage); //or setLiftPosition() +} + +void goToTiltPercentage(uint8_t tiltPercentage) { + /* This is where you would trigger your motor to go towards tiltPercentage, currentTilt should + be updated until tiltPercentage has been reached in order to provide feedback to controller */ + + // Our simulated cover updates instantly! + currentTilt = (tiltPercentage * MAX_TILT) / 100; + currentTiltPercentage = tiltPercentage; + Serial.printf("New requested tilt from Zigbee: %d (%d)\n", currentTilt, tiltPercentage); + + // Update the current position + zbCovering.setTiltPercentage(currentTiltPercentage); //or setTiltPosition() +} + +void stopMotor() { + // Motor can be stopped while moving cover toward current target, when stopped the actual position should be updated + Serial.println("Stopping motor"); + // Update the current position of both lift and tilt + zbCovering.setLiftPercentage(currentLiftPercentage); + zbCovering.setTiltPercentage(currentTiltPercentage); +} + +void manualControl() { + // Simulate lift percentage move by increasing it by 20% each time + currentLiftPercentage += 20; + if (currentLiftPercentage > 100) { + currentLiftPercentage = 0; + } + zbCovering.setLiftPercentage(currentLiftPercentage); + // Also setLiftPosition() can be used to set the exact position instead of percentage +} diff --git a/libraries/Zigbee/examples/Zigbee_Window_Covering/ci.json b/libraries/Zigbee/examples/Zigbee_Window_Covering/ci.json new file mode 100644 index 00000000000..7b7ccef8ed7 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Window_Covering/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed", + "requires": [ + "CONFIG_SOC_IEEE802154_SUPPORTED=y" + ] +} diff --git a/libraries/Zigbee/src/Zigbee.h b/libraries/Zigbee/src/Zigbee.h index c97be9378a8..49bfa44d5c1 100644 --- a/libraries/Zigbee/src/Zigbee.h +++ b/libraries/Zigbee/src/Zigbee.h @@ -20,3 +20,4 @@ #include "ep/ZigbeeCarbonDioxideSensor.h" #include "ep/ZigbeeContactSwitch.h" #include "ep/ZigbeeDoorWindowHandle.h" +#include "ep/ZigbeeWindowCovering.h" diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index 069db95aac0..fce0500ef0d 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -115,8 +115,8 @@ class ZigbeeEP { 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); + virtual void zbWindowCoveringMovementCmd(const esp_zb_zcl_window_covering_movement_message_t *message) {}; virtual void zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented - virtual void zbIASZoneStatusChangeNotification(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message) {}; virtual void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) {}; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 52c38eb0633..575b25ef404 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -12,6 +12,7 @@ static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_re static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message); static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message); static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message); +static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_message_t *message); // Zigbee action handlers [[maybe_unused]] @@ -28,6 +29,9 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, case ESP_ZB_CORE_IAS_ZONE_ENROLL_RESPONSE_VALUE_CB_ID: ret = zb_cmd_ias_zone_enroll_response_handler((esp_zb_zcl_ias_zone_enroll_response_message_t *)message); break; + case ESP_ZB_CORE_WINDOW_COVERING_MOVEMENT_CB_ID: + ret = zb_window_covering_movement_resp_handler((esp_zb_zcl_window_covering_movement_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; } @@ -198,4 +202,26 @@ static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_m return ESP_OK; } +static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_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), command(0x%x), payload(%d)", message->info.dst_endpoint, message->info.cluster, message->command, + message->payload + ); + + // 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()) { + (*it)->zbWindowCoveringMovementCmd(message); //method zbWindowCoveringMovementCmd must be implemented in specific EP class + } + } + return ESP_OK; +} + #endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp new file mode 100644 index 00000000000..970017165e2 --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp @@ -0,0 +1,308 @@ + +#include "ZigbeeWindowCovering.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +esp_zb_cluster_list_t *ZigbeeWindowCovering::zigbee_window_covering_clusters_create(zigbee_window_covering_cfg_t *window_covering_cfg) { + esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_basic_cluster_create(&window_covering_cfg->basic_cfg); + esp_zb_attribute_list_t *esp_zb_identify_cluster = esp_zb_identify_cluster_create(&window_covering_cfg->identify_cfg); + esp_zb_attribute_list_t *esp_zb_groups_cluster = esp_zb_groups_cluster_create(&window_covering_cfg->groups_cfg); + esp_zb_attribute_list_t *esp_zb_scenes_cluster = esp_zb_scenes_cluster_create(&window_covering_cfg->scenes_cfg); + esp_zb_attribute_list_t *esp_zb_window_covering_cluster = esp_zb_window_covering_cluster_create(&window_covering_cfg->window_covering_cfg); + + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_ID, &_current_lift_percentage + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID, &_current_tilt_percentage + ); + esp_zb_window_covering_cluster_add_attr(esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_ID, &_current_lift_position); + esp_zb_window_covering_cluster_add_attr(esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_ID, &_current_lift_position); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_LIFT_ID, &_installed_open_limit_lift + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_TILT_ID, &_installed_open_limit_tilt + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_LIFT_ID, &_installed_closed_limit_lift + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_TILT_ID, &_installed_closed_limit_tilt + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHYSICAL_CLOSED_LIMIT_LIFT_ID, &_physical_closed_limit_lift + ); + esp_zb_window_covering_cluster_add_attr( + esp_zb_window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHY_CLOSED_LIMIT_TILT_ID, &_physical_closed_limit_lift + ); + + // ------------------------------ Create cluster list ------------------------------ + esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(esp_zb_cluster_list, esp_zb_identify_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_groups_cluster(esp_zb_cluster_list, esp_zb_groups_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_scenes_cluster(esp_zb_cluster_list, esp_zb_scenes_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_window_covering_cluster(esp_zb_cluster_list, esp_zb_window_covering_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + + return esp_zb_cluster_list; +} + +ZigbeeWindowCovering::ZigbeeWindowCovering(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID; + + // set default values for window covering attributes + _current_lift_percentage = ESP_ZB_ZCL_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_DEFAULT_VALUE; + _current_tilt_percentage = ESP_ZB_ZCL_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_DEFAULT_VALUE; + _installed_open_limit_lift = ESP_ZB_ZCL_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_LIFT_DEFAULT_VALUE; + _installed_closed_limit_lift = ESP_ZB_ZCL_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_LIFT_DEFAULT_VALUE; + _installed_open_limit_tilt = ESP_ZB_ZCL_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_TILT_DEFAULT_VALUE; + _installed_closed_limit_tilt = ESP_ZB_ZCL_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_TILT_DEFAULT_VALUE; + _current_lift_position = ESP_ZB_ZCL_WINDOW_COVERING_CURRENT_POSITION_LIFT_DEFAULT_VALUE; + _current_tilt_position = ESP_ZB_ZCL_WINDOW_COVERING_CURRENT_POSITION_TILT_DEFAULT_VALUE; + _physical_closed_limit_lift = ESP_ZB_ZCL_WINDOW_COVERING_PHYSICAL_CLOSED_LIMIT_LIFT_DEFAULT_VALUE; + _physical_closed_limit_tilt = ESP_ZB_ZCL_WINDOW_COVERING_PHY_CLOSED_LIMIT_TILT_DEFAULT_VALUE; + + // Create custom window covering configuration + zigbee_window_covering_cfg_t window_covering_cfg = ZIGBEE_DEFAULT_WINDOW_COVERING_CONFIG(); + _cluster_list = zigbee_window_covering_clusters_create(&window_covering_cfg); + + _ep_config = { + .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID, .app_device_version = 0 + }; +} + +// Configuration methods for window covering +void ZigbeeWindowCovering::setCoveringType(ZigbeeWindowCoveringType covering_type) { + esp_zb_attribute_list_t *window_covering_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_WINDOW_COVERING_TYPE_ID, (void *)&covering_type); +} + +void ZigbeeWindowCovering::setConfigStatus( + bool operational, bool online, bool commands_reversed, bool lift_closed_loop, bool tilt_closed_loop, bool lift_encoder_controlled, + bool tilt_encoder_controlled +) { + uint8_t config_status = (operational ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_OPERATIONAL : 0) | (online ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_ONLINE : 0) + | (commands_reversed ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_REVERSE_COMMANDS : 0) + | (lift_closed_loop ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_LIFT_CONTROL_IS_CLOSED_LOOP : 0) + | (tilt_closed_loop ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_TILT_CONTROL_IS_CLOSED_LOOP : 0) + | (lift_encoder_controlled ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_LIFT_ENCODER_CONTROLLED : 0) + | (tilt_encoder_controlled ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_TILT_ENCODER_CONTROLLED : 0); + + log_v("Updating window covering config status to %d", config_status); + + esp_zb_attribute_list_t *window_covering_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID, (void *)&config_status); +} + +void ZigbeeWindowCovering::setMode(bool motor_reversed, bool calibration_mode, bool maintenance_mode, bool leds_on) { + uint8_t mode = (motor_reversed ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_REVERSED_MOTOR_DIRECTION : 0) + | (calibration_mode ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_RUN_IN_CALIBRATION_MODE : 0) + | (maintenance_mode ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_MOTOR_IS_RUNNING_IN_MAINTENANCE_MODE : 0) + | (leds_on ? ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_LEDS_WILL_DISPLAY_FEEDBACK : 0); + + log_v("Updating window covering mode to %d", mode); + + esp_zb_attribute_list_t *window_covering_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_MODE_ID, (void *)&mode); +} + +void ZigbeeWindowCovering::setLimits( + uint16_t installed_open_limit_lift, uint16_t installed_closed_limit_lift, uint16_t installed_open_limit_tilt, uint16_t installed_closed_limit_tilt +) { + _installed_open_limit_lift = installed_open_limit_lift; + _installed_closed_limit_lift = installed_closed_limit_lift; + _physical_closed_limit_lift = installed_closed_limit_lift; + _installed_open_limit_tilt = installed_open_limit_tilt; + _installed_closed_limit_tilt = installed_closed_limit_tilt; + _physical_closed_limit_tilt = installed_closed_limit_tilt; + + esp_zb_attribute_list_t *window_covering_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_LIFT_ID, (void *)&_installed_open_limit_lift); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_LIFT_ID, (void *)&_installed_closed_limit_lift); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_OPEN_LIMIT_TILT_ID, (void *)&_installed_open_limit_tilt); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_INSTALLED_CLOSED_LIMIT_TILT_ID, (void *)&_installed_closed_limit_tilt); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHYSICAL_CLOSED_LIMIT_LIFT_ID, (void *)&_physical_closed_limit_lift); + esp_zb_cluster_update_attr(window_covering_cluster, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_PHY_CLOSED_LIMIT_TILT_ID, (void *)&_physical_closed_limit_tilt); +} + +// Callback for handling incoming messages and commands +void ZigbeeWindowCovering::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_WINDOW_COVERING) { + log_v("Received attribute id: 0x%x / data.type: 0x%x", message->attribute.id, message->attribute.data.type); + if (message->attribute.id == ESP_ZB_ZCL_ATTR_WINDOW_COVERING_MODE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_8BITMAP) { + uint8_t mode = *(uint8_t *)message->attribute.data.value; + bool motor_reversed = mode & ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_REVERSED_MOTOR_DIRECTION; + bool calibration_mode = mode & ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_RUN_IN_CALIBRATION_MODE; + bool maintenance_mode = mode & ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_MOTOR_IS_RUNNING_IN_MAINTENANCE_MODE; + bool leds_on = mode & ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_LEDS_WILL_DISPLAY_FEEDBACK; + log_v( + "Updating window covering mode to motor reversed: %d, calibration mode: %d, maintenance mode: %d, leds on: %d", motor_reversed, calibration_mode, + maintenance_mode, leds_on + ); + setMode(motor_reversed, calibration_mode, maintenance_mode, leds_on); + //Update Configuration status with motor reversed status + uint8_t config_status; + config_status = (*(uint8_t *)esp_zb_zcl_get_attribute( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID + ) + ->data_p); + config_status = motor_reversed ? config_status | ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_REVERSE_COMMANDS + : config_status & ~ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_REVERSE_COMMANDS; + log_v("Updating window covering config status to %d", config_status); + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CONFIG_STATUS_ID, &config_status, + false + ); + esp_zb_lock_release(); + return; + } + } else { + log_w("Received message ignored. Cluster ID: %d not supported for Window Covering", message->info.cluster); + } +} + +void ZigbeeWindowCovering::zbWindowCoveringMovementCmd(const esp_zb_zcl_window_covering_movement_message_t *message) { + // check the data and call right method + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING) { + if (message->command == ESP_ZB_ZCL_CMD_WINDOW_COVERING_UP_OPEN) { + open(); + return; + } else if (message->command == ESP_ZB_ZCL_CMD_WINDOW_COVERING_DOWN_CLOSE) { + close(); + return; + } else if (message->command == ESP_ZB_ZCL_CMD_WINDOW_COVERING_STOP) { + stop(); + return; + } else if (message->command == ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_LIFT_PERCENTAGE) { + if (_current_lift_percentage != message->payload.percentage_lift_value) { + _current_lift_percentage = message->payload.percentage_lift_value; + goToLiftPercentage(_current_lift_percentage); + } + return; + } else if (message->command == ESP_ZB_ZCL_CMD_WINDOW_COVERING_GO_TO_TILT_PERCENTAGE) { + if (_current_tilt_percentage != message->payload.percentage_tilt_value) { + _current_tilt_percentage = message->payload.percentage_tilt_value; + goToTiltPercentage(_current_tilt_percentage); + } + } else { + log_w("Received message ignored. Command: %d not supported for Window Covering", message->command); + } + } else { + log_w("Received message ignored. Cluster ID: %d not supported for Window Covering", message->info.cluster); + } +} + +void ZigbeeWindowCovering::open() { + if (_on_open) { + _on_open(); + } +} + +void ZigbeeWindowCovering::close() { + if (_on_close) { + _on_close(); + } +} + +void ZigbeeWindowCovering::goToLiftPercentage(uint8_t lift_percentage) { + if (_on_go_to_lift_percentage) { + _on_go_to_lift_percentage(lift_percentage); + } +} + +void ZigbeeWindowCovering::goToTiltPercentage(uint8_t tilt_percentage) { + if (_on_go_to_tilt_percentage) { + _on_go_to_tilt_percentage(tilt_percentage); + } +} + +void ZigbeeWindowCovering::stop() { + if (_on_stop) { + _on_stop(); + } +} + +// Methods to control window covering from user application +void ZigbeeWindowCovering::setLiftPosition(uint16_t lift_position) { + // Update both lift attributes + _current_lift_position = lift_position; + _current_lift_percentage = ((lift_position - _installed_open_limit_lift) * 100) / (_installed_closed_limit_lift - _installed_open_limit_lift); + + log_v("Updating window covering lift position to %d (%d%)", _current_lift_position, _current_lift_percentage); + // set lift state + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_ID, + &_current_lift_position, false + ); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_ID, + &_current_lift_percentage, false + ); + esp_zb_lock_release(); +} + +void ZigbeeWindowCovering::setLiftPercentage(uint8_t lift_percentage) { + // Update both lift attributes + _current_lift_percentage = lift_percentage; + _current_lift_position = _installed_open_limit_lift + ((_installed_closed_limit_lift - _installed_open_limit_lift) * lift_percentage) / 100; + + log_v("Updating window covering lift position to %d (%d%)", _current_lift_position, _current_lift_percentage); + // set lift state + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_ID, + &_current_lift_position, false + ); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_LIFT_PERCENTAGE_ID, + &_current_lift_percentage, false + ); + esp_zb_lock_release(); +} + +void ZigbeeWindowCovering::setTiltPosition(uint16_t tilt_position) { + // Update both tilt attributes + _current_tilt_position = tilt_position; + _current_tilt_percentage = ((tilt_position - _installed_open_limit_tilt) * 100) / (_installed_closed_limit_tilt - _installed_open_limit_tilt); + + log_v("Updating window covering tilt position to %d (%d%)", _current_tilt_position, _current_tilt_percentage); + // set lift state + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_ID, + &_current_tilt_position, false + ); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID, + &_current_tilt_percentage, false + ); + esp_zb_lock_release(); +} + +void ZigbeeWindowCovering::setTiltPercentage(uint8_t tilt_percentage) { + // Update both tilt attributes + _current_tilt_percentage = tilt_percentage; + _current_tilt_position = _installed_open_limit_lift + ((_installed_closed_limit_tilt - _installed_open_limit_tilt) * tilt_percentage) / 100; + + log_v("Updating window covering tilt position to %d (%d%)", _current_tilt_position, _current_tilt_percentage); + // set lift state + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_ID, + &_current_tilt_position, false + ); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_WINDOW_COVERING_CURRENT_POSITION_TILT_PERCENTAGE_ID, + &_current_tilt_percentage, false + ); + esp_zb_lock_release(); +} + +#endif // SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h new file mode 100644 index 00000000000..5cc2b13701a --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h @@ -0,0 +1,147 @@ +/* Class of Zigbee Window Covering endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +// Window covering types supported by Zigbee Window Covering cluster +enum ZigbeeWindowCoveringType { + ROLLERSHADE = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_ROLLERSHADE, // LIFT support + ROLLERSHADE_2_MOTOR = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_ROLLERSHADE_2_MOTOR, // LIFT support + ROLLERSHADE_EXTERIOR = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_ROLLERSHADE_EXTERIOR, // LIFT support + ROLLERSHADE_EXTERIOR_2_MOTOR = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_ROLLERSHADE_EXTERIOR_2_MOTOR, // LIFT support + DRAPERY = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_DRAPERY, // LIFT support + AWNING = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_AWNING, // LIFT support + SHUTTER = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_SHUTTER, // TILT support + BLIND_TILT_ONLY = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_TILT_BLIND_TILT_ONLY, // TILT support + BLIND_LIFT_AND_TILT = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_TILT_BLIND_LIFT_AND_TILT, // LIFT and TILT support + PROJECTOR_SCREEN = ESP_ZB_ZCL_ATTR_WINDOW_COVERING_TYPE_PROJECTOR_SCREEN, // LIFT support +}; + +// clang-format off +#define ZIGBEE_DEFAULT_WINDOW_COVERING_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, \ + }, \ + .groups_cfg = \ + { \ + .groups_name_support_id = ESP_ZB_ZCL_GROUPS_NAME_SUPPORT_DEFAULT_VALUE, \ + }, \ + .scenes_cfg = \ + { \ + .scenes_count = ESP_ZB_ZCL_SCENES_SCENE_COUNT_DEFAULT_VALUE, \ + .current_scene = ESP_ZB_ZCL_SCENES_CURRENT_SCENE_DEFAULT_VALUE, \ + .current_group = ESP_ZB_ZCL_SCENES_CURRENT_GROUP_DEFAULT_VALUE, \ + .scene_valid = ESP_ZB_ZCL_SCENES_SCENE_VALID_DEFAULT_VALUE, \ + .name_support = ESP_ZB_ZCL_SCENES_NAME_SUPPORT_DEFAULT_VALUE, \ + }, \ + .window_covering_cfg = \ + { \ + .covering_type = ESP_ZB_ZCL_WINDOW_COVERING_WINDOW_COVERING_TYPE_DEFAULT_VALUE, \ + .covering_status = ESP_ZB_ZCL_WINDOW_COVERING_CONFIG_STATUS_DEFAULT_VALUE, \ + .covering_mode = ESP_ZB_ZCL_WINDOW_COVERING_MODE_DEFAULT_VALUE, \ + }, \ + } +// clang-format on + +typedef struct zigbee_window_covering_cfg_s { + esp_zb_basic_cluster_cfg_t basic_cfg; + esp_zb_identify_cluster_cfg_t identify_cfg; + esp_zb_groups_cluster_cfg_t groups_cfg; + esp_zb_scenes_cluster_cfg_t scenes_cfg; + esp_zb_window_covering_cluster_cfg_t window_covering_cfg; +} zigbee_window_covering_cfg_t; + +class ZigbeeWindowCovering : public ZigbeeEP { +public: + ZigbeeWindowCovering(uint8_t endpoint); + ~ZigbeeWindowCovering(); + + // Set the callback functions for the window covering commands + void onOpen(void (*callback)()) { + _on_open = callback; + } + void onClose(void (*callback)()) { + _on_close = callback; + } + void onGoToLiftPercentage(void (*callback)(uint8_t)) { + _on_go_to_lift_percentage = callback; + } + void onGoToTiltPercentage(void (*callback)(uint8_t)) { + _on_go_to_tilt_percentage = callback; + } + void onStop(void (*callback)()) { + _on_stop = callback; + } + + // Set the window covering position in centimeters or percentage (0-100) + void setLiftPosition(uint16_t lift_position); + void setLiftPercentage(uint8_t lift_percentage); + void setTiltPosition(uint16_t tilt_position); + void setTiltPercentage(uint8_t tilt_percentage); + + // Set the window covering type (see ZigbeeWindowCoveringType) + void setCoveringType(ZigbeeWindowCoveringType covering_type); + + // Set window covering config/status, for more info see esp_zb_zcl_window_covering_config_status_t + void setConfigStatus( + bool operational, bool online, bool commands_reversed, bool lift_closed_loop, bool tilt_closed_loop, bool lift_encoder_controlled, + bool tilt_encoder_controlled + ); + + // Set configuration mode of window covering, for more info see esp_zb_zcl_window_covering_mode_t + void setMode(bool motor_reversed, bool calibration_mode, bool maintenance_mode, bool leds_on); + + // Set limits of motion, for more info see esp_zb_zcl_window_covering_info_attr_t + void setLimits( + uint16_t installed_open_limit_lift, uint16_t installed_closed_limit_lift, uint16_t installed_open_limit_tilt, uint16_t installed_closed_limit_tilt + ); + +private: + void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; + void zbWindowCoveringMovementCmd(const esp_zb_zcl_window_covering_movement_message_t *message) override; + + // Create window covering cluster list + esp_zb_cluster_list_t *zigbee_window_covering_clusters_create(zigbee_window_covering_cfg_t *window_covering_cfg); + + void open(); + void close(); + void goToLiftPercentage(uint8_t); + void goToTiltPercentage(uint8_t); + void stop(); + + // callback function to be called on lift percentage change (lift percentage) + void (*_on_open)(); + void (*_on_close)(); + void (*_on_go_to_lift_percentage)(uint8_t); + void (*_on_go_to_tilt_percentage)(uint8_t); + void (*_on_stop)(); + + // Widows covering lift attributes + uint8_t _current_lift_percentage; + uint16_t _current_lift_position; + uint16_t _installed_open_limit_lift; + uint16_t _installed_closed_limit_lift; + uint16_t _physical_closed_limit_lift; + + // Windows covering tilt attributes + uint8_t _current_tilt_percentage; + uint16_t _current_tilt_position; + uint16_t _installed_open_limit_tilt; + uint16_t _installed_closed_limit_tilt; + uint16_t _physical_closed_limit_tilt; +}; + +#endif // SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED From 6fcaf690970401e63e75609628663b862a928b68 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 6 Feb 2025 11:19:27 +0200 Subject: [PATCH 22/33] fix(wifi): Make sure that esp-hosted events are propagated (#10939) --- libraries/Network/src/NetworkInterface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/Network/src/NetworkInterface.cpp b/libraries/Network/src/NetworkInterface.cpp index 4f310821204..01790ec2493 100644 --- a/libraries/Network/src/NetworkInterface.cpp +++ b/libraries/Network/src/NetworkInterface.cpp @@ -81,7 +81,7 @@ void NetworkInterface::_onIpEvent(int32_t event_id, void *event_data) { ); #endif memcpy(&arduino_event.event_info.got_ip, event_data, sizeof(ip_event_got_ip_t)); -#if SOC_WIFI_SUPPORTED +#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED if (_interface_id == ESP_NETIF_ID_STA) { arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_GOT_IP; } else @@ -96,7 +96,7 @@ void NetworkInterface::_onIpEvent(int32_t event_id, void *event_data) { #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE log_v("%s Lost IP", desc()); #endif -#if SOC_WIFI_SUPPORTED +#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED if (_interface_id == ESP_NETIF_ID_STA) { arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_LOST_IP; } else @@ -123,7 +123,7 @@ void NetworkInterface::_onIpEvent(int32_t event_id, void *event_data) { ); #endif memcpy(&arduino_event.event_info.got_ip6, event_data, sizeof(ip_event_got_ip6_t)); -#if SOC_WIFI_SUPPORTED +#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED if (_interface_id == ESP_NETIF_ID_STA) { arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_GOT_IP6; } else if (_interface_id == ESP_NETIF_ID_AP) { @@ -136,7 +136,7 @@ void NetworkInterface::_onIpEvent(int32_t event_id, void *event_data) { arduino_event.event_id = ARDUINO_EVENT_ETH_GOT_IP6; } #endif /* CONFIG_LWIP_IPV6 */ -#if SOC_WIFI_SUPPORTED +#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED } else if (event_id == IP_EVENT_AP_STAIPASSIGNED && _interface_id == ESP_NETIF_ID_AP) { setStatusBits(ESP_NETIF_HAS_IP_BIT); #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE From 5ba4c21a990f46b61ae8c7913b81e15064b2a8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:06:07 +0100 Subject: [PATCH 23/33] fix(zigbee): Add default destructor and fix initialization of tm struct (#10943) --- libraries/Zigbee/src/ZigbeeEP.h | 2 +- libraries/Zigbee/src/ep/ZigbeeWindowCovering.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index fce0500ef0d..584873f50dd 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -95,7 +95,7 @@ class ZigbeeEP { void reportBatteryPercentage(); // Set time - void addTimeCluster(tm time = {0}, int32_t gmt_offset = 0); // gmt offset in seconds + void addTimeCluster(tm time = {}, int32_t gmt_offset = 0); // gmt offset in seconds void setTime(tm time); void setTimezone(int32_t gmt_offset); diff --git a/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h index 5cc2b13701a..08e0a35f737 100644 --- a/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h +++ b/libraries/Zigbee/src/ep/ZigbeeWindowCovering.h @@ -67,7 +67,7 @@ typedef struct zigbee_window_covering_cfg_s { class ZigbeeWindowCovering : public ZigbeeEP { public: ZigbeeWindowCovering(uint8_t endpoint); - ~ZigbeeWindowCovering(); + ~ZigbeeWindowCovering() {} // Set the callback functions for the window covering commands void onOpen(void (*callback)()) { From 250c1abf781eba8cec7a524090939c30e05b690c Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Wed, 12 Feb 2025 10:40:15 +0200 Subject: [PATCH 24/33] fix(i2s): Add missing initializer for I2S CLK config (#10963) * fix(i2s): Add missing initializer for I2S CLK config * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- libraries/ESP_I2S/src/ESP_I2S.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/ESP_I2S/src/ESP_I2S.cpp b/libraries/ESP_I2S/src/ESP_I2S.cpp index d0ceb0c4b4c..6bf8089e48a 100644 --- a/libraries/ESP_I2S/src/ESP_I2S.cpp +++ b/libraries/ESP_I2S/src/ESP_I2S.cpp @@ -11,6 +11,12 @@ #include "mp3dec.h" #endif +#if SOC_I2S_HW_VERSION_2 +#undef I2S_STD_CLK_DEFAULT_CONFIG +#define I2S_STD_CLK_DEFAULT_CONFIG(rate) \ + { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, } +#endif + #define I2S_READ_CHUNK_SIZE 1920 #define I2S_DEFAULT_CFG() \ From 7b651b64d61ccd513480305f60fae877b26f764f Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 13 Feb 2025 12:51:28 +0200 Subject: [PATCH 25/33] feat(cdc): Add support for two CDC ports at once (#10962) * feat(cdc): Add support for two CDC ports at once * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- cores/esp32/USBCDC.cpp | 68 ++++++++++++++++++++++++--------- cores/esp32/esp32-hal-tinyusb.c | 24 ++++++------ cores/esp32/esp32-hal-tinyusb.h | 8 ++++ 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/cores/esp32/USBCDC.cpp b/cores/esp32/USBCDC.cpp index 795a17dc0b8..945021a79e2 100644 --- a/cores/esp32/USBCDC.cpp +++ b/cores/esp32/USBCDC.cpp @@ -25,8 +25,7 @@ ESP_EVENT_DEFINE_BASE(ARDUINO_USB_CDC_EVENTS); esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait); esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg); -#define MAX_USB_CDC_DEVICES 2 -USBCDC *devices[MAX_USB_CDC_DEVICES] = {NULL, NULL}; +USBCDC *devices[CFG_TUD_CDC]; static uint16_t load_cdc_descriptor(uint8_t *dst, uint8_t *itf) { uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB CDC"); @@ -38,23 +37,43 @@ static uint16_t load_cdc_descriptor(uint8_t *dst, uint8_t *itf) { return TUD_CDC_DESC_LEN; } +static uint16_t load_cdc_descriptor2(uint8_t *dst, uint8_t *itf) { + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB CDC2"); + uint8_t ep_ntfy = tinyusb_get_free_in_endpoint(); + TU_VERIFY(ep_ntfy != 0); + uint8_t ep_in = tinyusb_get_free_in_endpoint(); + TU_VERIFY(ep_in != 0); + uint8_t ep_out = tinyusb_get_free_out_endpoint(); + TU_VERIFY(ep_out != 0); + uint8_t descriptor[TUD_CDC_DESC_LEN] = { + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(*itf, str_index, (uint8_t)(0x80 | ep_ntfy), CFG_TUD_ENDOINT_SIZE, ep_out, (uint8_t)(0x80 | ep_in), CFG_TUD_ENDOINT_SIZE) + }; + *itf += 2; + memcpy(dst, descriptor, TUD_CDC_DESC_LEN); + return TUD_CDC_DESC_LEN; +} + // Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { - if (itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL) { + //log_v("ITF: %u, DTR: %u, RTS: %u", itf, dtr, rts); + if (itf < CFG_TUD_CDC && devices[itf] != NULL) { devices[itf]->_onLineState(dtr, rts); } } // Invoked when line coding is change via SET_LINE_CODING void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding) { - if (itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL) { + //log_v("ITF: %u, BITRATE: %lu, STOP_BITS: %u, PARITY: %u, DATA_BITS: %u", itf, p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits); + if (itf < CFG_TUD_CDC && devices[itf] != NULL) { devices[itf]->_onLineCoding(p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits); } } // Invoked when received new data void tud_cdc_rx_cb(uint8_t itf) { - if (itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL) { + //log_v("ITF: %u", itf); + if (itf < CFG_TUD_CDC && devices[itf] != NULL) { devices[itf]->_onRX(); } } @@ -66,13 +85,13 @@ void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) { // Invoked when space becomes available in TX buffer void tud_cdc_tx_complete_cb(uint8_t itf) { - if (itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL) { + if (itf < CFG_TUD_CDC && devices[itf] != NULL) { devices[itf]->_onTX(); } } static void ARDUINO_ISR_ATTR cdc0_write_char(char c) { - if (devices[0] != NULL) { + if (CFG_TUD_CDC && devices[0] != NULL) { tud_cdc_n_write_char(0, c); } } @@ -84,9 +103,15 @@ static void usb_unplugged_cb(void *arg, esp_event_base_t event_base, int32_t eve USBCDC::USBCDC(uint8_t itfn) : itf(itfn), bit_rate(0), stop_bits(0), parity(0), data_bits(0), dtr(false), rts(false), connected(false), reboot_enable(true), rx_queue(NULL), tx_lock(NULL), tx_timeout_ms(250) { - tinyusb_enable_interface(USB_INTERFACE_CDC, TUD_CDC_DESC_LEN, load_cdc_descriptor); - if (itf < MAX_USB_CDC_DEVICES) { + if (itf < CFG_TUD_CDC) { + if (itf == 0) { + tinyusb_enable_interface(USB_INTERFACE_CDC, TUD_CDC_DESC_LEN, load_cdc_descriptor); + } else { + tinyusb_enable_interface(USB_INTERFACE_CDC2, TUD_CDC_DESC_LEN, load_cdc_descriptor2); + } arduino_usb_event_handler_register_with(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, usb_unplugged_cb, this); + } else { + log_e("Maximum of %u CDC devices are supported", CFG_TUD_CDC); } } @@ -142,6 +167,9 @@ size_t USBCDC::setRxBufferSize(size_t rx_queue_len) { } void USBCDC::begin(unsigned long baud) { + if (itf >= CFG_TUD_CDC) { + return; + } if (tx_lock == NULL) { tx_lock = xSemaphoreCreateMutex(); } @@ -153,6 +181,9 @@ void USBCDC::begin(unsigned long baud) { } void USBCDC::end() { + if (itf >= CFG_TUD_CDC) { + return; + } connected = false; devices[itf] = NULL; setRxBufferSize(0); @@ -298,14 +329,14 @@ bool USBCDC::rebootEnabled(void) { } int USBCDC::available(void) { - if (itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL) { + if (itf >= CFG_TUD_CDC || rx_queue == NULL) { return -1; } return uxQueueMessagesWaiting(rx_queue); } int USBCDC::peek(void) { - if (itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL) { + if (itf >= CFG_TUD_CDC || rx_queue == NULL) { return -1; } uint8_t c; @@ -316,7 +347,7 @@ int USBCDC::peek(void) { } int USBCDC::read(void) { - if (itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL) { + if (itf >= CFG_TUD_CDC || rx_queue == NULL) { return -1; } uint8_t c = 0; @@ -327,7 +358,7 @@ int USBCDC::read(void) { } size_t USBCDC::read(uint8_t *buffer, size_t size) { - if (itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL) { + if (itf >= CFG_TUD_CDC || rx_queue == NULL) { return -1; } uint8_t c = 0; @@ -339,7 +370,7 @@ size_t USBCDC::read(uint8_t *buffer, size_t size) { } void USBCDC::flush(void) { - if (itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || !tud_cdc_n_connected(itf)) { + if (itf >= CFG_TUD_CDC || tx_lock == NULL || !tud_cdc_n_connected(itf)) { return; } if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) { @@ -350,7 +381,7 @@ void USBCDC::flush(void) { } int USBCDC::availableForWrite(void) { - if (itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || !tud_cdc_n_connected(itf)) { + if (itf >= CFG_TUD_CDC || tx_lock == NULL || !tud_cdc_n_connected(itf)) { return 0; } if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) { @@ -362,7 +393,7 @@ int USBCDC::availableForWrite(void) { } size_t USBCDC::write(const uint8_t *buffer, size_t size) { - if (itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || buffer == NULL || size == 0 || !tud_cdc_n_connected(itf)) { + if (itf >= CFG_TUD_CDC || tx_lock == NULL || buffer == NULL || size == 0 || !tud_cdc_n_connected(itf)) { return 0; } if (xPortInIsrContext()) { @@ -415,6 +446,9 @@ uint32_t USBCDC::baudRate() { } void USBCDC::setDebugOutput(bool en) { + if (itf) { + return; + } if (en) { uartSetDebug(NULL); ets_install_putc2((void (*)(char)) & cdc0_write_char); @@ -424,7 +458,7 @@ void USBCDC::setDebugOutput(bool en) { } USBCDC::operator bool() const { - if (itf >= MAX_USB_CDC_DEVICES) { + if (itf >= CFG_TUD_CDC) { return false; } return connected; diff --git a/cores/esp32/esp32-hal-tinyusb.c b/cores/esp32/esp32-hal-tinyusb.c index f83e8b61bd2..0991e08d27f 100644 --- a/cores/esp32/esp32-hal-tinyusb.c +++ b/cores/esp32/esp32-hal-tinyusb.c @@ -616,7 +616,7 @@ void usb_persist_restart(restart_type_t mode) { } static bool tinyusb_reserve_in_endpoint(uint8_t endpoint) { - if (endpoint > 6 || (tinyusb_endpoints.in & BIT(endpoint)) != 0) { + if (endpoint > CFG_TUD_NUM_EPS || (tinyusb_endpoints.in & BIT(endpoint)) != 0) { return false; } tinyusb_endpoints.in |= BIT(endpoint); @@ -624,7 +624,7 @@ static bool tinyusb_reserve_in_endpoint(uint8_t endpoint) { } static bool tinyusb_reserve_out_endpoint(uint8_t endpoint) { - if (endpoint > 6 || (tinyusb_endpoints.out & BIT(endpoint)) != 0) { + if (endpoint > CFG_TUD_NUM_EPS || (tinyusb_endpoints.out & BIT(endpoint)) != 0) { return false; } tinyusb_endpoints.out |= BIT(endpoint); @@ -632,11 +632,13 @@ static bool tinyusb_reserve_out_endpoint(uint8_t endpoint) { } static bool tinyusb_has_available_fifos(void) { - uint8_t max_endpoints = 4, active_endpoints = 0; + uint8_t max_endpoints = CFG_TUD_NUM_IN_EPS - 1, active_endpoints = 0; +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 if (tinyusb_loaded_interfaces_mask & BIT(USB_INTERFACE_CDC)) { - max_endpoints = 5; //CDC endpoint 0x85 is actually not linked to FIFO and not used + max_endpoints = CFG_TUD_NUM_IN_EPS; //CDC endpoint 0x85 is actually not linked to FIFO and not used } - for (uint8_t i = 1; i < 7; i++) { +#endif + for (uint8_t i = 1; i <= CFG_TUD_NUM_EPS; i++) { if ((tinyusb_endpoints.in & BIT(i)) != 0) { active_endpoints++; } @@ -771,7 +773,7 @@ static void usb_device_task(void *param) { * PUBLIC API * */ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR -const char *tinyusb_interface_names[USB_INTERFACE_MAX] = {"MSC", "DFU", "HID", "VENDOR", "CDC", "MIDI", "CUSTOM"}; +const char *tinyusb_interface_names[USB_INTERFACE_MAX] = {"MSC", "DFU", "HID", "VENDOR", "CDC", "CDC2", "MIDI", "CUSTOM"}; #endif static bool tinyusb_is_initialized = false; @@ -862,7 +864,7 @@ uint8_t tinyusb_get_free_duplex_endpoint(void) { log_e("No available IN endpoints"); return 0; } - for (uint8_t i = 1; i < 7; i++) { + for (uint8_t i = 1; i <= CFG_TUD_NUM_IN_EPS; i++) { if ((tinyusb_endpoints.in & BIT(i)) == 0 && (tinyusb_endpoints.out & BIT(i)) == 0) { tinyusb_endpoints.in |= BIT(i); tinyusb_endpoints.out |= BIT(i); @@ -878,13 +880,13 @@ uint8_t tinyusb_get_free_in_endpoint(void) { log_e("No available IN endpoints"); return 0; } - for (uint8_t i = 1; i < 7; i++) { + for (uint8_t i = 1; i <= CFG_TUD_NUM_IN_EPS; i++) { if ((tinyusb_endpoints.in & BIT(i)) == 0 && (tinyusb_endpoints.out & BIT(i)) != 0) { tinyusb_endpoints.in |= BIT(i); return i; } } - for (uint8_t i = 1; i < 7; i++) { + for (uint8_t i = 1; i <= CFG_TUD_NUM_IN_EPS; i++) { if ((tinyusb_endpoints.in & BIT(i)) == 0) { tinyusb_endpoints.in |= BIT(i); return i; @@ -894,13 +896,13 @@ uint8_t tinyusb_get_free_in_endpoint(void) { } uint8_t tinyusb_get_free_out_endpoint(void) { - for (uint8_t i = 1; i < 7; i++) { + for (uint8_t i = 1; i <= CFG_TUD_NUM_EPS; i++) { if ((tinyusb_endpoints.out & BIT(i)) == 0 && (tinyusb_endpoints.in & BIT(i)) != 0) { tinyusb_endpoints.out |= BIT(i); return i; } } - for (uint8_t i = 1; i < 7; i++) { + for (uint8_t i = 1; i <= CFG_TUD_NUM_EPS; i++) { if ((tinyusb_endpoints.out & BIT(i)) == 0) { tinyusb_endpoints.out |= BIT(i); return i; diff --git a/cores/esp32/esp32-hal-tinyusb.h b/cores/esp32/esp32-hal-tinyusb.h index 0b42760e69f..73210c4872b 100644 --- a/cores/esp32/esp32-hal-tinyusb.h +++ b/cores/esp32/esp32-hal-tinyusb.h @@ -38,6 +38,13 @@ extern "C" { #define CFG_TUD_ENDOINT_SIZE 64 #endif #endif +#if CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUD_NUM_EPS 15 +#define CFG_TUD_NUM_IN_EPS 8 +#else +#define CFG_TUD_NUM_EPS 6 +#define CFG_TUD_NUM_IN_EPS 5 +#endif typedef struct { uint16_t vid; @@ -88,6 +95,7 @@ typedef enum { USB_INTERFACE_HID, USB_INTERFACE_VENDOR, USB_INTERFACE_CDC, + USB_INTERFACE_CDC2, USB_INTERFACE_MIDI, USB_INTERFACE_CUSTOM, USB_INTERFACE_MAX From 6c3a49cac7815a160fe70da07bac66e56b6ef547 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:01:13 +0200 Subject: [PATCH 26/33] build(deps): bump cryptography from 43.0.1 to 44.0.1 in /tests (#10961) Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.1 to 44.0.1. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/43.0.1...44.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index cef0bf17881..c98c032a3ae 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,4 @@ -cryptography==43.0.1 +cryptography==44.0.1 --only-binary cryptography pytest-cov==5.0.0 pytest-embedded-serial-esp==1.12.0 From 83abca1604d6118269f04228f57e93f493241e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:02:35 +0100 Subject: [PATCH 27/33] feat(zigbee): Add OTA client cluster support (#10946) * feat(zigbee): Add OTA client cluster support * feat(zigbee): Add conditions to reject OTA upgrade * feat(zigbee): Add newest version of OTA handler * fix(zigbee): Fix errors and warnings, swap parameters order * feat(zigbee): Add simple OTA Client example * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../examples/Zigbee_OTA_Client/README.md | 68 ++++++ .../Zigbee_OTA_Client/Zigbee_OTA_Client.ino | 112 ++++++++++ .../Zigbee/examples/Zigbee_OTA_Client/ci.json | 6 + libraries/Zigbee/src/ZigbeeEP.cpp | 68 ++++++ libraries/Zigbee/src/ZigbeeEP.h | 23 +- libraries/Zigbee/src/ZigbeeHandlers.cpp | 205 ++++++++++++++++-- 6 files changed, 465 insertions(+), 17 deletions(-) create mode 100644 libraries/Zigbee/examples/Zigbee_OTA_Client/README.md create mode 100644 libraries/Zigbee/examples/Zigbee_OTA_Client/Zigbee_OTA_Client.ino create mode 100644 libraries/Zigbee/examples/Zigbee_OTA_Client/ci.json diff --git a/libraries/Zigbee/examples/Zigbee_OTA_Client/README.md b/libraries/Zigbee/examples/Zigbee_OTA_Client/README.md new file mode 100644 index 00000000000..143ff946f28 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_OTA_Client/README.md @@ -0,0 +1,68 @@ +# Arduino-ESP32 Zigbee OTA Client + on/off light Example + +This example shows how to configure the Zigbee end device with OTA Client and use it as a Home Automation (HA) on/off light. + +# Supported Targets + +Currently, this example supports the following targets. + +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +## Hardware Required + +* A USB cable for power supply and programming + +### Configure the Project + +Set the LED GPIO by changing the `LED_PIN` definition. By default, the LED_PIN is `RGB_BUILTIN`. +By default, the `rgbLedWrite` function is used to control the LED. You can change it to digitalWrite to control a simple LED. + +#### Using Arduino IDE + +To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits). + +* Before Compile/Verify, select the correct board: `Tools -> Board`. +* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ED (end device)` +* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs` +* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port. +* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`. + +## Troubleshooting + +If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. It is recommended to do this if you re-flash the coordinator. +You can do the following: + +* In the Arduino IDE go to the Tools menu and set `Erase All Flash Before Sketch Upload` to `Enabled`. +* Add to the sketch `Zigbee.factoryReset();` to reset the device and Zigbee stack. + +By default, the coordinator network is closed after rebooting or flashing new firmware. +To open the network you have 2 options: + +* Open network after reboot by setting `Zigbee.setRebootOpenNetwork(time);` before calling `Zigbee.begin();`. +* In application you can anytime call `Zigbee.openNetwork(time);` to open the network for devices to join. + + +***Important: Make sure you are using a good quality USB cable and that you have a reliable power source*** + +* **LED not blinking:** Check the wiring connection and the IO selection. +* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed. +* **COM port not detected:** Check the USB cable and the USB to Serial driver installation. + +If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute). + +## Contribute + +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) + +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! + +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. + +## Resources + +* Official ESP32 Forum: [Link](https://esp32.com) +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) +* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf) +* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf) +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) diff --git a/libraries/Zigbee/examples/Zigbee_OTA_Client/Zigbee_OTA_Client.ino b/libraries/Zigbee/examples/Zigbee_OTA_Client/Zigbee_OTA_Client.ino new file mode 100644 index 00000000000..29d114014b4 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_OTA_Client/Zigbee_OTA_Client.ino @@ -0,0 +1,112 @@ +// 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 OTA support on light bulb. + * + * The example demonstrates how to use Zigbee library to create a end device light bulb with OTA support. + * The light bulb 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 end device mode is not selected in Tools->Zigbee mode" +#endif + +#include "Zigbee.h" + +/* Zigbee light bulb configuration */ +#define ZIGBEE_LIGHT_ENDPOINT 1 +uint8_t led = RGB_BUILTIN; +uint8_t button = BOOT_PIN; + +/* Zigbee OTA configuration */ +#define OTA_UPGRADE_RUNNING_FILE_VERSION 0x01010100 // Increment this value when the running image is updated +#define OTA_UPGRADE_DOWNLOADED_FILE_VERSION 0x01010101 // Increment this value when the downloaded image is updated +#define OTA_UPGRADE_HW_VERSION 0x0101 // The hardware version, this can be used to differentiate between different hardware versions + +ZigbeeLight zbLight = ZigbeeLight(ZIGBEE_LIGHT_ENDPOINT); + +/********************* RGB LED functions **************************/ +void setLED(bool value) { + digitalWrite(led, value); +} + +/********************* Arduino functions **************************/ +void setup() { + Serial.begin(115200); + + // Init LED and turn it OFF (if LED_PIN == RGB_BUILTIN, the rgbLedWrite() will be used under the hood) + pinMode(led, OUTPUT); + digitalWrite(led, LOW); + + // Init button for factory reset + pinMode(button, INPUT_PULLUP); + + // Optional: set Zigbee device name and model + zbLight.setManufacturerAndModel("Espressif", "ZBLightBulb"); + + // Set callback function for light change + zbLight.onLightChange(setLED); + + // Add OTA client to the light bulb + zbLight.addOTAClient(OTA_UPGRADE_RUNNING_FILE_VERSION, OTA_UPGRADE_DOWNLOADED_FILE_VERSION, OTA_UPGRADE_HW_VERSION); + + // Add endpoint to Zigbee Core + Serial.println("Adding ZigbeeLight endpoint to Zigbee Core"); + Zigbee.addEndpoint(&zbLight); + + // When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE + if (!Zigbee.begin()) { + Serial.println("Zigbee failed to start!"); + Serial.println("Rebooting..."); + ESP.restart(); + } + Serial.println("Connecting to network"); + while (!Zigbee.connected()) { + Serial.print("."); + delay(100); + } + Serial.println(); + + // Start Zigbee OTA client query, first request is within a minute and the next requests are sent every hour automatically + zbLight.requestOTAUpdate(); +} + +void loop() { + // Checking button for factory reset + if (digitalRead(button) == LOW) { // Push button pressed + // Key debounce handling + delay(100); + int startTime = millis(); + while (digitalRead(button) == LOW) { + delay(50); + if ((millis() - startTime) > 3000) { + // If key pressed for more than 3secs, factory reset Zigbee and reboot + Serial.println("Resetting Zigbee to factory and rebooting in 1s."); + delay(1000); + Zigbee.factoryReset(); + } + } + // Toggle light by pressing the button + zbLight.setLight(!zbLight.getLightState()); + } + delay(100); +} diff --git a/libraries/Zigbee/examples/Zigbee_OTA_Client/ci.json b/libraries/Zigbee/examples/Zigbee_OTA_Client/ci.json new file mode 100644 index 00000000000..7b7ccef8ed7 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_OTA_Client/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed", + "requires": [ + "CONFIG_SOC_IEEE802154_SUPPORTED=y" + ] +} diff --git a/libraries/Zigbee/src/ZigbeeEP.cpp b/libraries/Zigbee/src/ZigbeeEP.cpp index 6a2ace0b90c..f3f890393eb 100644 --- a/libraries/Zigbee/src/ZigbeeEP.cpp +++ b/libraries/Zigbee/src/ZigbeeEP.cpp @@ -363,4 +363,72 @@ void ZigbeeEP::zbReadTimeCluster(const esp_zb_zcl_attribute_t *attribute) { } } +// typedef struct esp_zb_ota_cluster_cfg_s { +// uint32_t ota_upgrade_file_version; /*!< The attribute indicates the file version of the running firmware image on the device */ +// uint16_t ota_upgrade_manufacturer; /*!< The attribute indicates the value for the manufacturer of the device */ +// uint16_t ota_upgrade_image_type; /*!< The attribute indicates the the image type of the file that the client is currently downloading */ +// uint32_t ota_upgrade_downloaded_file_ver; /*!< The attribute indicates the file version of the downloaded image on the device*/ +// esp_zb_ota_cluster_cfg_t; + +// typedef struct esp_zb_zcl_ota_upgrade_client_variable_s { +// uint16_t timer_query; /*!< The field indicates the time of querying OTA image for OTA upgrade client */ +// uint16_t hw_version; /*!< The hardware version */ +// uint8_t max_data_size; /*!< The maximum size of OTA data */ +// } esp_zb_zcl_ota_upgrade_client_variable_t; + +void ZigbeeEP::addOTAClient( + uint32_t file_version, uint32_t downloaded_file_ver, uint16_t hw_version, uint16_t manufacturer, uint16_t image_type, uint8_t max_data_size +) { + + esp_zb_ota_cluster_cfg_t ota_cluster_cfg = {}; + ota_cluster_cfg.ota_upgrade_file_version = file_version; //OTA_UPGRADE_RUNNING_FILE_VERSION; + ota_cluster_cfg.ota_upgrade_downloaded_file_ver = downloaded_file_ver; //OTA_UPGRADE_DOWNLOADED_FILE_VERSION; + ota_cluster_cfg.ota_upgrade_manufacturer = manufacturer; //OTA_UPGRADE_MANUFACTURER; + ota_cluster_cfg.ota_upgrade_image_type = image_type; //OTA_UPGRADE_IMAGE_TYPE; + + esp_zb_attribute_list_t *ota_cluster = esp_zb_ota_cluster_create(&ota_cluster_cfg); + + esp_zb_zcl_ota_upgrade_client_variable_t variable_config = {}; + variable_config.timer_query = ESP_ZB_ZCL_OTA_UPGRADE_QUERY_TIMER_COUNT_DEF; + variable_config.hw_version = hw_version; //OTA_UPGRADE_HW_VERSION; + variable_config.max_data_size = max_data_size; //OTA_UPGRADE_MAX_DATA_SIZE; + + uint16_t ota_upgrade_server_addr = 0xffff; + uint8_t ota_upgrade_server_ep = 0xff; + + ESP_ERROR_CHECK(esp_zb_ota_cluster_add_attr(ota_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_CLIENT_DATA_ID, (void *)&variable_config)); + ESP_ERROR_CHECK(esp_zb_ota_cluster_add_attr(ota_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_SERVER_ADDR_ID, (void *)&ota_upgrade_server_addr)); + ESP_ERROR_CHECK(esp_zb_ota_cluster_add_attr(ota_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_SERVER_ENDPOINT_ID, (void *)&ota_upgrade_server_ep)); + + ESP_ERROR_CHECK(esp_zb_cluster_list_add_ota_cluster(_cluster_list, ota_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE)); +} + +static void findOTAServer(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + esp_zb_ota_upgrade_client_query_interval_set(*((uint8_t *)user_ctx), OTA_UPGRADE_QUERY_INTERVAL); + esp_zb_ota_upgrade_client_query_image_req(addr, endpoint); + log_i("Query OTA upgrade from server endpoint: %d after %d seconds", endpoint, OTA_UPGRADE_QUERY_INTERVAL); + } else { + log_w("No OTA Server found"); + } +} + +void ZigbeeEP::requestOTAUpdate() { + esp_zb_zdo_match_desc_req_param_t req; + uint16_t cluster_list[] = {ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE}; + + /* Match the OTA server of coordinator */ + req.addr_of_interest = 0x0000; + req.dst_nwk_addr = 0x0000; + req.num_in_clusters = 1; + req.num_out_clusters = 0; + req.profile_id = ESP_ZB_AF_HA_PROFILE_ID; + req.cluster_list = cluster_list; + esp_zb_lock_acquire(portMAX_DELAY); + if (esp_zb_bdb_dev_joined()) { + esp_zb_zdo_match_cluster(&req, findOTAServer, &_endpoint); + } + esp_zb_lock_release(); +} + #endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index 584873f50dd..72cde275293 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -8,7 +8,8 @@ #include /* Useful defines */ -#define ZB_CMD_TIMEOUT 10000 // 10 seconds +#define ZB_CMD_TIMEOUT 10000 // 10 seconds +#define OTA_UPGRADE_QUERY_INTERVAL (1 * 60) // 1 hour = 60 minutes #define ZB_ARRAY_LENTH(arr) (sizeof(arr) / sizeof(arr[0])) #define XYZ_TO_RGB(X, Y, Z, r, g, b) \ @@ -107,6 +108,26 @@ class ZigbeeEP { return _allow_multiple_binding; } + // OTA methods + /** + * @brief Add OTA client to the Zigbee endpoint. + * + * @param file_version The current file version of the OTA client. + * @param downloaded_file_ver The version of the downloaded file. + * @param hw_version The hardware version of the device. + * @param manufacturer The manufacturer code (default: 0x1001). + * @param image_type The image type code (default: 0x1011). + * @param max_data_size The maximum data size for OTA transfer (default and recommended: 223). + */ + void addOTAClient( + uint32_t file_version, uint32_t downloaded_file_ver, uint16_t hw_version, uint16_t manufacturer = 0x1001, uint16_t image_type = 0x1011, + uint8_t max_data_size = 223 + ); + /** + * @brief Request OTA update from the server, first request is within a minute and the next requests are sent every hour automatically. + */ + void requestOTAUpdate(); + // findEndpoind may be implemented by EPs to find and bind devices virtual void findEndpoint(esp_zb_zdo_match_desc_req_param_t *cmd_req) {}; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 575b25ef404..3af1b6c52aa 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -4,6 +4,26 @@ #if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED +#include "esp_ota_ops.h" +#if CONFIG_ZB_DELTA_OTA // Delta OTA, code is prepared for this feature but not enabled by default +#include "esp_delta_ota_ops.h" +#endif + +//OTA Upgrade defines and variables +#define OTA_ELEMENT_HEADER_LEN 6 /* OTA element format header size include tag identifier and length field */ + +/** + * @name Enumeration for the tag identifier denotes the type and format of the data within the element + * @anchor esp_ota_element_tag_id_t + */ +typedef enum esp_ota_element_tag_id_e { + UPGRADE_IMAGE = 0x0000, /*!< Upgrade image */ +} esp_ota_element_tag_id_t; + +static const esp_partition_t *s_ota_partition = NULL; +static esp_ota_handle_t s_ota_handle = 0; +static bool s_tagid_received = false; + // 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); @@ -13,6 +33,8 @@ static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message); static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message); static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_message_t *message); +static esp_err_t zb_ota_upgrade_status_handler(const esp_zb_zcl_ota_upgrade_value_message_t *message); +static esp_err_t zb_ota_upgrade_query_image_resp_handler(const esp_zb_zcl_ota_upgrade_query_image_resp_message_t *message); // Zigbee action handlers [[maybe_unused]] @@ -32,6 +54,10 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, case ESP_ZB_CORE_WINDOW_COVERING_MOVEMENT_CB_ID: ret = zb_window_covering_movement_resp_handler((esp_zb_zcl_window_covering_movement_message_t *)message); break; + case ESP_ZB_CORE_OTA_UPGRADE_VALUE_CB_ID: ret = zb_ota_upgrade_status_handler((esp_zb_zcl_ota_upgrade_value_message_t *)message); break; + case ESP_ZB_CORE_OTA_UPGRADE_QUERY_IMAGE_RESP_CB_ID: + ret = zb_ota_upgrade_query_image_resp_handler((esp_zb_zcl_ota_upgrade_query_image_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; } @@ -186,22 +212,6 @@ static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zo 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"); - return ESP_FAIL; - } - if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { - log_e("Received message: error status(%d)", message->info.status); - return ESP_ERR_INVALID_ARG; - } - 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; -} - static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_message_t *message) { if (!message) { log_e("Empty message"); @@ -224,4 +234,167 @@ static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_windo return ESP_OK; } +static esp_err_t esp_element_ota_data(uint32_t total_size, const void *payload, uint16_t payload_size, void **outbuf, uint16_t *outlen) { + static uint16_t tagid = 0; + void *data_buf = NULL; + uint16_t data_len; + + if (!s_tagid_received) { + uint32_t length = 0; + if (!payload || payload_size <= OTA_ELEMENT_HEADER_LEN) { + log_e("Invalid element format"); + return ESP_ERR_INVALID_ARG; + } + + const uint8_t *payload_ptr = (const uint8_t *)payload; + tagid = *(const uint16_t *)payload_ptr; + length = *(const uint32_t *)(payload_ptr + sizeof(tagid)); + if ((length + OTA_ELEMENT_HEADER_LEN) != total_size) { + log_e("Invalid element length [%ld/%ld]", length, total_size); + return ESP_ERR_INVALID_ARG; + } + + s_tagid_received = true; + + data_buf = (void *)(payload_ptr + OTA_ELEMENT_HEADER_LEN); + data_len = payload_size - OTA_ELEMENT_HEADER_LEN; + } else { + data_buf = (void *)payload; + data_len = payload_size; + } + + switch (tagid) { + case UPGRADE_IMAGE: + *outbuf = data_buf; + *outlen = data_len; + break; + default: + log_e("Unsupported element tag identifier %d", tagid); + return ESP_ERR_INVALID_ARG; + break; + } + + return ESP_OK; +} + +static esp_err_t zb_ota_upgrade_status_handler(const esp_zb_zcl_ota_upgrade_value_message_t *message) { + static uint32_t total_size = 0; + static uint32_t offset = 0; + [[maybe_unused]] + static int64_t start_time = 0; + esp_err_t ret = ESP_OK; + + if (message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS) { + switch (message->upgrade_status) { + case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_START: + log_i("Zigbee - OTA upgrade start"); + start_time = esp_timer_get_time(); + s_ota_partition = esp_ota_get_next_update_partition(NULL); + assert(s_ota_partition); +#if CONFIG_ZB_DELTA_OTA + ret = esp_delta_ota_begin(s_ota_partition, 0, &s_ota_handle); +#else + ret = esp_ota_begin(s_ota_partition, 0, &s_ota_handle); +#endif + if (ret != ESP_OK) { + log_e("Zigbee - Failed to begin OTA partition, status: %s", esp_err_to_name(ret)); + return ret; + } + break; + case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE: + total_size = message->ota_header.image_size; + offset += message->payload_size; + log_i("Zigbee - OTA Client receives data: progress [%ld/%ld]", offset, total_size); + if (message->payload_size && message->payload) { + uint16_t payload_size = 0; + void *payload = NULL; + ret = esp_element_ota_data(total_size, message->payload, message->payload_size, &payload, &payload_size); + if (ret != ESP_OK) { + log_e("Zigbee - Failed to element OTA data, status: %s", esp_err_to_name(ret)); + return ret; + } +#if CONFIG_ZB_DELTA_OTA + ret = esp_delta_ota_write(s_ota_handle, payload, payload_size); +#else + ret = esp_ota_write(s_ota_handle, (const void *)payload, payload_size); +#endif + if (ret != ESP_OK) { + log_e("Zigbee - Failed to write OTA data to partition, status: %s", esp_err_to_name(ret)); + return ret; + } + } + break; + case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_APPLY: log_i("Zigbee - OTA upgrade apply"); break; + case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_CHECK: + ret = offset == total_size ? ESP_OK : ESP_FAIL; + offset = 0; + total_size = 0; + s_tagid_received = false; + log_i("Zigbee - OTA upgrade check status: %s", esp_err_to_name(ret)); + break; + case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_FINISH: + log_i("Zigbee - OTA Finish"); + log_i( + "Zigbee - OTA Information: version: 0x%lx, manufacturer code: 0x%x, image type: 0x%x, total size: %ld bytes, cost time: %lld ms,", + message->ota_header.file_version, message->ota_header.manufacturer_code, message->ota_header.image_type, message->ota_header.image_size, + (esp_timer_get_time() - start_time) / 1000 + ); +#if CONFIG_ZB_DELTA_OTA + ret = esp_delta_ota_end(s_ota_handle); +#else + ret = esp_ota_end(s_ota_handle); +#endif + if (ret != ESP_OK) { + log_e("Zigbee - Failed to end OTA partition, status: %s", esp_err_to_name(ret)); + return ret; + } + ret = esp_ota_set_boot_partition(s_ota_partition); + if (ret != ESP_OK) { + log_e("Zigbee - Failed to set OTA boot partition, status: %s", esp_err_to_name(ret)); + return ret; + } + log_w("Zigbee - Prepare to restart system"); + esp_restart(); + break; + default: log_i("Zigbee - OTA status: %d", message->upgrade_status); break; + } + } + return ret; +} + +static esp_err_t zb_ota_upgrade_query_image_resp_handler(const esp_zb_zcl_ota_upgrade_query_image_resp_message_t *message) { + if (message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS) { + log_i("Zigbee - Queried OTA image from address: 0x%04hx, endpoint: %d", message->server_addr.u.short_addr, message->server_endpoint); + log_i("Zigbee - Image version: 0x%lx, manufacturer code: 0x%x, image size: %ld", message->file_version, message->manufacturer_code, message->image_size); + if (message->image_size == 0) { + log_i("Zigbee - Rejecting OTA image upgrade, image size is 0"); + return ESP_FAIL; + } + if (message->file_version == 0) { + log_i("Zigbee - Rejecting OTA image upgrade, file version is 0"); + return ESP_FAIL; + } + log_i("Zigbee - Approving OTA image upgrade"); + } else { + log_i("Zigbee - OTA image upgrade response status: 0x%x", message->info.status); + } + 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"); + return ESP_FAIL; + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; + } + 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 && CONFIG_ZB_ENABLED From 7f8c77f9ee7cfac433ef21304c7abf01cf2b86a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:03:02 +0100 Subject: [PATCH 28/33] feat(zigbee): Add vibration sensor endpoint (#10944) --- CMakeLists.txt | 1 + .../Zigbee_Vibration_Sensor/README.md | 58 ++++++++++ .../Zigbee_Vibration_Sensor.ino | 104 ++++++++++++++++++ .../examples/Zigbee_Vibration_Sensor/ci.json | 6 + libraries/Zigbee/src/Zigbee.h | 1 + .../Zigbee/src/ep/ZigbeeVibrationSensor.cpp | 86 +++++++++++++++ .../Zigbee/src/ep/ZigbeeVibrationSensor.h | 64 +++++++++++ 7 files changed, 320 insertions(+) create mode 100644 libraries/Zigbee/examples/Zigbee_Vibration_Sensor/README.md create mode 100644 libraries/Zigbee/examples/Zigbee_Vibration_Sensor/Zigbee_Vibration_Sensor.ino create mode 100644 libraries/Zigbee/examples/Zigbee_Vibration_Sensor/ci.json create mode 100644 libraries/Zigbee/src/ep/ZigbeeVibrationSensor.cpp create mode 100644 libraries/Zigbee/src/ep/ZigbeeVibrationSensor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 40567d1b45c..5ee4ce664ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,7 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS libraries/Zigbee/src/ep/ZigbeeContactSwitch.cpp libraries/Zigbee/src/ep/ZigbeeDoorWindowHandle.cpp libraries/Zigbee/src/ep/ZigbeeWindowCovering.cpp + libraries/Zigbee/src/ep/ZigbeeVibrationSensor.cpp ) set(ARDUINO_LIBRARY_BLE_SRCS diff --git a/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/README.md b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/README.md new file mode 100644 index 00000000000..b0e5b5f09e8 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/README.md @@ -0,0 +1,58 @@ +# Arduino-ESP32 Zigbee Vibration Sensor Example + +This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) vibration sensor (IAS Zone), +that can be used for example as a security device which is sensing a vibrations. + +# Supported Targets + +Currently, this example supports the following targets. + +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +## Hardware Required + +* A USB cable for power supply and programming + +### Configure the Project + +Set the Button GPIO by changing the `button` variable. By default, it's the pin `BOOT_PIN` (BOOT button on ESP32-C6 and ESP32-H2). +Set the Sensor GPIO by changing the `sensor_pin` variable. + +#### Using Arduino IDE + +To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits). + +* Before Compile/Verify, select the correct board: `Tools -> Board`. +* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ED (end device)` +* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee 4MB with spiffs` +* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port. +* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`. + +## Troubleshooting + +If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. + +***Important: Make sure you are using a good quality USB cable and that you have a reliable power source*** + +* **LED not blinking:** Check the wiring connection and the IO selection. +* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed. +* **COM port not detected:** Check the USB cable and the USB to Serial driver installation. + +If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute). + +## Contribute + +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) + +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! + +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. + +## Resources + +* Official ESP32 Forum: [Link](https://esp32.com) +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) +* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf) +* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf) +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) diff --git a/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/Zigbee_Vibration_Sensor.ino b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/Zigbee_Vibration_Sensor.ino new file mode 100644 index 00000000000..d9ac7b6e241 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/Zigbee_Vibration_Sensor.ino @@ -0,0 +1,104 @@ +// 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 vibration sensor (IAS Zone). + * + * The example demonstrates how to use Zigbee library to create a end device vibration sensor. + * The vibration sensor is a Zigbee end device, which is reporting data to the Zigbee network. + * + * 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 end device mode is not selected in Tools->Zigbee mode" +#endif + +#include "Zigbee.h" + +/* Zigbee vibration sensor configuration */ +#define VIBRATION_SENSOR_ENDPOINT_NUMBER 10 +uint8_t button = BOOT_PIN; +uint8_t sensor_pin = 4; + +ZigbeeVibrationSensor zbVibrationSensor = ZigbeeVibrationSensor(VIBRATION_SENSOR_ENDPOINT_NUMBER); + +void setup() { + Serial.begin(115200); + + // Init button + sensor + pinMode(button, INPUT_PULLUP); + pinMode(sensor_pin, INPUT); + + // Optional: set Zigbee device name and model + zbVibrationSensor.setManufacturerAndModel("Espressif", "ZigbeeVibrationSensor"); + + // Add endpoint to Zigbee Core + Zigbee.addEndpoint(&zbVibrationSensor); + + Serial.println("Starting Zigbee..."); + // When all EPs are registered, start Zigbee in End Device mode + if (!Zigbee.begin()) { + Serial.println("Zigbee failed to start!"); + Serial.println("Rebooting..."); + ESP.restart(); + } else { + Serial.println("Zigbee started successfully!"); + } + Serial.println("Connecting to network"); + while (!Zigbee.connected()) { + Serial.print("."); + delay(100); + } + Serial.println(); +} + +void loop() { + // Checking pin for contact change + static bool sensed = false; + if (digitalRead(sensor_pin) == HIGH && !sensed) { + // Update contact sensor value + zbVibrationSensor.setVibration(true); + sensed = true; + //if sensed, wait 2 seconds before next sensing + delay(2000); + } else if (digitalRead(sensor_pin) == LOW && sensed) { + zbVibrationSensor.setVibration(false); + sensed = false; + //if not sensed, wait 0,5 seconds before next sensing + delay(500); + } + + // Checking button for factory reset + if (digitalRead(button) == LOW) { // Push button pressed + // Key debounce handling + delay(100); + int startTime = millis(); + while (digitalRead(button) == LOW) { + delay(50); + if ((millis() - startTime) > 3000) { + // If key pressed for more than 3secs, factory reset Zigbee and reboot + Serial.println("Resetting Zigbee to factory and rebooting in 1s."); + delay(1000); + Zigbee.factoryReset(); + } + } + } + delay(100); +} diff --git a/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/ci.json b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/ci.json new file mode 100644 index 00000000000..7b7ccef8ed7 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Vibration_Sensor/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed", + "requires": [ + "CONFIG_SOC_IEEE802154_SUPPORTED=y" + ] +} diff --git a/libraries/Zigbee/src/Zigbee.h b/libraries/Zigbee/src/Zigbee.h index 49bfa44d5c1..b785c28d0df 100644 --- a/libraries/Zigbee/src/Zigbee.h +++ b/libraries/Zigbee/src/Zigbee.h @@ -21,3 +21,4 @@ #include "ep/ZigbeeContactSwitch.h" #include "ep/ZigbeeDoorWindowHandle.h" #include "ep/ZigbeeWindowCovering.h" +#include "ep/ZigbeeVibrationSensor.h" diff --git a/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.cpp b/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.cpp new file mode 100644 index 00000000000..2f2172c89df --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.cpp @@ -0,0 +1,86 @@ +#include "ZigbeeVibrationSensor.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +esp_zb_cluster_list_t *zigbee_vibration_sensor_clusters_create(zigbee_vibration_sensor_cfg_t *vibration_sensor) { + esp_zb_basic_cluster_cfg_t *basic_cfg = vibration_sensor ? &(vibration_sensor->basic_cfg) : NULL; + esp_zb_identify_cluster_cfg_t *identify_cfg = vibration_sensor ? &(vibration_sensor->identify_cfg) : NULL; + esp_zb_ias_zone_cluster_cfg_t *ias_zone_cfg = vibration_sensor ? &(vibration_sensor->ias_zone_cfg) : NULL; + esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_cluster_list_add_basic_cluster(cluster_list, esp_zb_basic_cluster_create(basic_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(identify_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, esp_zb_ias_zone_cluster_create(ias_zone_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + return cluster_list; +} + +ZigbeeVibrationSensor::ZigbeeVibrationSensor(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_IAS_ZONE_ID; + _zone_status = 0; + _zone_id = 0xff; + _ias_cie_endpoint = 1; + + //Create custom vibration sensor configuration + zigbee_vibration_sensor_cfg_t vibration_sensor_cfg = ZIGBEE_DEFAULT_VIBRATION_SENSOR_CONFIG(); + _cluster_list = zigbee_vibration_sensor_clusters_create(&vibration_sensor_cfg); + + _ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_IAS_ZONE_ID, .app_device_version = 0}; +} + +void ZigbeeVibrationSensor::setIASClientEndpoint(uint8_t ep_number) { + _ias_cie_endpoint = ep_number; +} + +void ZigbeeVibrationSensor::setVibration(bool sensed) { + log_v("Setting Vibration sensor to %s", sensed ? "sensed" : "not sensed"); + uint8_t vibration = (uint8_t)sensed; + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &vibration, false + ); + esp_zb_lock_release(); + _zone_status = vibration; + report(); +} + +void ZigbeeVibrationSensor::report() { + /* Send IAS Zone status changed notification command */ + + esp_zb_zcl_ias_zone_status_change_notif_cmd_t status_change_notif_cmd; + status_change_notif_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; + status_change_notif_cmd.zcl_basic_cmd.src_endpoint = _endpoint; + status_change_notif_cmd.zcl_basic_cmd.dst_endpoint = _ias_cie_endpoint; //default is 1 + memcpy(status_change_notif_cmd.zcl_basic_cmd.dst_addr_u.addr_long, _ias_cie_addr, sizeof(esp_zb_ieee_addr_t)); + + status_change_notif_cmd.zone_status = _zone_status; + status_change_notif_cmd.extend_status = 0; + status_change_notif_cmd.zone_id = _zone_id; + status_change_notif_cmd.delay = 0; + + esp_zb_lock_acquire(portMAX_DELAY); + esp_zb_zcl_ias_zone_status_change_notif_cmd_req(&status_change_notif_cmd); + esp_zb_lock_release(); + log_v("IAS Zone status changed notification sent"); +} + +void ZigbeeVibrationSensor::zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) { + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE) { + log_v("IAS Zone Enroll Response: zone id(%d), status(%d)", message->zone_id, message->response_code); + if (message->response_code == ESP_ZB_ZCL_IAS_ZONE_ENROLL_RESPONSE_CODE_SUCCESS) { + log_v("IAS Zone Enroll Response: success"); + esp_zb_lock_acquire(portMAX_DELAY); + memcpy( + _ias_cie_addr, + (*(esp_zb_ieee_addr_t *) + esp_zb_zcl_get_attribute(_endpoint, ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_IAS_ZONE_IAS_CIE_ADDRESS_ID) + ->data_p), + sizeof(esp_zb_ieee_addr_t) + ); + esp_zb_lock_release(); + _zone_id = message->zone_id; + } + + } else { + log_w("Received message ignored. Cluster ID: %d not supported for On/Off Light", message->info.cluster); + } +} + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.h b/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.h new file mode 100644 index 00000000000..799cc943cdb --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeVibrationSensor.h @@ -0,0 +1,64 @@ +/* Class of Zigbee contact switch (IAS Zone) endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED + +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +// clang-format off +#define ZIGBEE_DEFAULT_VIBRATION_SENSOR_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, \ + }, \ + .ias_zone_cfg = \ + { \ + .zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, \ + .zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_VIBRATION_MOVEMENT, \ + .zone_status = 0, \ + .ias_cie_addr = ESP_ZB_ZCL_ZONE_IAS_CIE_ADDR_DEFAULT, \ + .zone_id = 0xff, \ + .zone_ctx = {0, 0, 0, 0}, \ + }, \ + } +// clang-format on + +typedef struct zigbee_vibration_sensor_cfg_s { + esp_zb_basic_cluster_cfg_t basic_cfg; + esp_zb_identify_cluster_cfg_t identify_cfg; + esp_zb_ias_zone_cluster_cfg_t ias_zone_cfg; +} zigbee_vibration_sensor_cfg_t; + +class ZigbeeVibrationSensor : public ZigbeeEP { +public: + ZigbeeVibrationSensor(uint8_t endpoint); + ~ZigbeeVibrationSensor() {} + + // Set the IAS Client endpoint number (default is 1) + void setIASClientEndpoint(uint8_t ep_number); + + // Set the vibration sensor value (true = sensed, false = not sensed) + void setVibration(bool sensed); + + // Report the vibration sensor value, done automatically after setting the sensed value + void report(); + +private: + void zbIASZoneEnrollResponse(const esp_zb_zcl_ias_zone_enroll_response_message_t *message) override; + uint8_t _zone_status; + uint8_t _zone_id; + esp_zb_ieee_addr_t _ias_cie_addr; + uint8_t _ias_cie_endpoint; +}; + +#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED From 606a4049a22f33ed32d8e3242b8d7bac536e0c8c Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 13 Feb 2025 13:16:30 +0200 Subject: [PATCH 29/33] IDF release/v5.3 (#10873) * IDF release/v5.3 adf53196 * IDF release/v5.3 fb25eb02 * IDF release/v5.3 fb25eb02 * IDF release/v5.3 fb25eb02 * IDF release/v5.3 fb25eb02 * IDF release/v5.3 489d7a2b --- package/package_esp32_index.template.json | 68 +++++++++++------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/package/package_esp32_index.template.json b/package/package_esp32_index.template.json index ad031a4d155..5d73debe76f 100644 --- a/package/package_esp32_index.template.json +++ b/package/package_esp32_index.template.json @@ -42,7 +42,7 @@ { "packager": "esp32", "name": "esp32-arduino-libs", - "version": "idf-release_v5.3-cfea4f7c-v1" + "version": "idf-release_v5.3-489d7a2b-v1" }, { "packager": "esp32", @@ -95,63 +95,63 @@ "tools": [ { "name": "esp32-arduino-libs", - "version": "idf-release_v5.3-cfea4f7c-v1", + "version": "idf-release_v5.3-489d7a2b-v1", "systems": [ { "host": "i686-mingw32", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "x86_64-mingw32", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "arm64-apple-darwin", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "x86_64-apple-darwin", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "x86_64-pc-linux-gnu", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "i686-pc-linux-gnu", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "aarch64-linux-gnu", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" }, { "host": "arm-linux-gnueabihf", - "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-cfea4f7c-v1.zip", - "checksum": "SHA-256:1099291229a9be453c771c5867b8487a0266db9246b672417337b8f511d7f820", - "size": "341118284" + "url": "https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.3/esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "archiveFileName": "esp32-arduino-libs-idf-release_v5.3-489d7a2b-v1.zip", + "checksum": "SHA-256:489012502218a7d30f6c312764bc8d10830a51e1db29558f15181c68373d0095", + "size": "341414090" } ] }, From fb6b788b30d69e57995d36db5374645e4c2b4a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:34:30 +0100 Subject: [PATCH 30/33] feat(zigbee): Support HSV color commands for RGB light endpoint (#10959) * feat(zigbee): Support HSV color commands * ci(pre-commit): Apply automatic fixes * feat(zigbee): Add hue and sat attributes and update color capabilities * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: Me No Dev Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- libraries/Zigbee/src/ZigbeeEP.h | 16 +-- .../src/ep/ZigbeeColorDimmableLight.cpp | 116 +++++++++--------- .../Zigbee/src/ep/ZigbeeColorDimmableLight.h | 60 +++++++-- .../Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp | 50 ++------ 4 files changed, 126 insertions(+), 116 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index 72cde275293..3bdd7f22b23 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -6,27 +6,13 @@ #if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED #include +#include /* Useful defines */ #define ZB_CMD_TIMEOUT 10000 // 10 seconds #define OTA_UPGRADE_QUERY_INTERVAL (1 * 60) // 1 hour = 60 minutes #define ZB_ARRAY_LENTH(arr) (sizeof(arr) / sizeof(arr[0])) -#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) \ { \ diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp index f034daba54a..7ffd6976e1f 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp @@ -4,8 +4,17 @@ 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(); + esp_zb_color_dimmable_light_cfg_t light_cfg = ZIGBEE_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG(); _cluster_list = esp_zb_color_dimmable_light_clusters_create(&light_cfg); + + //Add support for hue and saturation + uint8_t hue = 0; + uint8_t saturation = 0; + + esp_zb_attribute_list_t *color_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID, &hue); + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID, &saturation); + _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 }; @@ -13,9 +22,7 @@ ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP( //set default values _current_state = false; _current_level = 255; - _current_red = 255; - _current_green = 255; - _current_blue = 255; + _current_color = {255, 255, 255}; } uint16_t ZigbeeColorDimmableLight::getCurrentColorX() { @@ -32,37 +39,18 @@ uint16_t ZigbeeColorDimmableLight::getCurrentColorY() { ->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; - - XYZ_TO_RGB(color_X, 1, color_Z, r, g, b); - - red = (uint8_t)(r * (float)255); - green = (uint8_t)(g * (float)255); - blue = (uint8_t)(b * (float)255); +uint8_t ZigbeeColorDimmableLight::getCurrentColorHue() { + return (*(uint8_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_HUE_ID + ) + ->data_p); } -void ZigbeeColorDimmableLight::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); +uint8_t ZigbeeColorDimmableLight::getCurrentColorSaturation() { + 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_SATURATION_ID + ) + ->data_p); } //set attribute method -> method overridden in child class @@ -94,11 +82,7 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me 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; + _current_color = espXYToRgbColor(255, light_color_x, light_color_y); //TODO: Check if level is correct lightChanged(); return; @@ -106,11 +90,17 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me 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; + _current_color = espXYToRgbColor(255, light_color_x, light_color_y); //TODO: Check if level is correct + lightChanged(); + return; + } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { + uint8_t light_color_hue = (*(uint8_t *)message->attribute.data.value); + _current_color = espHsvToRgbColor(light_color_hue, getCurrentColorSaturation(), 255); + lightChanged(); + return; + } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { + uint8_t light_color_saturation = (*(uint8_t *)message->attribute.data.value); + _current_color = espHsvToRgbColor(getCurrentColorHue(), light_color_saturation, 255); lightChanged(); return; } else { @@ -123,7 +113,7 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me void ZigbeeColorDimmableLight::lightChanged() { if (_on_light_change) { - _on_light_change(_current_state, _current_red, _current_green, _current_blue, _current_level); + _on_light_change(_current_state, _current_color.r, _current_color.g, _current_color.b, _current_level); } } @@ -131,12 +121,13 @@ void ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, //Update all attributes _current_state = state; _current_level = level; - _current_red = red; - _current_green = green; - _current_blue = blue; + _current_color = {red, green, blue}; lightChanged(); - log_v("Updating on/off light state to %d", state); + espXyColor_t xy_color = espRgbColorToXYColor(_current_color); + espHsvColor_t hsv_color = espRgbColorToHsvColor(_current_color); + + log_v("Updating light state: %d, level: %d, color: %d, %d, %d", state, level, red, green, blue); /* Update light clusters */ esp_zb_lock_acquire(portMAX_DELAY); //set on/off state @@ -147,28 +138,43 @@ void ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, esp_zb_zcl_set_attribute_val( _endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, &_current_level, false ); - //set color - uint16_t color_x, color_y; - calculateXY(red, green, blue, color_x, color_y); + //set xy color + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID, &xy_color.x, false + ); esp_zb_zcl_set_attribute_val( - _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID, &color_x, false + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID, &xy_color.y, false ); + //set hsv color + uint8_t hue = (uint8_t)hsv_color.h; esp_zb_zcl_set_attribute_val( - _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID, &color_y, false + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID, &hue, false + ); + esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID, &hsv_color.s, false ); esp_zb_lock_release(); } void ZigbeeColorDimmableLight::setLightState(bool state) { - setLight(state, _current_level, _current_red, _current_green, _current_blue); + setLight(state, _current_level, _current_color.r, _current_color.g, _current_color.b); } void ZigbeeColorDimmableLight::setLightLevel(uint8_t level) { - setLight(_current_state, level, _current_red, _current_green, _current_blue); + setLight(_current_state, level, _current_color.r, _current_color.g, _current_color.b); } void ZigbeeColorDimmableLight::setLightColor(uint8_t red, uint8_t green, uint8_t blue) { setLight(_current_state, _current_level, red, green, blue); } +void ZigbeeColorDimmableLight::setLightColor(espRgbColor_t rgb_color) { + setLight(_current_state, _current_level, rgb_color.r, rgb_color.g, rgb_color.b); +} + +void ZigbeeColorDimmableLight::setLightColor(espHsvColor_t hsv_color) { + espRgbColor_t rgb_color = espHsvColorToRgbColor(hsv_color); + setLight(_current_state, _current_level, rgb_color.r, rgb_color.g, rgb_color.b); +} + #endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h index dad267c7b39..265fec1b37c 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h @@ -9,6 +9,47 @@ #include "ZigbeeEP.h" #include "ha/esp_zigbee_ha_standard.h" +#define ZIGBEE_DEFAULT_COLOR_DIMMABLE_LIGHT_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, \ + }, \ + .groups_cfg = \ + { \ + .groups_name_support_id = ESP_ZB_ZCL_GROUPS_NAME_SUPPORT_DEFAULT_VALUE, \ + }, \ + .scenes_cfg = \ + { \ + .scenes_count = ESP_ZB_ZCL_SCENES_SCENE_COUNT_DEFAULT_VALUE, \ + .current_scene = ESP_ZB_ZCL_SCENES_CURRENT_SCENE_DEFAULT_VALUE, \ + .current_group = ESP_ZB_ZCL_SCENES_CURRENT_GROUP_DEFAULT_VALUE, \ + .scene_valid = ESP_ZB_ZCL_SCENES_SCENE_VALID_DEFAULT_VALUE, \ + .name_support = ESP_ZB_ZCL_SCENES_NAME_SUPPORT_DEFAULT_VALUE, \ + }, \ + .on_off_cfg = \ + { \ + .on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE, \ + }, \ + .level_cfg = \ + { \ + .current_level = ESP_ZB_ZCL_LEVEL_CONTROL_CURRENT_LEVEL_DEFAULT_VALUE, \ + }, \ + .color_cfg = { \ + .current_x = ESP_ZB_ZCL_COLOR_CONTROL_CURRENT_X_DEF_VALUE, \ + .current_y = ESP_ZB_ZCL_COLOR_CONTROL_CURRENT_Y_DEF_VALUE, \ + .color_mode = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_MODE_DEFAULT_VALUE, \ + .options = ESP_ZB_ZCL_COLOR_CONTROL_OPTIONS_DEFAULT_VALUE, \ + .enhanced_color_mode = ESP_ZB_ZCL_COLOR_CONTROL_ENHANCED_COLOR_MODE_DEFAULT_VALUE, \ + .color_capabilities = 0x0009, \ + }, \ + } + class ZigbeeColorDimmableLight : public ZigbeeEP { public: ZigbeeColorDimmableLight(uint8_t endpoint); @@ -24,6 +65,8 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { void setLightState(bool state); void setLightLevel(uint8_t level); void setLightColor(uint8_t red, uint8_t green, uint8_t blue); + void setLightColor(espRgbColor_t rgb_color); + void setLightColor(espHsvColor_t hsv_color); void setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue); bool getLightState() { @@ -32,23 +75,26 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { uint8_t getLightLevel() { return _current_level; } + espRgbColor_t getLightColor() { + return _current_color; + } uint8_t getLightRed() { - return _current_red; + return _current_color.r; } uint8_t getLightGreen() { - return _current_green; + return _current_color.g; } uint8_t getLightBlue() { - return _current_blue; + return _current_color.b; } 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 calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y); uint16_t getCurrentColorX(); uint16_t getCurrentColorY(); + uint8_t getCurrentColorHue(); + uint8_t getCurrentColorSaturation(); void lightChanged(); //callback function to be called on light change (State, R, G, B, Level) @@ -56,9 +102,7 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { bool _current_state; uint8_t _current_level; - uint16_t _current_red; - uint16_t _current_green; - uint16_t _current_blue; + espRgbColor_t _current_color; }; #endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp index 4fd492a5477..7bdd8b8ad6a 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmerSwitch.cpp @@ -16,24 +16,6 @@ ZigbeeColorDimmerSwitch::ZigbeeColorDimmerSwitch(uint8_t endpoint) : ZigbeeEP(en }; } -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!"); @@ -417,15 +399,13 @@ void ZigbeeColorDimmerSwitch::setLightLevel(uint8_t level, uint8_t endpoint, esp 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); + espXyColor_t xy_color = espRgbToXYColor(red, green, blue); 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.color_x = xy_color.x; + cmd_req.color_y = xy_color.y; cmd_req.transition_time = 0; log_v("Sending 'set light color' command"); esp_zb_lock_acquire(portMAX_DELAY); @@ -438,16 +418,14 @@ void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t 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); + espXyColor_t xy_color = espRgbToXYColor(red, green, blue); 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.color_x = xy_color.x; + cmd_req.color_y = xy_color.y; cmd_req.transition_time = 0; log_v("Sending 'set light color' command to group address 0x%x", group_addr); esp_zb_lock_acquire(portMAX_DELAY); @@ -460,17 +438,15 @@ void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t 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); + espXyColor_t xy_color = espRgbToXYColor(red, green, blue); 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.color_x = xy_color.x; + cmd_req.color_y = xy_color.y; cmd_req.transition_time = 0; log_v("Sending 'set light color' command to endpoint %d, address 0x%x", endpoint, short_addr); esp_zb_lock_acquire(portMAX_DELAY); @@ -483,17 +459,15 @@ void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t void ZigbeeColorDimmerSwitch::setLightColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t endpoint, esp_zb_ieee_addr_t ieee_addr) { if (_is_bound) { - //Convert RGB to XY - uint16_t color_x, color_y; - calculateXY(red, green, blue, color_x, color_y); + espXyColor_t xy_color = espRgbToXYColor(red, green, blue); 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.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT; memcpy(cmd_req.zcl_basic_cmd.dst_addr_u.addr_long, ieee_addr, sizeof(esp_zb_ieee_addr_t)); - cmd_req.color_x = color_x; - cmd_req.color_y = color_y; + cmd_req.color_x = xy_color.x; + cmd_req.color_y = xy_color.y; cmd_req.transition_time = 0; log_v( "Sending 'set light color' command to endpoint %d, ieee address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", endpoint, ieee_addr[7], ieee_addr[6], From 6fb55a7f688742c50ccd715843b03d8e599f31bf Mon Sep 17 00:00:00 2001 From: vortigont Date: Thu, 13 Feb 2025 21:03:21 +0900 Subject: [PATCH 31/33] feat(board): Update 3rd party board Huidu HD-WF2/HD-WF4 (#10957) * feat(board): Update 3rd party board Huidu HD-WF2/HD-WF4 Followup #10779 - fixed flash mode to 'dio' - removed psram flags - corrected gpio mapping defines * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- boards.txt | 28 ---------- variants/huidu_hd_wf2/pins_arduino.h | 15 ------ variants/huidu_hd_wf4/pins_arduino.h | 77 +++++++++++++++------------- 3 files changed, 41 insertions(+), 79 deletions(-) diff --git a/boards.txt b/boards.txt index 9ecd6359759..86f821476cf 100644 --- a/boards.txt +++ b/boards.txt @@ -48923,20 +48923,6 @@ huidu_hd_wf2.menu.CPUFreq.80.build.f_cpu=80000000L huidu_hd_wf2.menu.CPUFreq.40=40MHz huidu_hd_wf2.menu.CPUFreq.40.build.f_cpu=40000000L -huidu_hd_wf2.menu.PSRAM.enabled=QSPI Flash fix -huidu_hd_wf2.menu.PSRAM.enabled.build.defines=-DBOARD_HAS_PSRAM -huidu_hd_wf2.menu.PSRAM.enabled.build.psram_type=qspi - -huidu_hd_wf2.menu.FlashMode.qio=QIO 80MHz -huidu_hd_wf2.menu.FlashMode.qio.build.flash_mode=dio -huidu_hd_wf2.menu.FlashMode.qio.build.boot=qio -huidu_hd_wf2.menu.FlashMode.qio.build.boot_freq=80m -huidu_hd_wf2.menu.FlashMode.qio.build.flash_freq=80m -huidu_hd_wf2.menu.FlashMode.qio120=QIO 120MHz -huidu_hd_wf2.menu.FlashMode.qio120.build.flash_mode=dio -huidu_hd_wf2.menu.FlashMode.qio120.build.boot=qio -huidu_hd_wf2.menu.FlashMode.qio120.build.boot_freq=120m -huidu_hd_wf2.menu.FlashMode.qio120.build.flash_freq=80m huidu_hd_wf2.menu.FlashMode.dio=DIO 80MHz huidu_hd_wf2.menu.FlashMode.dio.build.flash_mode=dio huidu_hd_wf2.menu.FlashMode.dio.build.boot=dio @@ -49076,20 +49062,6 @@ huidu_hd_wf4.menu.CPUFreq.80.build.f_cpu=80000000L huidu_hd_wf4.menu.CPUFreq.40=40MHz huidu_hd_wf4.menu.CPUFreq.40.build.f_cpu=40000000L -huidu_hd_wf4.menu.PSRAM.enabled=QSPI Flash fix -huidu_hd_wf4.menu.PSRAM.enabled.build.defines=-DBOARD_HAS_PSRAM -huidu_hd_wf4.menu.PSRAM.enabled.build.psram_type=qspi - -huidu_hd_wf4.menu.FlashMode.qio=QIO 80MHz -huidu_hd_wf4.menu.FlashMode.qio.build.flash_mode=dio -huidu_hd_wf4.menu.FlashMode.qio.build.boot=qio -huidu_hd_wf4.menu.FlashMode.qio.build.boot_freq=80m -huidu_hd_wf4.menu.FlashMode.qio.build.flash_freq=80m -huidu_hd_wf4.menu.FlashMode.qio120=QIO 120MHz -huidu_hd_wf4.menu.FlashMode.qio120.build.flash_mode=dio -huidu_hd_wf4.menu.FlashMode.qio120.build.boot=qio -huidu_hd_wf4.menu.FlashMode.qio120.build.boot_freq=120m -huidu_hd_wf4.menu.FlashMode.qio120.build.flash_freq=80m huidu_hd_wf4.menu.FlashMode.dio=DIO 80MHz huidu_hd_wf4.menu.FlashMode.dio.build.flash_mode=dio huidu_hd_wf4.menu.FlashMode.dio.build.boot=dio diff --git a/variants/huidu_hd_wf2/pins_arduino.h b/variants/huidu_hd_wf2/pins_arduino.h index 97d72dcc0f3..6068e4d6371 100644 --- a/variants/huidu_hd_wf2/pins_arduino.h +++ b/variants/huidu_hd_wf2/pins_arduino.h @@ -58,19 +58,4 @@ static const uint8_t MOSI = 11; static const uint8_t MISO = 13; static const uint8_t SCK = 12; -static const uint8_t T1 = WF2_X1_R1_PIN; -static const uint8_t T2 = WF2_X1_R2_PIN; -static const uint8_t T3 = WF2_X1_G1_PIN; -static const uint8_t T4 = WF2_X1_G2_PIN; -static const uint8_t T5 = WF2_X1_B1_PIN; -static const uint8_t T6 = WF2_X1_B2_PIN; -static const uint8_t T7 = WF2_A_PIN; -static const uint8_t T8 = WF2_B_PIN; -static const uint8_t T9 = WF2_C_PIN; -static const uint8_t T10 = WF2_D_PIN; -static const uint8_t T11 = WF2_X1_E_PIN; -static const uint8_t T12 = WF2_OE_PIN; -static const uint8_t T13 = WF2_CLK_PIN; -static const uint8_t T14 = WF2_LAT_PIN; - #endif /* Pins_Arduino_h */ diff --git a/variants/huidu_hd_wf4/pins_arduino.h b/variants/huidu_hd_wf4/pins_arduino.h index cd96db6c445..5b8667477bf 100644 --- a/variants/huidu_hd_wf4/pins_arduino.h +++ b/variants/huidu_hd_wf4/pins_arduino.h @@ -19,7 +19,7 @@ #define WF4_CLK_PIN 34 #define WF4_LAT_PIN 33 -// X1 +// X1 HUB75 #define WF4_X1_R1_PIN 2 #define WF4_X1_R2_PIN 3 #define WF4_X1_G1_PIN 6 @@ -28,7 +28,7 @@ #define WF4_X1_B2_PIN 11 #define WF4_X1_CS_PIN 45 // CS gpio must be set HIGH to enable X1 output -// X2 +// X2 HUB75 #define WF4_X2_R1_PIN 4 #define WF4_X2_R2_PIN 5 #define WF4_X2_G1_PIN 8 @@ -37,7 +37,7 @@ #define WF4_X2_B2_PIN 13 #define WF4_X2_CS_PIN WF4_X1_CS_PIN // CS gpio must be set HIGH to enable X2 output -// X3 +// X3 HUB75 #define WF4_X3_R1_PIN 2 #define WF4_X3_R2_PIN 3 #define WF4_X3_G1_PIN 6 @@ -46,7 +46,7 @@ #define WF4_X3_B2_PIN 11 #define WF4_X3_CS_PIN 14 // CS gpio must be set HIGH to enable X3 output -// X4 +// X4 HUB75 #define WF4_X4_R1_PIN 4 #define WF4_X4_R2_PIN 5 #define WF4_X4_G1_PIN 8 @@ -55,52 +55,57 @@ #define WF4_X4_B2_PIN 13 #define WF4_X4_CS_PIN WF4_X3_CS_PIN // CS gpio must be set HIGH to enable X4 output -//#define WF4_P1_PIN UART -#define WF4_P2_DATA_PIN 0 // GPIO0 boot -#define WF4_P5_DATA_PIN 16 // temperature -//#define WF4_P7_PIN VCC/GND -#define WF4_P11_DATA_PIN 15 // IR +// P1 is a UART connector +#define WF4_P1_RX_PIN 44 +#define WF4_P1_TX_PIN 43 + +// P2: PCB holes gpio/gnd +#define WF4_P2_DATA_PIN 0 // GPIO0 boot + +// P5: temperature sensor connector +#define WF4_P5_DATA_PIN 16 + +// P7: VCC/GPIO holes on PCB +#define WF4_P7_DATA_PIN 1 + +// P11: IR connector +#define WF4_P11_DATA_PIN 15 + +// P12: two gpio's, Vcc, GND #define WF4_P12_DATA1_PIN 47 #define WF4_P12_DATA2_PIN 18 -#define WF4_S1_DATA_PIN 17 // Button -#define WF4_S2_DATA_PIN 48 -#define WF4_S3_DATA_PIN 26 -#define WF4_S4_DATA_PIN 46 + +// S1 Button +#define WF4_S1_DATA_PIN 17 + +// S2-S3 PCB holes +#define WF4_S2_DATA_PIN 48 +#define WF4_S3_DATA_PIN 26 +#define WF4_S4_DATA_PIN 46 #define WF4_BUTTON_TEST WF4_S1_PIN // Test key button on PCB, 1=normal, 0=pressed #define WF4_LED_RUN_PIN 40 // Status LED on PCB #define WF4_BM8563_I2C_SDA 41 // RTC BM8563 I2C port #define WF4_BM8563_I2C_SCL 42 -#define WF4_USB_DM_PIN 19 -#define WF4_USB_DP_PIN 20 +#define WF4_USB_DN_PIN 19 // USB-A D- +#define WF4_USB_DP_PIN 20 // USB-A D+ #define LED_BUILTIN WF4_LED_RUN_PIN #define BUILTIN_LED LED_BUILTIN // backward compatibility -static const uint8_t TX = 43; -static const uint8_t RX = 44; +static const uint8_t TX = WF4_P1_TX_PIN; +static const uint8_t RX = WF4_P1_RX_PIN; static const uint8_t SDA = WF4_BM8563_I2C_SDA; static const uint8_t SCL = WF4_BM8563_I2C_SCL; -static const uint8_t SS = 10; -static const uint8_t MOSI = 11; -static const uint8_t MISO = 13; -static const uint8_t SCK = 12; - -static const uint8_t T1 = WF4_X1_R1_PIN; -static const uint8_t T2 = WF4_X1_R2_PIN; -static const uint8_t T3 = WF4_X1_G1_PIN; -static const uint8_t T4 = WF4_X1_G2_PIN; -static const uint8_t T5 = WF4_X1_B1_PIN; -static const uint8_t T6 = WF4_X1_B2_PIN; -static const uint8_t T7 = WF4_A_PIN; -static const uint8_t T8 = WF4_B_PIN; -static const uint8_t T9 = WF4_C_PIN; -static const uint8_t T10 = WF4_D_PIN; -static const uint8_t T11 = WF4_E_PIN; -static const uint8_t T12 = WF4_OE_PIN; -static const uint8_t T13 = WF4_CLK_PIN; -static const uint8_t T14 = WF4_LAT_PIN; +// there is no dedicated SPI connector on board, but SPI could be accessed via PCB holes +static const uint8_t SS = WF4_S2_DATA_PIN; +static const uint8_t MOSI = WF4_S3_DATA_PIN; +static const uint8_t MISO = WF4_S4_DATA_PIN; +static const uint8_t SCK = WF4_P7_DATA_PIN; + +// touch pins are mostly busy with HUB75 ports +static const uint8_t T1 = WF4_P7_DATA_PIN; #endif /* Pins_Arduino_h */ From 5488d5d23fb0da47f463fce89a879c4593a6231f Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 13 Feb 2025 13:14:05 +0100 Subject: [PATCH 32/33] Fix crash when using String::move on empty string (#10938) (#10945) Fixes: #10938 Keep allocated memory when rhs fits Use case: Appending to a String with pre-allocated memory (e.g. from `reserve()`) No need to move 0-termination char in String::move Simplify calls to String::copy A lot of the same checks were done before calling `copy()` which should be done in the `copy()` function itself. String::copy() Should not copy more than given length Fix potential out of range in String::concat There is no prerequisite the given array has to be a 0-terminated char array. So we should only copy the length that has been given. The `setLen()` function will make sure the internal string is 0-terminated. So no need to dangerously assume there will be 1 more byte to copy Allow String::concat(const String &s) with s.buffer() == nullptr When constructing a String object, the internal buffer is a nullptr. However concatenating this to another String would return `false` while this is perfectly fine to do. --- cores/esp32/WString.cpp | 54 ++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/cores/esp32/WString.cpp b/cores/esp32/WString.cpp index 71183213ac2..5296a2d9652 100644 --- a/cores/esp32/WString.cpp +++ b/cores/esp32/WString.cpp @@ -226,11 +226,11 @@ bool String::changeBuffer(unsigned int maxStrLen) { /*********************************************/ String &String::copy(const char *cstr, unsigned int length) { - if (!reserve(length)) { + if (cstr == nullptr || !reserve(length)) { invalidate(); return *this; } - memmove(wbuffer(), cstr, length + 1); + memmove(wbuffer(), cstr, length); setLen(length); return *this; } @@ -239,15 +239,18 @@ String &String::copy(const char *cstr, unsigned int length) { void String::move(String &rhs) { if (buffer()) { if (capacity() >= rhs.len()) { - memmove(wbuffer(), rhs.buffer(), rhs.length() + 1); + // Use case: When 'reserve()' was called and the first + // assignment/append is the return value of a function. + if (rhs.len() && rhs.buffer()) { + memmove(wbuffer(), rhs.buffer(), rhs.length()); + } setLen(rhs.len()); rhs.invalidate(); return; - } else { - if (!isSSO()) { - free(wbuffer()); - setBuffer(nullptr); - } + } + if (!isSSO()) { + free(wbuffer()); + setBuffer(nullptr); } } if (rhs.isSSO()) { @@ -259,10 +262,7 @@ void String::move(String &rhs) { } setCapacity(rhs.capacity()); setLen(rhs.len()); - rhs.setSSO(false); - rhs.setCapacity(0); - rhs.setBuffer(nullptr); - rhs.setLen(0); + rhs.init(); } #endif @@ -270,12 +270,7 @@ String &String::operator=(const String &rhs) { if (this == &rhs) { return *this; } - if (rhs.buffer()) { - copy(rhs.buffer(), rhs.len()); - } else { - invalidate(); - } - return *this; + return copy(rhs.buffer(), rhs.len()); } #ifdef __GXX_EXPERIMENTAL_CXX0X__ @@ -295,12 +290,7 @@ String &String::operator=(StringSumHelper &&rval) { #endif String &String::operator=(const char *cstr) { - if (cstr) { - copy(cstr, strlen(cstr)); - } else { - invalidate(); - } - return *this; + return copy(cstr, strlen(cstr)); } /*********************************************/ @@ -311,23 +301,21 @@ bool String::concat(const String &s) { // Special case if we're concatting ourself (s += s;) since we may end up // realloc'ing the buffer and moving s.buffer in the method called if (&s == this) { - unsigned int newlen = 2 * len(); - if (!s.buffer()) { - return false; - } if (s.len() == 0) { return true; } + if (!s.buffer()) { + return false; + } + unsigned int newlen = 2 * len(); if (!reserve(newlen)) { return false; } memmove(wbuffer() + len(), buffer(), len()); setLen(newlen); - wbuffer()[len()] = 0; return true; - } else { - return concat(s.buffer(), s.len()); } + return concat(s.buffer(), s.len()); } bool String::concat(const char *cstr, unsigned int length) { @@ -343,10 +331,10 @@ bool String::concat(const char *cstr, unsigned int length) { } if (cstr >= wbuffer() && cstr < wbuffer() + len()) { // compatible with SSO in ram #6155 (case "x += x.c_str()") - memmove(wbuffer() + len(), cstr, length + 1); + memmove(wbuffer() + len(), cstr, length); } else { // compatible with source in flash #6367 - memcpy_P(wbuffer() + len(), cstr, length + 1); + memcpy_P(wbuffer() + len(), cstr, length); } setLen(newlen); return true; From 62082398d322c636184ba4b4a385f9b618f61a77 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Thu, 13 Feb 2025 14:32:20 +0200 Subject: [PATCH 33/33] Update core version to 3.1.2 --- cores/esp32/esp_arduino_version.h | 2 +- libraries/ArduinoOTA/library.properties | 2 +- libraries/AsyncUDP/library.properties | 2 +- libraries/BLE/library.properties | 2 +- libraries/BluetoothSerial/library.properties | 2 +- libraries/DNSServer/library.properties | 2 +- libraries/EEPROM/library.properties | 2 +- libraries/ESP32/library.properties | 2 +- libraries/ESP_I2S/library.properties | 2 +- libraries/ESP_NOW/library.properties | 2 +- libraries/ESP_SR/library.properties | 2 +- libraries/ESPmDNS/library.properties | 2 +- libraries/Ethernet/library.properties | 2 +- libraries/FFat/library.properties | 2 +- libraries/FS/library.properties | 2 +- libraries/HTTPClient/library.properties | 2 +- libraries/HTTPUpdate/library.properties | 2 +- libraries/HTTPUpdateServer/library.properties | 2 +- libraries/Insights/library.properties | 2 +- libraries/LittleFS/library.properties | 2 +- libraries/Matter/library.properties | 2 +- libraries/NetBIOS/library.properties | 2 +- libraries/Network/library.properties | 2 +- libraries/NetworkClientSecure/library.properties | 2 +- libraries/OpenThread/library.properties | 2 +- libraries/PPP/library.properties | 2 +- libraries/Preferences/library.properties | 2 +- libraries/RainMaker/library.properties | 2 +- libraries/SD/library.properties | 2 +- libraries/SD_MMC/library.properties | 2 +- libraries/SPI/library.properties | 2 +- libraries/SPIFFS/library.properties | 2 +- libraries/SimpleBLE/library.properties | 2 +- libraries/TFLiteMicro/library.properties | 2 +- libraries/Ticker/library.properties | 2 +- libraries/USB/library.properties | 2 +- libraries/Update/library.properties | 2 +- libraries/WebServer/library.properties | 2 +- libraries/WiFi/library.properties | 2 +- libraries/WiFiProv/library.properties | 2 +- libraries/Wire/library.properties | 2 +- libraries/Zigbee/library.properties | 2 +- package.json | 2 +- platform.txt | 2 +- 44 files changed, 44 insertions(+), 44 deletions(-) diff --git a/cores/esp32/esp_arduino_version.h b/cores/esp32/esp_arduino_version.h index dece36df1b8..4c9961053f9 100644 --- a/cores/esp32/esp_arduino_version.h +++ b/cores/esp32/esp_arduino_version.h @@ -23,7 +23,7 @@ extern "C" { /** Minor version number (x.X.x) */ #define ESP_ARDUINO_VERSION_MINOR 1 /** Patch version number (x.x.X) */ -#define ESP_ARDUINO_VERSION_PATCH 1 +#define ESP_ARDUINO_VERSION_PATCH 2 /** * Macro to convert ARDUINO version number into an integer diff --git a/libraries/ArduinoOTA/library.properties b/libraries/ArduinoOTA/library.properties index 524564ff699..76043001e9b 100644 --- a/libraries/ArduinoOTA/library.properties +++ b/libraries/ArduinoOTA/library.properties @@ -1,5 +1,5 @@ name=ArduinoOTA -version=3.1.1 +version=3.1.2 author=Ivan Grokhotkov and Hristo Gochkov maintainer=Hristo Gochkov sentence=Enables Over The Air upgrades, via wifi and espota.py UDP request/TCP download. diff --git a/libraries/AsyncUDP/library.properties b/libraries/AsyncUDP/library.properties index 5b1afd68273..c27bbde2eb5 100644 --- a/libraries/AsyncUDP/library.properties +++ b/libraries/AsyncUDP/library.properties @@ -1,5 +1,5 @@ name=ESP32 Async UDP -version=3.1.1 +version=3.1.2 author=Me-No-Dev maintainer=Me-No-Dev sentence=Async UDP Library for ESP32 diff --git a/libraries/BLE/library.properties b/libraries/BLE/library.properties index afda29db686..35346c13b67 100644 --- a/libraries/BLE/library.properties +++ b/libraries/BLE/library.properties @@ -1,5 +1,5 @@ name=BLE -version=3.1.1 +version=3.1.2 author=Neil Kolban maintainer=Dariusz Krempa sentence=BLE functions for ESP32 diff --git a/libraries/BluetoothSerial/library.properties b/libraries/BluetoothSerial/library.properties index cf0dab07aa7..d33d461d77c 100644 --- a/libraries/BluetoothSerial/library.properties +++ b/libraries/BluetoothSerial/library.properties @@ -1,5 +1,5 @@ name=BluetoothSerial -version=3.1.1 +version=3.1.2 author=Evandro Copercini maintainer=Evandro Copercini sentence=Simple UART to Classical Bluetooth bridge for ESP32 diff --git a/libraries/DNSServer/library.properties b/libraries/DNSServer/library.properties index 8a6d1786625..7996c2fef5f 100644 --- a/libraries/DNSServer/library.properties +++ b/libraries/DNSServer/library.properties @@ -1,5 +1,5 @@ name=DNSServer -version=3.1.1 +version=3.1.2 author=Kristijan Novoselić maintainer=Kristijan Novoselić, sentence=A simple DNS server for ESP32. diff --git a/libraries/EEPROM/library.properties b/libraries/EEPROM/library.properties index d66116947c7..aabaf2e3be1 100644 --- a/libraries/EEPROM/library.properties +++ b/libraries/EEPROM/library.properties @@ -1,5 +1,5 @@ name=EEPROM -version=3.1.1 +version=3.1.2 author=Ivan Grokhotkov maintainer=Paolo Becchi sentence=Enables reading and writing data a sequential, addressable FLASH storage diff --git a/libraries/ESP32/library.properties b/libraries/ESP32/library.properties index 160b392f39e..f3cc3d94be2 100644 --- a/libraries/ESP32/library.properties +++ b/libraries/ESP32/library.properties @@ -1,5 +1,5 @@ name=ESP32 -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov maintainer=Hristo Gochkov sentence=ESP32 sketches examples diff --git a/libraries/ESP_I2S/library.properties b/libraries/ESP_I2S/library.properties index 3105266d708..8369862022c 100644 --- a/libraries/ESP_I2S/library.properties +++ b/libraries/ESP_I2S/library.properties @@ -1,5 +1,5 @@ name=ESP_I2S -version=3.1.1 +version=3.1.2 author=me-no-dev maintainer=me-no-dev sentence=Library for ESP I2S communication diff --git a/libraries/ESP_NOW/library.properties b/libraries/ESP_NOW/library.properties index ba52e080046..1f7999ed26f 100644 --- a/libraries/ESP_NOW/library.properties +++ b/libraries/ESP_NOW/library.properties @@ -1,5 +1,5 @@ name=ESP_NOW -version=3.1.1 +version=3.1.2 author=me-no-dev maintainer=P-R-O-C-H-Y sentence=Library for ESP_NOW diff --git a/libraries/ESP_SR/library.properties b/libraries/ESP_SR/library.properties index b52c0552cbb..35225ae79bc 100644 --- a/libraries/ESP_SR/library.properties +++ b/libraries/ESP_SR/library.properties @@ -1,5 +1,5 @@ name=ESP_SR -version=3.1.1 +version=3.1.2 author=me-no-dev maintainer=me-no-dev sentence=Library for ESP Sound Recognition diff --git a/libraries/ESPmDNS/library.properties b/libraries/ESPmDNS/library.properties index fe06ddb6851..3612e3a84de 100644 --- a/libraries/ESPmDNS/library.properties +++ b/libraries/ESPmDNS/library.properties @@ -1,5 +1,5 @@ name=ESPmDNS -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov maintainer=Hristo Gochkov sentence=ESP32 mDNS Library diff --git a/libraries/Ethernet/library.properties b/libraries/Ethernet/library.properties index e8075d15f11..82fff9cb85e 100644 --- a/libraries/Ethernet/library.properties +++ b/libraries/Ethernet/library.properties @@ -1,5 +1,5 @@ name=Ethernet -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Enables network connection (local and Internet) using the ESP32 Ethernet. diff --git a/libraries/FFat/library.properties b/libraries/FFat/library.properties index e75ef4206a6..3c387cd3c64 100644 --- a/libraries/FFat/library.properties +++ b/libraries/FFat/library.properties @@ -1,5 +1,5 @@ name=FFat -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov, Larry Bernstone maintainer=Hristo Gochkov sentence=ESP32 FAT on Flash File System diff --git a/libraries/FS/library.properties b/libraries/FS/library.properties index b339f57356f..1a00810a221 100644 --- a/libraries/FS/library.properties +++ b/libraries/FS/library.properties @@ -1,5 +1,5 @@ name=FS -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov maintainer=Hristo Gochkov sentence=ESP32 File System diff --git a/libraries/HTTPClient/library.properties b/libraries/HTTPClient/library.properties index df11cb033b8..20f971ba3ea 100644 --- a/libraries/HTTPClient/library.properties +++ b/libraries/HTTPClient/library.properties @@ -1,5 +1,5 @@ name=HTTPClient -version=3.1.1 +version=3.1.2 author=Markus Sattler maintainer=Markus Sattler sentence=HTTP Client for ESP32 diff --git a/libraries/HTTPUpdate/library.properties b/libraries/HTTPUpdate/library.properties index dc02780c8d2..9145001ce31 100644 --- a/libraries/HTTPUpdate/library.properties +++ b/libraries/HTTPUpdate/library.properties @@ -1,5 +1,5 @@ name=HTTPUpdate -version=3.1.1 +version=3.1.2 author=Markus Sattler maintainer=Markus Sattler sentence=Http Update for ESP32 diff --git a/libraries/HTTPUpdateServer/library.properties b/libraries/HTTPUpdateServer/library.properties index 6cdcc1b34ea..7a05dd26e19 100644 --- a/libraries/HTTPUpdateServer/library.properties +++ b/libraries/HTTPUpdateServer/library.properties @@ -1,5 +1,5 @@ name=HTTPUpdateServer -version=3.1.1 +version=3.1.2 author=Hristo Kapanakov maintainer= sentence=Simple HTTP Update server based on the WebServer diff --git a/libraries/Insights/library.properties b/libraries/Insights/library.properties index 39b1f80c876..076b5b8d0fb 100644 --- a/libraries/Insights/library.properties +++ b/libraries/Insights/library.properties @@ -1,5 +1,5 @@ name=ESP Insights -version=3.1.1 +version=3.1.2 author=Sanket Wadekar maintainer=Sanket Wadekar sentence=ESP Insights diff --git a/libraries/LittleFS/library.properties b/libraries/LittleFS/library.properties index 9f665385dce..f0d29ce90d8 100644 --- a/libraries/LittleFS/library.properties +++ b/libraries/LittleFS/library.properties @@ -1,5 +1,5 @@ name=LittleFS -version=3.1.1 +version=3.1.2 author= maintainer= sentence=LittleFS for esp32 diff --git a/libraries/Matter/library.properties b/libraries/Matter/library.properties index 14a7f48c6de..7f84307354e 100644 --- a/libraries/Matter/library.properties +++ b/libraries/Matter/library.properties @@ -1,5 +1,5 @@ name=Matter -version=3.1.1 +version=3.1.2 author=Rodrigo Garcia | GitHub @SuGlider maintainer=Rodrigo Garcia sentence=Library for supporting Matter environment on ESP32. diff --git a/libraries/NetBIOS/library.properties b/libraries/NetBIOS/library.properties index f28405b7f64..571a5a75d9d 100644 --- a/libraries/NetBIOS/library.properties +++ b/libraries/NetBIOS/library.properties @@ -1,5 +1,5 @@ name=NetBIOS -version=3.1.1 +version=3.1.2 author=Pablo@xpablo.cz maintainer=Hristo Gochkov sentence=Enables NBNS (NetBIOS) name resolution. diff --git a/libraries/Network/library.properties b/libraries/Network/library.properties index d3a9f329306..e7dea53d2e7 100644 --- a/libraries/Network/library.properties +++ b/libraries/Network/library.properties @@ -1,5 +1,5 @@ name=Networking -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=General network management library. diff --git a/libraries/NetworkClientSecure/library.properties b/libraries/NetworkClientSecure/library.properties index 6d79dc61095..ec5ee5e2548 100644 --- a/libraries/NetworkClientSecure/library.properties +++ b/libraries/NetworkClientSecure/library.properties @@ -1,5 +1,5 @@ name=NetworkClientSecure -version=3.1.1 +version=3.1.2 author=Evandro Luis Copercini maintainer=Github Community sentence=Enables secure network connection (local and Internet) using the ESP32 built-in WiFi. diff --git a/libraries/OpenThread/library.properties b/libraries/OpenThread/library.properties index a50a803dfa6..ed4506d5efd 100644 --- a/libraries/OpenThread/library.properties +++ b/libraries/OpenThread/library.properties @@ -1,5 +1,5 @@ name=OpenThread -version=3.1.1 +version=3.1.2 author=Rodrigo Garcia | GitHub @SuGlider maintainer=Rodrigo Garcia sentence=Library for OpenThread Network on ESP32. diff --git a/libraries/PPP/library.properties b/libraries/PPP/library.properties index fefd89d4904..5266ac6f6f7 100644 --- a/libraries/PPP/library.properties +++ b/libraries/PPP/library.properties @@ -1,5 +1,5 @@ name=PPP -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Enables network connection using GSM Modem. diff --git a/libraries/Preferences/library.properties b/libraries/Preferences/library.properties index 842ade94003..d5e3a99d081 100644 --- a/libraries/Preferences/library.properties +++ b/libraries/Preferences/library.properties @@ -1,5 +1,5 @@ name=Preferences -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Provides friendly access to ESP32's Non-Volatile Storage diff --git a/libraries/RainMaker/library.properties b/libraries/RainMaker/library.properties index 2f49258e78d..2ff5262c31a 100644 --- a/libraries/RainMaker/library.properties +++ b/libraries/RainMaker/library.properties @@ -1,5 +1,5 @@ name=ESP RainMaker -version=3.1.1 +version=3.1.2 author=Sweety Mhaiske maintainer=Hristo Gochkov sentence=ESP RainMaker Support diff --git a/libraries/SD/library.properties b/libraries/SD/library.properties index 117de77ca36..20815d220d2 100644 --- a/libraries/SD/library.properties +++ b/libraries/SD/library.properties @@ -1,5 +1,5 @@ name=SD -version=3.1.1 +version=3.1.2 author=Arduino, SparkFun maintainer=Arduino sentence=Enables reading and writing on SD cards. For all Arduino boards. diff --git a/libraries/SD_MMC/library.properties b/libraries/SD_MMC/library.properties index 72c12c61d5c..9ebf53dde74 100644 --- a/libraries/SD_MMC/library.properties +++ b/libraries/SD_MMC/library.properties @@ -1,5 +1,5 @@ name=SD_MMC -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov maintainer=Hristo Gochkov sentence=ESP32 SDMMC File System diff --git a/libraries/SPI/library.properties b/libraries/SPI/library.properties index 8c567f00d40..81c76fcca48 100644 --- a/libraries/SPI/library.properties +++ b/libraries/SPI/library.properties @@ -1,5 +1,5 @@ name=SPI -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Enables the communication with devices that use the Serial Peripheral Interface (SPI) Bus. For all Arduino boards, BUT Arduino DUE. diff --git a/libraries/SPIFFS/library.properties b/libraries/SPIFFS/library.properties index 68cb55ca124..94f38b3ea00 100644 --- a/libraries/SPIFFS/library.properties +++ b/libraries/SPIFFS/library.properties @@ -1,5 +1,5 @@ name=SPIFFS -version=3.1.1 +version=3.1.2 author=Hristo Gochkov, Ivan Grokhtkov maintainer=Hristo Gochkov sentence=ESP32 SPIFFS File System diff --git a/libraries/SimpleBLE/library.properties b/libraries/SimpleBLE/library.properties index a5541dd9750..1ad4a83a69a 100644 --- a/libraries/SimpleBLE/library.properties +++ b/libraries/SimpleBLE/library.properties @@ -1,5 +1,5 @@ name=SimpleBLE -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Provides really simple BLE advertizer with just on and off diff --git a/libraries/TFLiteMicro/library.properties b/libraries/TFLiteMicro/library.properties index ef9b474611f..39badc8ba1b 100644 --- a/libraries/TFLiteMicro/library.properties +++ b/libraries/TFLiteMicro/library.properties @@ -1,5 +1,5 @@ name=TFLite Micro -version=3.1.1 +version=3.1.2 author=Sanket Wadekar maintainer=Sanket Wadekar sentence=TensorFlow Lite for Microcontrollers diff --git a/libraries/Ticker/library.properties b/libraries/Ticker/library.properties index 8d123806109..080a4a17f40 100644 --- a/libraries/Ticker/library.properties +++ b/libraries/Ticker/library.properties @@ -1,5 +1,5 @@ name=Ticker -version=3.1.1 +version=3.1.2 author=Bert Melis maintainer=Hristo Gochkov sentence=Allows to call functions with a given interval. diff --git a/libraries/USB/library.properties b/libraries/USB/library.properties index 17d4e25cae2..9dc655dbd10 100644 --- a/libraries/USB/library.properties +++ b/libraries/USB/library.properties @@ -1,5 +1,5 @@ name=USB -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=ESP32S2 USB Library diff --git a/libraries/Update/library.properties b/libraries/Update/library.properties index 5af58e380e1..83fbfd4c901 100644 --- a/libraries/Update/library.properties +++ b/libraries/Update/library.properties @@ -1,5 +1,5 @@ name=Update -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=ESP32 Sketch Update Library diff --git a/libraries/WebServer/library.properties b/libraries/WebServer/library.properties index c225d6ebd95..73d8d87d690 100644 --- a/libraries/WebServer/library.properties +++ b/libraries/WebServer/library.properties @@ -1,5 +1,5 @@ name=WebServer -version=3.1.1 +version=3.1.2 author=Ivan Grokhotkov maintainer=Ivan Grokhtkov sentence=Simple web server library diff --git a/libraries/WiFi/library.properties b/libraries/WiFi/library.properties index a82ae45a2d2..608bf607bf7 100644 --- a/libraries/WiFi/library.properties +++ b/libraries/WiFi/library.properties @@ -1,5 +1,5 @@ name=WiFi -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Enables network connection (local and Internet) using the ESP32 built-in WiFi. diff --git a/libraries/WiFiProv/library.properties b/libraries/WiFiProv/library.properties index 4cf8aa56b39..c87cbcd8cee 100644 --- a/libraries/WiFiProv/library.properties +++ b/libraries/WiFiProv/library.properties @@ -1,5 +1,5 @@ name=WiFiProv -version=3.1.1 +version=3.1.2 author=Switi Mhaiske maintainer=Hristo Gochkov sentence=Enables provisioning. diff --git a/libraries/Wire/library.properties b/libraries/Wire/library.properties index 04beb2053bb..a23b33f431d 100644 --- a/libraries/Wire/library.properties +++ b/libraries/Wire/library.properties @@ -1,5 +1,5 @@ name=Wire -version=3.1.1 +version=3.1.2 author=Hristo Gochkov maintainer=Hristo Gochkov sentence=Allows the communication between devices or sensors connected via Two Wire Interface Bus. For esp8266 boards. diff --git a/libraries/Zigbee/library.properties b/libraries/Zigbee/library.properties index 87e0cd63e9f..18fb81548c0 100644 --- a/libraries/Zigbee/library.properties +++ b/libraries/Zigbee/library.properties @@ -1,5 +1,5 @@ name=Zigbee -version=3.1.1 +version=3.1.2 author=P-R-O-C-H-Y maintainer=Jan Procházka sentence=Enables zigbee connection with the ESP32 diff --git a/package.json b/package.json index 742b1154d63..748a2ad1bfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "framework-arduinoespressif32", - "version": "3.1.1", + "version": "3.1.2", "description": "Arduino Wiring-based Framework for the Espressif ESP32, ESP32-P4, ESP32-S and ESP32-C series of SoCs", "keywords": [ "framework", diff --git a/platform.txt b/platform.txt index cc9608a4f9d..2b7d40cff11 100644 --- a/platform.txt +++ b/platform.txt @@ -1,5 +1,5 @@ name=ESP32 Arduino -version=3.1.1 +version=3.1.2 tools.esp32-arduino-libs.path={runtime.platform.path}/tools/esp32-arduino-libs tools.esp32-arduino-libs.path.windows={runtime.platform.path}\tools\esp32-arduino-libs