Merge branch 'master' into release/v3.3.x

This commit is contained in:
Lucas Saavedra Vaz 2025-04-14 14:28:03 -03:00 committed by GitHub
commit ae2ae8dfa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 698 additions and 127 deletions

View file

@ -300,6 +300,7 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS
libraries/Zigbee/src/ep/ZigbeeGateway.cpp libraries/Zigbee/src/ep/ZigbeeGateway.cpp
libraries/Zigbee/src/ep/ZigbeeWindSpeedSensor.cpp libraries/Zigbee/src/ep/ZigbeeWindSpeedSensor.cpp
libraries/Zigbee/src/ep/ZigbeeIlluminanceSensor.cpp libraries/Zigbee/src/ep/ZigbeeIlluminanceSensor.cpp
libraries/Zigbee/src/ep/ZigbeePM25Sensor.cpp
) )
set(ARDUINO_LIBRARY_BLE_SRCS set(ARDUINO_LIBRARY_BLE_SRCS

View file

@ -603,6 +603,7 @@ void HWCDC::setDebugOutput(bool en) {
} else { } else {
ets_install_putc2(NULL); ets_install_putc2(NULL);
} }
ets_install_putc1(NULL); // closes UART log output
} }
#if ARDUINO_USB_MODE && ARDUINO_USB_CDC_ON_BOOT // Hardware JTAG CDC selected #if ARDUINO_USB_MODE && ARDUINO_USB_CDC_ON_BOOT // Hardware JTAG CDC selected

View file

@ -607,6 +607,24 @@ bool HardwareSerial::setMode(SerialMode mode) {
return uartSetMode(_uart, mode); return uartSetMode(_uart, mode);
} }
// Sets the UART Clock Source based on the compatible SoC options
// This method must be called before starting UART using begin(), otherwise it won't have any effect.
// Clock Source Options are:
// UART_CLK_SRC_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source
// UART_CLK_SRC_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3
// UART_CLK_SRC_PLL :: ESP32-C2, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 and ESP32-P4
// UART_CLK_SRC_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_CLK_SRC_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_CLK_SRC_REF_TICK :: ESP32 and ESP32-S2
// Note: CLK_SRC_PLL Freq depends on the SoC - ESP32-C2 has 40MHz, ESP32-H2 has 48MHz and ESP32-C5, C6, C61 and P4 has 80MHz
// Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only RTC_FAST or XTAL/2 as Clock Source
bool HardwareSerial::setClockSource(SerialClkSrc clkSrc) {
if (_uart) {
log_e("No Clock Source change was done. This function must be called before beginning UART%d.", _uart_nr);
return false;
}
return uartSetClockSource(_uart_nr, (uart_sclk_t)clkSrc);
}
// minimum total RX Buffer size is the UART FIFO space (128 bytes for most SoC) + 1. IDF imposition. // minimum total RX Buffer size is the UART FIFO space (128 bytes for most SoC) + 1. IDF imposition.
// LP UART has FIFO of 16 bytes // LP UART has FIFO of 16 bytes
size_t HardwareSerial::setRxBufferSize(size_t new_size) { size_t HardwareSerial::setRxBufferSize(size_t new_size) {

View file

@ -96,6 +96,29 @@ typedef enum {
UART_PARITY_ERROR UART_PARITY_ERROR
} hardwareSerial_error_t; } hardwareSerial_error_t;
typedef enum {
UART_CLK_SRC_DEFAULT = UART_SCLK_DEFAULT,
#if SOC_UART_SUPPORT_APB_CLK
UART_CLK_SRC_APB = UART_SCLK_APB,
#endif
#if SOC_UART_SUPPORT_PLL_F40M_CLK
UART_CLK_SRC_PLL = UART_SCLK_PLL_F40M,
#elif SOC_UART_SUPPORT_PLL_F80M_CLK
UART_CLK_SRC_PLL = UART_SCLK_PLL_F80M,
#elif CONFIG_IDF_TARGET_ESP32H2
UART_CLK_SRC_PLL = UART_SCLK_PLL_F48M,
#endif
#if SOC_UART_SUPPORT_XTAL_CLK
UART_CLK_SRC_XTAL = UART_SCLK_XTAL,
#endif
#if SOC_UART_SUPPORT_RTC_CLK
UART_CLK_SRC_RTC = UART_SCLK_RTC,
#endif
#if SOC_UART_SUPPORT_REF_TICK
UART_CLK_SRC_REF_TICK = UART_SCLK_REF_TICK,
#endif
} SerialClkSrc;
#ifndef ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE #ifndef ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE
#ifndef CONFIG_ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE #ifndef CONFIG_ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE
#define ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE 2048 #define ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE 2048
@ -352,6 +375,17 @@ public:
// UART_MODE_RS485_COLLISION_DETECT = 0x03 mode: RS485 collision detection UART mode (used for test purposes) // UART_MODE_RS485_COLLISION_DETECT = 0x03 mode: RS485 collision detection UART mode (used for test purposes)
// UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes) // UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes)
bool setMode(SerialMode mode); bool setMode(SerialMode mode);
// Used to set the UART clock source mode. It must be set before calling begin(), otherwise it won't have any effect.
// Not all clock source are available to every SoC. The compatible option are listed here:
// UART_CLK_SRC_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source
// UART_CLK_SRC_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3
// UART_CLK_SRC_PLL :: ESP32-C2, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 and ESP32-P4
// UART_CLK_SRC_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_CLK_SRC_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_CLK_SRC_REF_TICK :: ESP32 and ESP32-S2
// Note: CLK_SRC_PLL Freq depends on the SoC - ESP32-C2 has 40MHz, ESP32-H2 has 48MHz and ESP32-C5, C6, C61 and P4 has 80MHz
// Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only RTC_FAST or XTAL/2 as Clock Source
bool setClockSource(SerialClkSrc clkSrc);
size_t setRxBufferSize(size_t new_size); size_t setRxBufferSize(size_t new_size);
size_t setTxBufferSize(size_t new_size); size_t setTxBufferSize(size_t new_size);

View file

@ -100,6 +100,7 @@ static bool tinyusb_device_suspended = false;
void tud_mount_cb(void) { void tud_mount_cb(void) {
tinyusb_device_mounted = true; tinyusb_device_mounted = true;
arduino_usb_event_data_t p; arduino_usb_event_data_t p;
p.suspend.remote_wakeup_en = 0;
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STARTED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STARTED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
} }
@ -107,6 +108,7 @@ void tud_mount_cb(void) {
void tud_umount_cb(void) { void tud_umount_cb(void) {
tinyusb_device_mounted = false; tinyusb_device_mounted = false;
arduino_usb_event_data_t p; arduino_usb_event_data_t p;
p.suspend.remote_wakeup_en = 0;
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
} }
@ -123,6 +125,7 @@ void tud_suspend_cb(bool remote_wakeup_en) {
void tud_resume_cb(void) { void tud_resume_cb(void) {
tinyusb_device_suspended = false; tinyusb_device_suspended = false;
arduino_usb_event_data_t p; arduino_usb_event_data_t p;
p.suspend.remote_wakeup_en = 0;
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_RESUME_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY); arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_RESUME_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
} }

View file

@ -455,6 +455,7 @@ void USBCDC::setDebugOutput(bool en) {
} else { } else {
ets_install_putc2(NULL); ets_install_putc2(NULL);
} }
ets_install_putc1(NULL); // closes UART log output
} }
USBCDC::operator bool() const { USBCDC::operator bool() const {

View file

@ -15,7 +15,7 @@
#include "esp32-hal-bt.h" #include "esp32-hal-bt.h"
#if SOC_BT_SUPPORTED #if SOC_BT_SUPPORTED
#ifdef CONFIG_BT_ENABLED #ifdef CONFIG_BT_BLUEDROID_ENABLED
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
bool btInUse() { bool btInUse() {

View file

@ -25,9 +25,9 @@
#include "esp_ota_ops.h" #include "esp_ota_ops.h"
#endif //CONFIG_APP_ROLLBACK_ENABLE #endif //CONFIG_APP_ROLLBACK_ENABLE
#include "esp_private/startup_internal.h" #include "esp_private/startup_internal.h"
#if defined(CONFIG_BT_ENABLED) && SOC_BT_SUPPORTED #if defined(CONFIG_BT_BLUEDROID_ENABLED) && SOC_BT_SUPPORTED
#include "esp_bt.h" #include "esp_bt.h"
#endif //CONFIG_BT_ENABLED #endif //CONFIG_BT_BLUEDROID_ENABLED
#include <sys/time.h> #include <sys/time.h>
#include "soc/rtc.h" #include "soc/rtc.h"
#if !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) && !defined(CONFIG_IDF_TARGET_ESP32P4) && !defined(CONFIG_IDF_TARGET_ESP32C5) #if !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) && !defined(CONFIG_IDF_TARGET_ESP32P4) && !defined(CONFIG_IDF_TARGET_ESP32C5)
@ -245,7 +245,7 @@ bool verifyRollbackLater() {
} }
#endif #endif
#ifdef CONFIG_BT_ENABLED #ifdef CONFIG_BT_BLUEDROID_ENABLED
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
//overwritten in esp32-hal-bt.c //overwritten in esp32-hal-bt.c
bool btInUse() __attribute__((weak)); bool btInUse() __attribute__((weak));
@ -307,7 +307,7 @@ void initArduino() {
if (err) { if (err) {
log_e("Failed to initialize NVS! Error: %u", err); log_e("Failed to initialize NVS! Error: %u", err);
} }
#if defined(CONFIG_BT_ENABLED) && SOC_BT_SUPPORTED #if defined(CONFIG_BT_BLUEDROID_ENABLED) && SOC_BT_SUPPORTED
if (!btInUse()) { if (!btInUse()) {
esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); esp_bt_controller_mem_release(ESP_BT_MODE_BTDM);
} }

View file

@ -206,7 +206,8 @@ bool rmtSetCarrier(int pin, bool carrier_en, bool carrier_level, uint32_t freque
log_w("GPIO %d - RMT Carrier must be a float percentage from 0 to 1. Setting to 50%.", pin); log_w("GPIO %d - RMT Carrier must be a float percentage from 0 to 1. Setting to 50%.", pin);
duty_percent = 0.5; duty_percent = 0.5;
} }
rmt_carrier_config_t carrier_cfg = {0}; rmt_carrier_config_t carrier_cfg;
memset((void *)&carrier_cfg, 0, sizeof(rmt_carrier_config_t));
carrier_cfg.duty_cycle = duty_percent; // duty cycle carrier_cfg.duty_cycle = duty_percent; // duty cycle
carrier_cfg.frequency_hz = carrier_en ? frequency_Hz : 0; // carrier frequency in Hz carrier_cfg.frequency_hz = carrier_en ? frequency_Hz : 0; // carrier frequency in Hz
carrier_cfg.flags.polarity_active_low = carrier_level; // carrier modulation polarity level carrier_cfg.flags.polarity_active_low = carrier_level; // carrier modulation polarity level
@ -313,7 +314,8 @@ static bool _rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, bool bl
return false; return false;
} }
rmt_transmit_config_t transmit_cfg = {0}; // loop mode disabled rmt_transmit_config_t transmit_cfg; // loop mode disabled
memset((void *)&transmit_cfg, 0, sizeof(rmt_transmit_config_t));
bool retCode = true; bool retCode = true;
RMT_MUTEX_LOCK(bus); RMT_MUTEX_LOCK(bus);
@ -380,6 +382,7 @@ static bool _rmtRead(int pin, rmt_data_t *data, size_t *num_rmt_symbols, bool wa
// request reading RMT Channel Data // request reading RMT Channel Data
rmt_receive_config_t receive_config; rmt_receive_config_t receive_config;
memset((void *)&receive_config, 0, sizeof(rmt_receive_config_t));
receive_config.signal_range_min_ns = bus->signal_range_min_ns; receive_config.signal_range_min_ns = bus->signal_range_min_ns;
receive_config.signal_range_max_ns = bus->signal_range_max_ns; receive_config.signal_range_max_ns = bus->signal_range_max_ns;
@ -530,6 +533,7 @@ bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_
if (channel_direction == RMT_TX_MODE) { if (channel_direction == RMT_TX_MODE) {
// TX Channel // TX Channel
rmt_tx_channel_config_t tx_cfg; rmt_tx_channel_config_t tx_cfg;
memset((void *)&tx_cfg, 0, sizeof(rmt_tx_channel_config_t));
tx_cfg.gpio_num = pin; tx_cfg.gpio_num = pin;
// CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2 // CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
tx_cfg.clk_src = RMT_CLK_SRC_DEFAULT; tx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
@ -559,6 +563,7 @@ bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_
} else { } else {
// RX Channel // RX Channel
rmt_rx_channel_config_t rx_cfg; rmt_rx_channel_config_t rx_cfg;
memset((void *)&rx_cfg, 0, sizeof(rmt_rx_channel_config_t));
rx_cfg.gpio_num = pin; rx_cfg.gpio_num = pin;
// CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2 // CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
rx_cfg.clk_src = RMT_CLK_SRC_DEFAULT; rx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
@ -585,7 +590,8 @@ bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_
} }
// allocate memory for the RMT Copy encoder // allocate memory for the RMT Copy encoder
rmt_copy_encoder_config_t copy_encoder_config = {}; rmt_copy_encoder_config_t copy_encoder_config;
memset((void *)&copy_encoder_config, 0, sizeof(rmt_copy_encoder_config_t));
if (rmt_new_copy_encoder(&copy_encoder_config, &bus->rmt_copy_encoder_h) != ESP_OK) { if (rmt_new_copy_encoder(&copy_encoder_config, &bus->rmt_copy_encoder_h) != ESP_OK) {
log_e("GPIO %d - RMT Encoder Memory Allocation error.", pin); log_e("GPIO %d - RMT Encoder Memory Allocation error.", pin);
goto Err; goto Err;

View file

@ -58,6 +58,7 @@ struct uart_struct_t {
uint16_t _rx_buffer_size, _tx_buffer_size; // UART RX and TX buffer sizes uint16_t _rx_buffer_size, _tx_buffer_size; // UART RX and TX buffer sizes
bool _inverted; // UART inverted signal bool _inverted; // UART inverted signal
uint8_t _rxfifo_full_thrhd; // UART RX FIFO full threshold uint8_t _rxfifo_full_thrhd; // UART RX FIFO full threshold
int8_t _uart_clock_source; // UART Clock Source used when it is started using uartBegin()
}; };
#if CONFIG_DISABLE_HAL_LOCKS #if CONFIG_DISABLE_HAL_LOCKS
@ -66,21 +67,21 @@ struct uart_struct_t {
#define UART_MUTEX_UNLOCK() #define UART_MUTEX_UNLOCK()
static uart_t _uart_bus_array[] = { static uart_t _uart_bus_array[] = {
{0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#if SOC_UART_NUM > 1 #if SOC_UART_NUM > 1
{1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 2 #if SOC_UART_NUM > 2
{2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 3 #if SOC_UART_NUM > 3
{3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 4 #if SOC_UART_NUM > 4
{4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 5 #if SOC_UART_NUM > 5
{5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
}; };
@ -95,21 +96,21 @@ static uart_t _uart_bus_array[] = {
xSemaphoreGive(uart->lock) xSemaphoreGive(uart->lock)
static uart_t _uart_bus_array[] = { static uart_t _uart_bus_array[] = {
{NULL, 0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 0, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#if SOC_UART_NUM > 1 #if SOC_UART_NUM > 1
{NULL, 1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 1, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 2 #if SOC_UART_NUM > 2
{NULL, 2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 2, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 3 #if SOC_UART_NUM > 3
{NULL, 3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 3, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 4 #if SOC_UART_NUM > 4
{NULL, 4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 4, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
#if SOC_UART_NUM > 5 #if SOC_UART_NUM > 5
{NULL, 5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0}, {NULL, 5, false, 0, NULL, -1, -1, -1, -1, 0, 0, 0, 0, false, 0, -1},
#endif #endif
}; };
@ -676,30 +677,40 @@ uart_t *uartBegin(
rxfifo_full_thrhd = uart_config.rx_flow_ctrl_thresh; // makes sure that it will be set correctly in the struct rxfifo_full_thrhd = uart_config.rx_flow_ctrl_thresh; // makes sure that it will be set correctly in the struct
uart_config.baud_rate = baudrate; uart_config.baud_rate = baudrate;
#if SOC_UART_LP_NUM >= 1 #if SOC_UART_LP_NUM >= 1
if (uart_nr >= SOC_UART_HP_NUM) { // it is a LP UART NUM if (uart_nr >= SOC_UART_HP_NUM) { // it is a LP UART NUM
uart_config.lp_source_clk = LP_UART_SCLK_DEFAULT; // use default LP clock if (uart->_uart_clock_source > 0) {
log_v("Setting UART%d to use LP clock", uart_nr); uart_config.lp_source_clk = (soc_periph_lp_uart_clk_src_t)uart->_uart_clock_source; // use user defined LP UART clock
} else log_v("Setting UART%d to user defined LP clock source (%d) ", uart_nr, uart->_uart_clock_source);
#endif
{
// there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored
// therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue.
#if SOC_UART_SUPPORT_XTAL_CLK
uart_config.source_clk = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4
log_v("Setting UART%d to use XTAL clock", uart_nr);
#elif SOC_UART_SUPPORT_REF_TICK
if (baudrate <= REF_TICK_BAUDRATE_LIMIT) {
uart_config.source_clk = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps
log_v("Setting UART%d to use REF_TICK clock", uart_nr);
} else { } else {
uart_config.source_clk = UART_SCLK_APB; // baudrate may change with the APB Frequency! uart_config.lp_source_clk = LP_UART_SCLK_DEFAULT; // use default LP clock
log_v("Setting UART%d to use APB clock", uart_nr); log_v("Setting UART%d to Default LP clock source", uart_nr);
} }
} else
#endif // SOC_UART_LP_NUM >= 1
{
if (uart->_uart_clock_source >= 0) {
uart_config.source_clk = (soc_module_clk_t)uart->_uart_clock_source; // use user defined HP UART clock
log_v("Setting UART%d to user defined HP clock source (%d) ", uart_nr, uart->_uart_clock_source);
} else {
// there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored
// therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue.
#if SOC_UART_SUPPORT_XTAL_CLK
uart_config.source_clk = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4
log_v("Setting UART%d to use XTAL clock", uart_nr);
#elif SOC_UART_SUPPORT_REF_TICK
if (baudrate <= REF_TICK_BAUDRATE_LIMIT) {
uart_config.source_clk = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps
log_v("Setting UART%d to use REF_TICK clock", uart_nr);
} else {
uart_config.source_clk = UART_SCLK_APB; // baudrate may change with the APB Frequency!
log_v("Setting UART%d to use APB clock", uart_nr);
}
#else #else
// Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6 // Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6|P4
uart_config.source_clk = UART_SCLK_DEFAULT; // baudrate may change with the APB Frequency! uart_config.source_clk = UART_SCLK_DEFAULT; // baudrate may change with the APB Frequency!
log_v("Setting UART%d to use DEFAULT clock", uart_nr); log_v("Setting UART%d to use DEFAULT clock", uart_nr);
#endif #endif // SOC_UART_SUPPORT_XTAL_CLK
}
} }
UART_MUTEX_LOCK(); UART_MUTEX_LOCK();
@ -728,6 +739,14 @@ uart_t *uartBegin(
uart->_tx_buffer_size = tx_buffer_size; uart->_tx_buffer_size = tx_buffer_size;
uart->has_peek = false; uart->has_peek = false;
uart->peek_byte = 0; uart->peek_byte = 0;
#if SOC_UART_LP_NUM >= 1
if (uart_nr >= SOC_UART_HP_NUM) {
uart->_uart_clock_source = uart_config.lp_source_clk;
} else
#endif
{
uart->_uart_clock_source = uart_config.source_clk;
}
} }
UART_MUTEX_UNLOCK(); UART_MUTEX_UNLOCK();
@ -987,22 +1006,52 @@ bool uartSetBaudRate(uart_t *uart, uint32_t baud_rate) {
return false; return false;
} }
bool retCode = true; bool retCode = true;
UART_MUTEX_LOCK(); soc_module_clk_t newClkSrc = UART_SCLK_DEFAULT;
#if SOC_UART_SUPPORT_XTAL_CLK // ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-H2 and ESP32-P4 int8_t previousClkSrc = uart->_uart_clock_source;
soc_module_clk_t newClkSrc = UART_SCLK_XTAL;
#if SOC_UART_LP_NUM >= 1 #if SOC_UART_LP_NUM >= 1
if (uart->num >= SOC_UART_HP_NUM) { // it is a LP UART NUM if (uart->num >= SOC_UART_HP_NUM) { // it is a LP UART NUM
newClkSrc = LP_UART_SCLK_DEFAULT; // use default LP clock if (uart->_uart_clock_source > 0) {
newClkSrc = (soc_periph_lp_uart_clk_src_t)uart->_uart_clock_source; // use user defined LP UART clock
log_v("Setting UART%d to user defined LP clock source (%d) ", uart->num, newClkSrc);
} else {
newClkSrc = LP_UART_SCLK_DEFAULT; // use default LP clock
log_v("Setting UART%d to Default LP clock source", uart->num);
}
} else
#endif // SOC_UART_LP_NUM >= 1
{
if (uart->_uart_clock_source >= 0) {
newClkSrc = (soc_module_clk_t)uart->_uart_clock_source; // use user defined HP UART clock
log_v("Setting UART%d to use HP clock source (%d) ", uart->num, newClkSrc);
} else {
// there is an issue when returning from light sleep with the C6 and H2: the uart baud rate is not restored
// therefore, uart clock source will set to XTAL for all SoC that support it. This fix solves the C6|H2 issue.
#if SOC_UART_SUPPORT_XTAL_CLK
newClkSrc = UART_SCLK_XTAL; // valid for C2, S3, C3, C6, H2 and P4
log_v("Setting UART%d to use XTAL clock", uart->num);
#elif SOC_UART_SUPPORT_REF_TICK
if (baud_rate <= REF_TICK_BAUDRATE_LIMIT) {
newClkSrc = UART_SCLK_REF_TICK; // valid for ESP32, S2 - MAX supported baud rate is 250 Kbps
log_v("Setting UART%d to use REF_TICK clock", uart->num);
} else {
newClkSrc = UART_SCLK_APB; // baudrate may change with the APB Frequency!
log_v("Setting UART%d to use APB clock", uart->num);
}
#else
// Default CLK Source: CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F40M for C2 -- CLK_PLL_F48M for H2 -- CLK_PLL_F80M for C6|P4
// using newClkSrc = UART_SCLK_DEFAULT as defined in the variable declaration
log_v("Setting UART%d to use DEFAULT clock", uart->num);
#endif // SOC_UART_SUPPORT_XTAL_CLK
}
} }
#endif UART_MUTEX_LOCK();
// ESP32-P4 demands an atomic operation for setting the clock source // if necessary, set the correct UART Clock Source before changing the baudrate
HP_UART_SRC_CLK_ATOMIC() { if (previousClkSrc < 0 || previousClkSrc != newClkSrc) {
uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc); HP_UART_SRC_CLK_ATOMIC() {
uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc);
}
uart->_uart_clock_source = newClkSrc;
} }
#else // ESP32, ESP32-S2
soc_module_clk_t newClkSrc = baud_rate <= REF_TICK_BAUDRATE_LIMIT ? SOC_MOD_CLK_REF_TICK : SOC_MOD_CLK_APB;
uart_ll_set_sclk(UART_LL_GET_HW(uart->num), newClkSrc);
#endif
if (uart_set_baudrate(uart->num, baud_rate) == ESP_OK) { if (uart_set_baudrate(uart->num, baud_rate) == ESP_OK) {
log_v("Setting UART%d baud rate to %ld.", uart->num, baud_rate); log_v("Setting UART%d baud rate to %ld.", uart->num, baud_rate);
uart->_baudrate = baud_rate; uart->_baudrate = baud_rate;
@ -1096,6 +1145,31 @@ bool uartSetMode(uart_t *uart, uart_mode_t mode) {
return retCode; return retCode;
} }
// this function will set the uart clock source
// it must be called before uartBegin(), otherwise it won't change any thing.
bool uartSetClockSource(uint8_t uartNum, uart_sclk_t clkSrc) {
if (uartNum >= SOC_UART_NUM) {
log_e("UART%d is invalid. This device has %d UARTs, from 0 to %d.", uartNum, SOC_UART_NUM, SOC_UART_NUM - 1);
return false;
}
uart_t *uart = &_uart_bus_array[uartNum];
#if SOC_UART_LP_NUM >= 1
if (uart->num >= SOC_UART_HP_NUM) {
switch (clkSrc) {
case UART_SCLK_XTAL: uart->_uart_clock_source = LP_UART_SCLK_XTAL_D2; break;
case UART_SCLK_RTC: uart->_uart_clock_source = LP_UART_SCLK_LP_FAST; break;
case UART_SCLK_DEFAULT:
default: uart->_uart_clock_source = LP_UART_SCLK_DEFAULT;
}
} else
#endif
{
uart->_uart_clock_source = clkSrc;
}
//log_i("UART%d set clock source to %d", uart->num, uart->_uart_clock_source);
return true;
}
void uartSetDebug(uart_t *uart) { void uartSetDebug(uart_t *uart) {
// LP UART is not supported for debug // LP UART is not supported for debug
if (uart == NULL || uart->num >= SOC_UART_HP_NUM) { if (uart == NULL || uart->num >= SOC_UART_HP_NUM) {
@ -1124,7 +1198,7 @@ int log_printfv(const char *format, va_list arg) {
return 0; return 0;
} }
} }
/* /*
// This causes dead locks with logging in specific cases and also with C++ constructors that may send logs // This causes dead locks with logging in specific cases and also with C++ constructors that may send logs
#if !CONFIG_DISABLE_HAL_LOCKS #if !CONFIG_DISABLE_HAL_LOCKS
if(s_uart_debug_nr != -1 && _uart_bus_array[s_uart_debug_nr].lock){ if(s_uart_debug_nr != -1 && _uart_bus_array[s_uart_debug_nr].lock){
@ -1132,16 +1206,8 @@ int log_printfv(const char *format, va_list arg) {
} }
#endif #endif
*/ */
#if (ARDUINO_USB_CDC_ON_BOOT == 1 && ARDUINO_USB_MODE == 0) || CONFIG_IDF_TARGET_ESP32C3 \
|| ((CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4) && ARDUINO_USB_CDC_ON_BOOT == 1)
vsnprintf(temp, len + 1, format, arg); vsnprintf(temp, len + 1, format, arg);
ets_printf("%s", temp); ets_printf("%s", temp);
#else
int wlen = vsnprintf(temp, len + 1, format, arg);
for (int i = 0; i < wlen; i++) {
ets_write_char_uart(temp[i]);
}
#endif
/* /*
// This causes dead locks with logging and also with constructors that may send logs // This causes dead locks with logging and also with constructors that may send logs
#if !CONFIG_DISABLE_HAL_LOCKS #if !CONFIG_DISABLE_HAL_LOCKS

View file

@ -97,6 +97,19 @@ bool uartSetHwFlowCtrlMode(uart_t *uart, uart_hw_flowcontrol_t mode, uint8_t thr
// UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes) // UART_MODE_RS485_APP_CTRL = 0x04 mode: application control RS485 UART mode (used for test purposes)
bool uartSetMode(uart_t *uart, uart_mode_t mode); bool uartSetMode(uart_t *uart, uart_mode_t mode);
// Used to set the UART clock source mode. It must be set before calling uartBegin(), otherwise it won't have any effect.
// Not all clock source are available to every SoC. The compatible option are listed here:
// UART_SCLK_DEFAULT :: any SoC - it will set whatever IDF defines as the default UART Clock Source
// UART_SCLK_APB :: ESP32, ESP32-S2, ESP32-C3 and ESP32-S3
// UART_SCLK_PLL_F80M :: ESP32-C5, ESP32-C6, ESP32-C61 and ESP32-P4
// UART_SCLK_PLL_F40M :: ESP32-C2
// UART_SCLK_PLL_F48M :: ESP32-H2
// UART_SCLK_XTAL :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_SCLK_RTC :: ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, ESP32-S3 and ESP32-P4
// UART_SCLK_REF_TICK :: ESP32 and ESP32-S2
// Note: ESP32-C6, C61, ESP32-P4 and ESP32-C5 have LP UART that will use only LP_UART_SCLK_LP_FAST (RTC_FAST) or LP_UART_SCLK_XTAL_D2 (XTAL/2) as Clock Source
bool uartSetClockSource(uint8_t uartNum, uart_sclk_t clkSrc);
void uartStartDetectBaudrate(uart_t *uart); void uartStartDetectBaudrate(uart_t *uart);
unsigned long uartDetectBaudrate(uart_t *uart); unsigned long uartDetectBaudrate(uart_t *uart);

View file

@ -1,4 +1,4 @@
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD // Copyright 2025 Espressif Systems (Shanghai) PTE LTD
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -35,14 +35,11 @@
rmt_data_t my_data[256]; rmt_data_t my_data[256];
rmt_data_t data[256]; rmt_data_t data[256];
static EventGroupHandle_t events;
#define RMT_FREQ 10000000 // tick time is 100ns #define RMT_FREQ 10000000 // tick time is 100ns
#define RMT_NUM_EXCHANGED_DATA 30 #define RMT_NUM_EXCHANGED_DATA 32
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
events = xEventGroupCreate();
if (!rmtInit(RMT_TX_PIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ)) { if (!rmtInit(RMT_TX_PIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ)) {
Serial.println("init sender failed\n"); Serial.println("init sender failed\n");
@ -50,25 +47,41 @@ void setup() {
if (!rmtInit(RMT_RX_PIN, RMT_RX_MODE, RMT_MEM_RX, RMT_FREQ)) { if (!rmtInit(RMT_RX_PIN, RMT_RX_MODE, RMT_MEM_RX, RMT_FREQ)) {
Serial.println("init receiver failed\n"); Serial.println("init receiver failed\n");
} }
Serial.println();
Serial.println("RMT tick set to: 100ns");
// End of transmission shall be detected when line is idle for 2us = 20*100ns // End of transmission shall be detected when line is idle for 2us = 20*100ns
rmtSetRxMaxThreshold(RMT_RX_PIN, 20); rmtSetRxMaxThreshold(RMT_RX_PIN, 20);
// Disable Glitch filter // Disable Glitch filter
rmtSetRxMinThreshold(RMT_RX_PIN, 0); rmtSetRxMinThreshold(RMT_RX_PIN, 0);
Serial.println("real tick set to: 100ns"); // create multiple pulses with different width to be sent
Serial.printf("\nPlease connect GPIO %d to GPIO %d, now.\n", RMT_TX_PIN, RMT_RX_PIN); for (int i = 0; i < 255; i++) {
} data[i].level0 = 1; // HIGH
data[i].duration0 = 1 + 13 - (i % 13); // number of Tick on High
void loop() { data[i].level1 = 0; // LOW
// Init data data[i].duration1 = 1 + (i % 13); // number of Ticks on Low
int i;
for (i = 0; i < 255; i++) {
data[i].val = 0x80010001 + ((i % 13) << 16) + 13 - (i % 13);
my_data[i].val = 0; my_data[i].val = 0;
} }
data[255].val = 0; data[255].val = 0;
Serial.println();
Serial.println("====================================================================================================");
Serial.println("Preloaded Data that will sent (time in 0.1us):");
// Printout the received data plus the original values
for (int i = 0; i < RMT_NUM_EXCHANGED_DATA; i++) {
Serial.printf("%08lx=[%c 0x%02x|%c 0x%02x] ", data[i].val, data[i].level0 ? 'H' : 'L', data[i].duration0, data[i].level1 ? 'H' : 'L', data[i].duration1);
if (!((i + 1) % 4)) {
Serial.println();
}
}
Serial.println("====================================================================================================");
Serial.printf("Please connect GPIO %d to GPIO %d, now.", RMT_TX_PIN, RMT_RX_PIN);
Serial.println();
Serial.println();
}
void loop() {
// Start an async data read // Start an async data read
size_t rx_num_symbols = RMT_NUM_EXCHANGED_DATA; size_t rx_num_symbols = RMT_NUM_EXCHANGED_DATA;
rmtReadAsync(RMT_RX_PIN, my_data, &rx_num_symbols); rmtReadAsync(RMT_RX_PIN, my_data, &rx_num_symbols);
@ -84,13 +97,13 @@ void loop() {
Serial.printf("Got %d RMT symbols\n", rx_num_symbols); Serial.printf("Got %d RMT symbols\n", rx_num_symbols);
// Printout the received data plus the original values // Printout the received data plus the original values
for (i = 0; i < 60; i++) { for (int i = 0; i < RMT_NUM_EXCHANGED_DATA; i++) {
Serial.printf("%08lx=%08lx ", my_data[i].val, data[i].val); Serial.printf("%08lx=%08lx ", my_data[i].val, data[i].val);
if (!((i + 1) % 4)) { if (!((i + 1) % 4)) {
Serial.println(""); Serial.println();
} }
} }
Serial.println("\n"); Serial.println();
delay(500); delay(2000);
} }

View file

@ -37,10 +37,14 @@
#define CONSUMER_CONTROL_WIRELESS_RADIO_SLIDER_SWITCH 0x00C8 #define CONSUMER_CONTROL_WIRELESS_RADIO_SLIDER_SWITCH 0x00C8
// Media Control // Media Control
#define CONSUMER_CONTROL_PLAY_PAUSE 0x00CD #define CONSUMER_CONTROL_RECORD 0x00B2
#define CONSUMER_CONTROL_FAST_FORWARD 0x00B3
#define CONSUMER_CONTROL_REWIND 0x00B4
#define CONSUMER_CONTROL_SCAN_NEXT 0x00B5 #define CONSUMER_CONTROL_SCAN_NEXT 0x00B5
#define CONSUMER_CONTROL_SCAN_PREVIOUS 0x00B6 #define CONSUMER_CONTROL_SCAN_PREVIOUS 0x00B6
#define CONSUMER_CONTROL_STOP 0x00B7 #define CONSUMER_CONTROL_STOP 0x00B7
#define CONSUMER_CONTROL_EJECT 0x00B8
#define CONSUMER_CONTROL_PLAY_PAUSE 0x00CD
#define CONSUMER_CONTROL_VOLUME 0x00E0 #define CONSUMER_CONTROL_VOLUME 0x00E0
#define CONSUMER_CONTROL_MUTE 0x00E2 #define CONSUMER_CONTROL_MUTE 0x00E2
#define CONSUMER_CONTROL_BASS 0x00E3 #define CONSUMER_CONTROL_BASS 0x00E3

View file

@ -606,9 +606,9 @@ bool WiFiGenericClass::mode(wifi_mode_t m) {
#else #else
#define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N) #define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)
#endif #endif
uint8_t current_protocol = 0; uint32_t current_protocol = 0;
if (m & WIFI_MODE_STA) { if (m & WIFI_MODE_STA) {
err = esp_wifi_get_protocol(WIFI_IF_STA, &current_protocol); err = esp_wifi_get_protocol(WIFI_IF_STA, (uint8_t *)&current_protocol);
if (err == ESP_OK && current_protocol == WIFI_PROTOCOL_LR) { if (err == ESP_OK && current_protocol == WIFI_PROTOCOL_LR) {
log_v("Disabling long range on STA"); log_v("Disabling long range on STA");
err = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_DEFAULT); err = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_DEFAULT);
@ -618,7 +618,7 @@ bool WiFiGenericClass::mode(wifi_mode_t m) {
} }
} }
if (m & WIFI_MODE_AP) { if (m & WIFI_MODE_AP) {
err = esp_wifi_get_protocol(WIFI_IF_AP, &current_protocol); err = esp_wifi_get_protocol(WIFI_IF_AP, (uint8_t *)&current_protocol);
if (err == ESP_OK && current_protocol == WIFI_PROTOCOL_LR) { if (err == ESP_OK && current_protocol == WIFI_PROTOCOL_LR) {
log_v("Disabling long range on AP"); log_v("Disabling long range on AP");
err = esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_DEFAULT); err = esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_DEFAULT);

View file

@ -0,0 +1,72 @@
# Arduino-ESP32 PM2.5 Sensor
This example shows how to configure the Zigbee end device and use it as a Home Automation (HA) simple sensor device type with particulate matter (PM2.5) measuring
# Supported Targets
Currently, this example supports the following targets.
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
## Pressure + Flow Sensor Functions
* After this board first starts up, it would be configured locally to report the PM2.5 on every 30 seconds.
* By clicking the button (BOOT) on this board, this board will immediately send a report of the current PM2.5 to the network.
## Hardware Required
* A USB cable for power supply and programming
### Configure the Project
In this example, the internal temperature sensor is used to demonstrate reading of the PM2.5 sensors.
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).
#### 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)

View file

@ -0,0 +1,109 @@
// 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 PM2.5 sensor.
*
* The example demonstrates how to use Zigbee library to create a end device PM2.5 sensor.
*
* 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 PM2.5 sensor configuration */
#define PM2_5_SENSOR_ENDPOINT_NUMBER 1
uint8_t button = BOOT_PIN;
ZigbeePM25Sensor zbPM25Sensor = ZigbeePM25Sensor(PM2_5_SENSOR_ENDPOINT_NUMBER);
void setup() {
Serial.begin(115200);
// Init button switch
pinMode(button, INPUT_PULLUP);
// Optional: set Zigbee device name and model
zbPM25Sensor.setManufacturerAndModel("Espressif", "ZigbeePM25Sensor");
// Set minimum and maximum PM2.5 measurement value in µg/m³
zbPM25Sensor.setMinMaxValue(0, 350);
// Set tolerance for PM2.5 measurement in µg/m³
zbPM25Sensor.setTolerance(0.1);
// Add endpoints to Zigbee Core
Zigbee.addEndpoint(&zbPM25Sensor);
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();
// Set reporting interval for PM2.5 measurement to be done every 30 seconds, must be called after Zigbee.begin()
// min_interval and max_interval in seconds, delta (PM2.5 change in µg/m³)
// if min = 1 and max = 0, reporting is sent only when PM2.5 changes by delta
// if min = 0 and max = 10, reporting is sent every 10 seconds or when PM2.5 changes by delta
// if min = 0, max = 10 and delta = 0, reporting is sent every 10 seconds regardless of delta change
zbPM25Sensor.setReporting(0, 30, 0);
}
void loop() {
static uint32_t timeCounter = 0;
// Read PM2.5 sensor every 2s
if (!(timeCounter++ % 20)) { // delaying for 100ms x 20 = 2s
// Read sensor value - here is chip temperature used + 50 as a dummy value for demonstration
float pm25_value = 50.5 + temperatureRead();
Serial.printf("Updating PM2.5 sensor value to %0.1f µg/m³\r\n", pm25_value);
zbPM25Sensor.setPM25(pm25_value);
}
// Checking button for factory reset and reporting
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();
}
}
zbPM25Sensor.report();
}
delay(100);
}

View file

@ -0,0 +1,7 @@
{
"fqbn_append": "PartitionScheme=zigbee,ZigbeeMode=ed",
"requires": [
"CONFIG_SOC_IEEE802154_SUPPORTED=y",
"CONFIG_ZB_ENABLED=y"
]
}

View file

@ -85,9 +85,9 @@ void setup() {
// Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C) // Set tolerance for temperature measurement in °C (lowest possible value is 0.01°C)
zbTempSensor.setTolerance(1); zbTempSensor.setTolerance(1);
// Set power source to battery and set battery percentage to measured value (now 100% for demonstration) // Set power source to battery, battery percentage and battery voltage (now 100% and 3.5V for demonstration)
// The value can be also updated by calling zbTempSensor.setBatteryPercentage(percentage) anytime // The value can be also updated by calling zbTempSensor.setBatteryPercentage(percentage) or zbTempSensor.setBatteryVoltage(voltage) anytime after Zigbee.begin()
zbTempSensor.setPowerSource(ZB_POWER_SOURCE_BATTERY, 100); zbTempSensor.setPowerSource(ZB_POWER_SOURCE_BATTERY, 100, 35);
// Add humidity cluster to the temperature sensor device with min, max and tolerance values // Add humidity cluster to the temperature sensor device with min, max and tolerance values
zbTempSensor.addHumiditySensor(0, 100, 1); zbTempSensor.addHumiditySensor(0, 100, 1);

View file

@ -7,23 +7,29 @@
#include "ZigbeeEP.h" #include "ZigbeeEP.h"
// Endpoints // Endpoints
#include "ep/ZigbeeLight.h" //// Switches
#include "ep/ZigbeeSwitch.h"
#include "ep/ZigbeeDimmableLight.h"
#include "ep/ZigbeeColorDimmableLight.h"
#include "ep/ZigbeeColorDimmerSwitch.h" #include "ep/ZigbeeColorDimmerSwitch.h"
#include "ep/ZigbeeTempSensor.h" #include "ep/ZigbeeSwitch.h"
//// Lights
#include "ep/ZigbeeColorDimmableLight.h"
#include "ep/ZigbeeDimmableLight.h"
#include "ep/ZigbeeLight.h"
//// Controllers
#include "ep/ZigbeeThermostat.h" #include "ep/ZigbeeThermostat.h"
#include "ep/ZigbeePressureSensor.h" //// Sensors
#include "ep/ZigbeeAnalog.h" #include "ep/ZigbeeAnalog.h"
#include "ep/ZigbeeFlowSensor.h"
#include "ep/ZigbeeOccupancySensor.h"
#include "ep/ZigbeeIlluminanceSensor.h"
#include "ep/ZigbeeCarbonDioxideSensor.h" #include "ep/ZigbeeCarbonDioxideSensor.h"
#include "ep/ZigbeeContactSwitch.h" #include "ep/ZigbeeContactSwitch.h"
#include "ep/ZigbeeDoorWindowHandle.h" #include "ep/ZigbeeDoorWindowHandle.h"
#include "ep/ZigbeeWindowCovering.h" #include "ep/ZigbeeFlowSensor.h"
#include "ep/ZigbeeIlluminanceSensor.h"
#include "ep/ZigbeeOccupancySensor.h"
#include "ep/ZigbeePM25Sensor.h"
#include "ep/ZigbeePressureSensor.h"
#include "ep/ZigbeeTempSensor.h"
#include "ep/ZigbeeVibrationSensor.h" #include "ep/ZigbeeVibrationSensor.h"
#include "ep/ZigbeeRangeExtender.h"
#include "ep/ZigbeeGateway.h"
#include "ep/ZigbeeWindSpeedSensor.h" #include "ep/ZigbeeWindSpeedSensor.h"
#include "ep/ZigbeeWindowCovering.h"
//// Other
#include "ep/ZigbeeGateway.h"
#include "ep/ZigbeeRangeExtender.h"

View file

@ -19,6 +19,8 @@ ZigbeeEP::ZigbeeEP(uint8_t endpoint) {
_ep_config.endpoint = 0; _ep_config.endpoint = 0;
_cluster_list = nullptr; _cluster_list = nullptr;
_on_identify = nullptr; _on_identify = nullptr;
_read_model = NULL;
_read_manufacturer = NULL;
_time_status = 0; _time_status = 0;
if (!lock) { if (!lock) {
lock = xSemaphoreCreateBinary(); lock = xSemaphoreCreateBinary();
@ -33,16 +35,23 @@ void ZigbeeEP::setVersion(uint8_t version) {
} }
bool ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) { bool ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) {
// Allocate a new array of size length + 2 (1 for the length, 1 for null terminator)
char zb_name[ZB_MAX_NAME_LENGTH + 2];
char zb_model[ZB_MAX_NAME_LENGTH + 2];
// Convert manufacturer to ZCL string // Convert manufacturer to ZCL string
size_t name_length = strlen(name); size_t name_length = strlen(name);
size_t model_length = strlen(model); size_t model_length = strlen(model);
if (name_length > 32 || model_length > 32) { if (name_length > ZB_MAX_NAME_LENGTH || model_length > ZB_MAX_NAME_LENGTH) {
log_e("Manufacturer or model name is too long"); log_e("Manufacturer or model name is too long");
return false; return false;
} }
// Allocate a new array of size length + 2 (1 for the length, 1 for null terminator) // Get and check the basic cluster
char *zb_name = new char[name_length + 2]; esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
char *zb_model = new char[model_length + 2]; if (basic_cluster == nullptr) {
log_e("Failed to get basic cluster");
return false;
}
// Store the length as the first element // Store the length as the first element
zb_name[0] = static_cast<char>(name_length); // Cast size_t to char zb_name[0] = static_cast<char>(name_length); // Cast size_t to char
zb_model[0] = static_cast<char>(model_length); zb_model[0] = static_cast<char>(model_length);
@ -52,9 +61,7 @@ bool ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) {
// Null-terminate the array // Null-terminate the array
zb_name[name_length + 1] = '\0'; zb_name[name_length + 1] = '\0';
zb_model[model_length + 1] = '\0'; zb_model[model_length + 1] = '\0';
// Update the manufacturer and model attributes
// Get the basic cluster and update the manufacturer and model attributes
esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_err_t ret_name = esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)zb_name); esp_err_t ret_name = esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)zb_name);
if (ret_name != ESP_OK) { if (ret_name != ESP_OK) {
log_e("Failed to set manufacturer: 0x%x: %s", ret_name, esp_err_to_name(ret_name)); log_e("Failed to set manufacturer: 0x%x: %s", ret_name, esp_err_to_name(ret_name));
@ -63,12 +70,10 @@ bool ZigbeeEP::setManufacturerAndModel(const char *name, const char *model) {
if (ret_model != ESP_OK) { if (ret_model != ESP_OK) {
log_e("Failed to set model: 0x%x: %s", ret_model, esp_err_to_name(ret_model)); log_e("Failed to set model: 0x%x: %s", ret_model, esp_err_to_name(ret_model));
} }
delete[] zb_name;
delete[] zb_model;
return ret_name == ESP_OK && ret_model == ESP_OK; return ret_name == ESP_OK && ret_model == ESP_OK;
} }
bool ZigbeeEP::setPowerSource(zb_power_source_t power_source, uint8_t battery_percentage) { bool ZigbeeEP::setPowerSource(zb_power_source_t power_source, uint8_t battery_percentage, uint8_t battery_voltage) {
esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_attribute_list_t *basic_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_err_t ret = esp_zb_cluster_update_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, (void *)&power_source); esp_err_t ret = esp_zb_cluster_update_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, (void *)&power_source);
if (ret != ESP_OK) { if (ret != ESP_OK) {
@ -88,6 +93,11 @@ bool ZigbeeEP::setPowerSource(zb_power_source_t power_source, uint8_t battery_pe
log_e("Failed to add battery percentage attribute: 0x%x: %s", ret, esp_err_to_name(ret)); log_e("Failed to add battery percentage attribute: 0x%x: %s", ret, esp_err_to_name(ret));
return false; return false;
} }
ret = esp_zb_power_config_cluster_add_attr(power_config_cluster, ESP_ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID, (void *)&battery_voltage);
if (ret != ESP_OK) {
log_e("Failed to add battery voltage attribute: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_cluster_list_add_power_config_cluster(_cluster_list, power_config_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); ret = esp_zb_cluster_list_add_power_config_cluster(_cluster_list, power_config_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
if (ret != ESP_OK) { if (ret != ESP_OK) {
log_e("Failed to add power config cluster: 0x%x: %s", ret, esp_err_to_name(ret)); log_e("Failed to add power config cluster: 0x%x: %s", ret, esp_err_to_name(ret));
@ -120,6 +130,21 @@ bool ZigbeeEP::setBatteryPercentage(uint8_t percentage) {
return true; return true;
} }
bool ZigbeeEP::setBatteryVoltage(uint8_t voltage) {
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
esp_zb_lock_acquire(portMAX_DELAY);
ret = esp_zb_zcl_set_attribute_val(
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID, &voltage, false
);
esp_zb_lock_release();
if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) {
log_e("Failed to set battery voltage: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret));
return false;
}
log_v("Battery voltage updated");
return true;
}
bool ZigbeeEP::reportBatteryPercentage() { bool ZigbeeEP::reportBatteryPercentage() {
/* Send report attributes command */ /* Send report attributes command */
esp_zb_zcl_report_attr_cmd_t report_attr_cmd; esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
@ -163,10 +188,10 @@ char *ZigbeeEP::readManufacturer(uint8_t endpoint, uint16_t short_addr, esp_zb_i
read_req.attr_number = ZB_ARRAY_LENTH(attributes); read_req.attr_number = ZB_ARRAY_LENTH(attributes);
read_req.attr_field = attributes; read_req.attr_field = attributes;
if (_read_manufacturer != nullptr) { if (_read_manufacturer != NULL) {
free(_read_manufacturer); free(_read_manufacturer);
} }
_read_manufacturer = nullptr; _read_manufacturer = NULL;
esp_zb_lock_acquire(portMAX_DELAY); esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_read_attr_cmd_req(&read_req); esp_zb_zcl_read_attr_cmd_req(&read_req);
@ -201,10 +226,10 @@ char *ZigbeeEP::readModel(uint8_t endpoint, uint16_t short_addr, esp_zb_ieee_add
read_req.attr_number = ZB_ARRAY_LENTH(attributes); read_req.attr_number = ZB_ARRAY_LENTH(attributes);
read_req.attr_field = attributes; read_req.attr_field = attributes;
if (_read_model != nullptr) { if (_read_model != NULL) {
free(_read_model); free(_read_model);
} }
_read_model = nullptr; _read_model = NULL;
esp_zb_lock_acquire(portMAX_DELAY); esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_read_attr_cmd_req(&read_req); esp_zb_zcl_read_attr_cmd_req(&read_req);
@ -245,20 +270,28 @@ void ZigbeeEP::zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute) {
/* Basic cluster attributes */ /* Basic cluster attributes */
if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) {
zbstring_t *zbstr = (zbstring_t *)attribute->data.value; zbstring_t *zbstr = (zbstring_t *)attribute->data.value;
char *string = (char *)malloc(zbstr->len + 1); _read_manufacturer = (char *)malloc(zbstr->len + 1);
memcpy(string, zbstr->data, zbstr->len); if (_read_manufacturer == NULL) {
string[zbstr->len] = '\0'; log_e("Failed to allocate memory for manufacturer data");
log_i("Peer Manufacturer is \"%s\"", string); xSemaphoreGive(lock);
_read_manufacturer = string; return;
}
memcpy(_read_manufacturer, zbstr->data, zbstr->len);
_read_manufacturer[zbstr->len] = '\0';
log_i("Peer Manufacturer is \"%s\"", _read_manufacturer);
xSemaphoreGive(lock); xSemaphoreGive(lock);
} }
if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) { if (attribute->id == ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID && attribute->data.type == ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING && attribute->data.value) {
zbstring_t *zbstr = (zbstring_t *)attribute->data.value; zbstring_t *zbstr = (zbstring_t *)attribute->data.value;
char *string = (char *)malloc(zbstr->len + 1); _read_model = (char *)malloc(zbstr->len + 1);
memcpy(string, zbstr->data, zbstr->len); if (_read_model == NULL) {
string[zbstr->len] = '\0'; log_e("Failed to allocate memory for model data");
log_i("Peer Model is \"%s\"", string); xSemaphoreGive(lock);
_read_model = string; return;
}
memcpy(_read_model, zbstr->data, zbstr->len);
_read_model[zbstr->len] = '\0';
log_i("Peer Model is \"%s\"", _read_model);
xSemaphoreGive(lock); xSemaphoreGive(lock);
} }
} }

View file

@ -41,6 +41,10 @@ typedef enum {
/* Zigbee End Device Class */ /* Zigbee End Device Class */
class ZigbeeEP { class ZigbeeEP {
public: public:
// constants and limits
static constexpr size_t ZB_MAX_NAME_LENGTH = 32;
// constructors and destructor
ZigbeeEP(uint8_t endpoint = 10); ZigbeeEP(uint8_t endpoint = 10);
~ZigbeeEP() {} ~ZigbeeEP() {}
@ -77,9 +81,10 @@ public:
char *readModel(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 // Set Power source and battery percentage for battery powered devices
bool setPowerSource(zb_power_source_t power_source, uint8_t percentage = 255); bool setPowerSource(zb_power_source_t power_source, uint8_t percentage = 0xff, uint8_t voltage = 0xff); // voltage in 100mV
bool setBatteryPercentage(uint8_t percentage); bool setBatteryPercentage(uint8_t percentage); // 0-100 %
bool reportBatteryPercentage(); bool setBatteryVoltage(uint8_t voltage); // voltage in 100mV (example value 35 for 3.5V)
bool reportBatteryPercentage(); // battery voltage is not reportable attribute
// Set time // Set time
bool addTimeCluster(tm time = {}, int32_t gmt_offset = 0); // gmt offset in seconds bool addTimeCluster(tm time = {}, int32_t gmt_offset = 0); // gmt offset in seconds

View file

@ -0,0 +1,118 @@
#include "ZigbeePM25Sensor.h"
#if CONFIG_ZB_ENABLED
esp_zb_cluster_list_t *zigbee_pm2_5_sensor_clusters_create(zigbee_pm2_5_sensor_cfg_t *pm2_5_sensor) {
esp_zb_basic_cluster_cfg_t *basic_cfg = pm2_5_sensor ? &(pm2_5_sensor->basic_cfg) : NULL;
esp_zb_identify_cluster_cfg_t *identify_cfg = pm2_5_sensor ? &(pm2_5_sensor->identify_cfg) : NULL;
esp_zb_pm2_5_measurement_cluster_cfg_t *pm2_5_meas_cfg = pm2_5_sensor ? &(pm2_5_sensor->pm2_5_meas_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_pm2_5_measurement_cluster(cluster_list, esp_zb_pm2_5_measurement_cluster_create(pm2_5_meas_cfg), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}
ZigbeePM25Sensor::ZigbeePM25Sensor(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID;
//Create custom PM2.5 sensor configuration
zigbee_pm2_5_sensor_cfg_t pm2_5_sensor_cfg = ZIGBEE_DEFAULT_PM2_5_SENSOR_CONFIG();
_cluster_list = zigbee_pm2_5_sensor_clusters_create(&pm2_5_sensor_cfg);
_ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID, .app_device_version = 0};
}
bool ZigbeePM25Sensor::setMinMaxValue(float min, float max) {
esp_zb_attribute_list_t *pm2_5_measure_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_err_t ret = esp_zb_cluster_update_attr(pm2_5_measure_cluster, ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_MIN_MEASURED_VALUE_ID, (void *)&min);
if (ret != ESP_OK) {
log_e("Failed to set min value: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_cluster_update_attr(pm2_5_measure_cluster, ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_MAX_MEASURED_VALUE_ID, (void *)&max);
if (ret != ESP_OK) {
log_e("Failed to set max value: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}
bool ZigbeePM25Sensor::setTolerance(float tolerance) {
esp_zb_attribute_list_t *pm2_5_measure_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_err_t ret = esp_zb_pm2_5_measurement_cluster_add_attr(pm2_5_measure_cluster, ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_TOLERANCE_ID, (void *)&tolerance);
if (ret != ESP_OK) {
log_e("Failed to set tolerance: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}
bool ZigbeePM25Sensor::setReporting(uint16_t min_interval, uint16_t max_interval, float delta) {
esp_zb_zcl_reporting_info_t reporting_info;
memset(&reporting_info, 0, sizeof(esp_zb_zcl_reporting_info_t));
reporting_info.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV;
reporting_info.ep = _endpoint;
reporting_info.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT;
reporting_info.cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE;
reporting_info.attr_id = ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_MEASURED_VALUE_ID;
reporting_info.u.send_info.min_interval = min_interval;
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 = (uint16_t)(delta * 100); // Convert delta to ZCL uint16_t
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;
memcpy(&reporting_info.u.send_info.delta.s32, &delta_f, sizeof(float));
esp_zb_lock_acquire(portMAX_DELAY);
esp_err_t ret = esp_zb_zcl_update_reporting_info(&reporting_info);
esp_zb_lock_release();
if (ret != ESP_OK) {
log_e("Failed to set reporting: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}
bool ZigbeePM25Sensor::setPM25(float pm25) {
esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS;
log_v("Updating PM2.5 sensor value...");
/* Update PM2.5 sensor measured value */
log_d("Setting PM2.5 to %0.1f", pm25);
esp_zb_lock_acquire(portMAX_DELAY);
ret = esp_zb_zcl_set_attribute_val(
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_MEASURED_VALUE_ID, &pm25, false
);
esp_zb_lock_release();
if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) {
log_e("Failed to set PM2.5: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret));
return false;
}
return true;
}
bool ZigbeePM25Sensor::report() {
/* Send report attributes command */
esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_PM2_5_MEASUREMENT_MEASURED_VALUE_ID;
report_attr_cmd.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI;
report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT;
report_attr_cmd.zcl_basic_cmd.src_endpoint = _endpoint;
report_attr_cmd.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC;
esp_zb_lock_acquire(portMAX_DELAY);
esp_err_t ret = esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
esp_zb_lock_release();
if (ret != ESP_OK) {
log_e("Failed to send PM2.5 report: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
log_v("PM2.5 report sent");
return true;
}
#endif // CONFIG_ZB_ENABLED

View file

@ -0,0 +1,60 @@
/* Class of Zigbee PM2.5 sensor endpoint inherited from common EP class */
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if CONFIG_ZB_ENABLED
#include "ZigbeeEP.h"
#include "ha/esp_zigbee_ha_standard.h"
// clang-format off
#define ZIGBEE_DEFAULT_PM2_5_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, \
}, \
.pm2_5_meas_cfg = \
{ \
.measured_value = 0.0, \
.min_measured_value = 0.0, \
.max_measured_value = 500.0, \
}, \
}
// clang-format on
typedef struct zigbee_pm2_5_sensor_cfg_s {
esp_zb_basic_cluster_cfg_t basic_cfg;
esp_zb_identify_cluster_cfg_t identify_cfg;
esp_zb_pm2_5_measurement_cluster_cfg_t pm2_5_meas_cfg;
} zigbee_pm2_5_sensor_cfg_t;
class ZigbeePM25Sensor : public ZigbeeEP {
public:
ZigbeePM25Sensor(uint8_t endpoint);
~ZigbeePM25Sensor() {}
// Set the PM2.5 value in 0.1 µg/m³
bool setPM25(float pm25);
// Set the min and max value for the PM2.5 sensor in 0.1 µg/m³
bool setMinMaxValue(float min, float max);
// Set the tolerance value for the PM2.5 sensor in 0.1 µg/m³
bool setTolerance(float tolerance);
// Set the reporting interval for PM2.5 measurement in seconds and delta (PM2.5 change in 0.1 µg/m³)
bool setReporting(uint16_t min_interval, uint16_t max_interval, float delta);
// Report the PM2.5 value
bool report();
};
#endif // CONFIG_ZB_ENABLED

View file

@ -1,6 +1,7 @@
#ifndef Pins_Arduino_h #ifndef Pins_Arduino_h
#define Pins_Arduino_h #define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0xAFD0 #define USB_VID 0xAFD0
#define USB_PID 0x0003 #define USB_PID 0x0003
#define USB_MANUFACTURER "Alfredo" #define USB_MANUFACTURER "Alfredo"

View file

@ -8,7 +8,7 @@
#endif #endif
#define USB_VID 0x303a #define USB_VID 0x303a
#define USB_PID 0x82D1 #define USB_PID 0x82D4
#define USB_MANUFACTURER "LILYGO" #define USB_MANUFACTURER "LILYGO"
#define USB_PRODUCT "T-LoRa-Pager" #define USB_PRODUCT "T-LoRa-Pager"