Skip to content

Commit

Permalink
Merge branch 'feature/zigbee_deep_sleep' into 'main'
Browse files Browse the repository at this point in the history
esp-zigbee-sdk: add zigbee deep sleep example

See merge request espressif/esp-zigbee-sdk!76
  • Loading branch information
chshu committed Nov 30, 2023
2 parents ad5adf3 + c6754cc commit c46797a
Show file tree
Hide file tree
Showing 21 changed files with 454 additions and 3 deletions.
8 changes: 7 additions & 1 deletion examples/.build-rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ examples/esp_zigbee_rcp:
temporary: true
reason: the other targets are not tested yet

examples/esp_zigbee_sleep/sleepy_end_device:
examples/esp_zigbee_sleep/deep_sleep:
enable:
- if: IDF_TARGET in ["esp32c6", "esp32h2"]
temporary: true
reason: the other targets are not tested yet

examples/esp_zigbee_sleep/light_sleep:
enable:
- if: IDF_TARGET in ["esp32c6", "esp32h2"]
temporary: true
Expand Down
87 changes: 87 additions & 0 deletions examples/esp_zigbee_sleep/deep_sleep/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
| Supported Targets | ESP32-H2 | ESP32-C6 |
| ----------------- | -------- | -------- |

# Sleepy End Device Example

This test code shows how to configure Zigbee sleepy end device using [Deep Sleep mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/system/sleep_modes.html#sleep-modes).

This example is designed to address a specific deep sleep application scenario. First, it joins to the Zigbee network, and after 5 seconds, it enters deep sleep mode. There are two ways to wake up in this example: one is by using a 20-second periodic RTC timer, and the other is through GPIO input. Deep sleep is part of the upper-layer logic, and it's the user's responsibility to manage it in their own applications. If you need more wake-up methods, you can refer to the [Exapmle deep sleep]([../../../system/deep_sleep/](https://github.com/espressif/esp-idf/tree/master/examples/system/deep_sleep)). Additionally, Espressif provides a stub for handling wake-ups, which allows for a quick check, and the user can decide whether to wake up or continue deep sleep in this stub, as explained in the [Example deep sleep stub]([../../../system/deep_sleep_wake_stub](https://github.com/espressif/esp-idf/tree/master/examples/system/deep_sleep_wake_stub)).

Note: Implementing a standard Zigbee Sleepy Device is recommended using the [Light Sleep example](../light_sleep). Deep sleep triggers a reboot, and the device needs to undergo a re-attach process to rejoin the network. This means additional packet interactions are necessary after each wake-up from deep sleep. It can be advantageous in reducing power consumption, especially when the device remains in a sleep state for extended periods, such as more than 30 minutes.

## Hardware Required

* One development board with ESP32-H2 SoC acting as Zigbee end-device (loaded with sleepy_end_device example)
* A USB cable for power supply and programming
* Choose another ESP32-H2 as Zigbee coordinator (see [HA_on_off_switch example](../../esp_zigbee_HA_sample/HA_on_off_switch/))

## Configure the project

Before project configuration and build, make sure to set the correct chip target using `idf.py --preview set-target TARGET` command.

## Erase the NVRAM

Before flash it to the board, it is recommended to erase NVRAM if user doesn't want to keep the previous examples or other projects stored info
using `idf.py -p PORT erase-flash`

## Build and Flash

Build the project, flash it to the board, and start the monitor tool to view the serial output by running `idf.py -p PORT flash monitor`.

(To exit the serial monitor, type ``Ctrl-]``.)

## Example Output

As you run the example, you will see the following log:

```
I (414) sleep: Configure to isolate all GPIO pins in sleep state
I (420) sleep: Enable automatic switching of GPIO sleep configuration
I (427) main_task: Started on CPU0
I (427) main_task: Calling app_main()
I (437) ESP_ZB_DEEP_SLEEP: Not a deep sleep reset
I (437) ESP_ZB_DEEP_SLEEP: Enabling timer wakeup, 20s
I (447) phy: phy_version: 211,0, 5857fe5, Nov 1 2023, 11:31:09
I (447) phy: libbtbb version: ce629d6, Nov 1 2023, 11:31:19
I (457) main_task: Returned from app_main()
I (587) ESP_ZB_DEEP_SLEEP: ZDO signal: ZDO Config Ready (0x17), status: ESP_FAIL
I (587) ESP_ZB_DEEP_SLEEP: Zigbee stack initialized
I (587) ESP_ZB_DEEP_SLEEP: Start network steering
I (10557) ESP_ZB_DEEP_SLEEP: Joined network successfully (Extended PAN ID: 60:55:f9:ff:fe:f7:2f:3e, PAN ID: 0xe4c4, Channel:13, Short Address: 0x5e1c)
I (10567) ESP_ZB_DEEP_SLEEP: Start one-shot timer for 5s to enter the deep sleep
I (15567) ESP_ZB_DEEP_SLEEP: Enter deep sleep
ESP-ROM:esp32h2-20221101
Build:Nov 1 2022
rst:0x5 (SLEEP_WAKEUP),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
...
...
...
I (225) sleep: Configure to isolate all GPIO pins in sleep state
I (231) sleep: Enable automatic switching of GPIO sleep configuration
I (239) main_task: Started on CPU0
I (239) main_task: Calling app_main()
I (249) ESP_ZB_DEEP_SLEEP: Wake up from timer. Time spent in deep sleep and boot: 20257ms
I (249) ESP_ZB_DEEP_SLEEP: Enabling timer wakeup, 20s
I (269) phy: phy_version: 211,0, 5857fe5, Nov 1 2023, 11:31:09
I (269) phy: libbtbb version: ce629d6, Nov 1 2023, 11:31:19
I (279) main_task: Returned from app_main()
I (289) ESP_ZB_DEEP_SLEEP: ZDO signal: ZDO Config Ready (0x17), status: ESP_FAIL
I (289) ESP_ZB_DEEP_SLEEP: Zigbee stack initialized
I (8749) ESP_ZB_DEEP_SLEEP: Start network steering
I (8849) ESP_ZB_DEEP_SLEEP: Joined network successfully (Extended PAN ID: 60:55:f9:ff:fe:f7:2f:3e, PAN ID: 0xe4c4, Channel:13, Short Address: 0x5e1c)
I (8849) ESP_ZB_DEEP_SLEEP: Start one-shot timer for 5s to enter the deep sleep
I (13859) ESP_ZB_DEEP_SLEEP: Enter deep sleep
```

During the deep sleep, a typical power consumption is shown below:
![H2-deep-sleep-power-consumption](image/ESP32H2-deep-sleep-power-consumption.png)

## Troubleshooting

For any technical queries, please open an [issue](https://github.com/espressif/esp-zigbee-sdk/issues) on GitHub. We will get back to you soon.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
235 changes: 235 additions & 0 deletions examples/esp_zigbee_sleep/deep_sleep/main/esp_zb_sleepy_end_device.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*
* Zigbee Sleepy end device Example
*
* This example code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#include "esp_check.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "time.h"
#include "sys/time.h"
#include "driver/rtc_io.h"
#include "driver/uart.h"
#include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ha/esp_zigbee_ha_standard.h"
#include "esp_zb_sleepy_end_device.h"

#ifdef CONFIG_PM_ENABLE
#include "esp_pm.h"
#include "esp_private/esp_clk.h"
#endif
/**
* @note Make sure set idf.py menuconfig in zigbee component as zigbee end device!
*/
#if !defined ZB_ED_ROLE
#error Define ZB_ED_ROLE in idf.py menuconfig to compile light (End Device) source code.
#endif

static const char *TAG = "ESP_ZB_DEEP_SLEEP";

static RTC_DATA_ATTR struct timeval s_sleep_enter_time;
static esp_timer_handle_t s_oneshot_timer;

/********************* Define functions **************************/
static void s_oneshot_timer_callback(void* arg)
{
/* Enter deep sleep */
ESP_LOGI(TAG, "Enter deep sleep");
gettimeofday(&s_sleep_enter_time, NULL);
esp_deep_sleep_start();
}

static void zb_deep_sleep_init(void)
{
/* Within this function, we print the reason for the wake-up and configure the method of waking up from deep sleep.
This example provides support for two wake-up sources from deep sleep: RTC timer and GPIO. */

/* The one-shot timer will start when the device transitions to the CHILD state for the first time.
After a 5-second delay, the device will enter deep sleep. */

const esp_timer_create_args_t s_oneshot_timer_args = {
.callback = &s_oneshot_timer_callback,
.name = "one-shot"
};

ESP_ERROR_CHECK(esp_timer_create(&s_oneshot_timer_args, &s_oneshot_timer));

// Print the wake-up reason:
struct timeval now;
gettimeofday(&now, NULL);
int sleep_time_ms = (now.tv_sec - s_sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - s_sleep_enter_time.tv_usec) / 1000;
esp_sleep_wakeup_cause_t wake_up_cause = esp_sleep_get_wakeup_cause();
switch (wake_up_cause) {
case ESP_SLEEP_WAKEUP_TIMER: {
ESP_LOGI(TAG, "Wake up from timer. Time spent in deep sleep and boot: %dms", sleep_time_ms);
break;
}
case ESP_SLEEP_WAKEUP_EXT1: {
ESP_LOGI(TAG, "Wake up from GPIO. Time spent in deep sleep and boot: %dms", sleep_time_ms);
break;
}
case ESP_SLEEP_WAKEUP_UNDEFINED:
default:
ESP_LOGI(TAG, "Not a deep sleep reset");
break;
}

/* Set the methods of how to wake up: */
/* 1. RTC timer waking-up */
const int wakeup_time_sec = 20;
ESP_LOGI(TAG, "Enabling timer wakeup, %ds\n", wakeup_time_sec);
ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000));

/* 2. GPIO waking-up */
#if CONFIG_IDF_TARGET_ESP32C6
/* For ESP32C6 boards, RTCIO only supports GPIO0~GPIO7 */
/* GPIO7 pull down to wake up */
const int gpio_wakeup_pin = 7;
#elif CONFIG_IDF_TARGET_ESP32H2
/* You can wake up by pulling down GPIO9. On ESP32H2 development boards, the BOOT button is connected to GPIO9.
You can use the BOOT button to wake up the boards directly.*/
const int gpio_wakeup_pin = 9;
#endif
const uint64_t gpio_wakeup_pin_mask = 1ULL << gpio_wakeup_pin;
/* The configuration mode depends on your hardware design.
Since the BOOT button is connected to a pull-up resistor, the wake-up mode is configured as LOW. */
ESP_ERROR_CHECK(esp_sleep_enable_ext1_wakeup_io(gpio_wakeup_pin_mask, ESP_EXT1_WAKEUP_ANY_LOW));

/* Also these two GPIO configurations are also depended on the hardware design.
The BOOT button is connected to the pull-up resistor, so enable the pull-up mode and disable the pull-down mode.
Notice: if these GPIO configurations do not match the hardware design, the deep sleep module will enable the GPIO hold
feature to hold the GPIO voltage when enter the sleep, which will ensure the board be waked up by GPIO. But it will cause
3~4 times power consumption increasing during sleep. */
ESP_ERROR_CHECK(gpio_pullup_en(gpio_wakeup_pin));
ESP_ERROR_CHECK(gpio_pulldown_dis(gpio_wakeup_pin));
}

static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask)
{
ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask));
}

void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct)
{
uint32_t *p_sg_p = signal_struct->p_app_signal;
esp_err_t err_status = signal_struct->esp_err_status;
esp_zb_app_signal_type_t sig_type = *p_sg_p;
switch (sig_type) {
case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
ESP_LOGI(TAG, "Zigbee stack initialized");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
break;
case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
if (err_status == ESP_OK) {
ESP_LOGI(TAG, "Start network steering");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else {
/* commissioning failed */
ESP_LOGW(TAG, "Failed to initialize Zigbee stack (status: %d)", err_status);
esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
}
break;
case ESP_ZB_BDB_SIGNAL_STEERING:
if (err_status == ESP_OK) {
esp_zb_ieee_addr_t extended_pan_id;
esp_zb_get_extended_pan_id(extended_pan_id);
ESP_LOGI(TAG, "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4],
extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0],
esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address());

/* Start the one-shot timer */
const int before_deep_sleep_time_sec = 5;
ESP_LOGI(TAG, "Start one-shot timer for %ds to enter the deep sleep", before_deep_sleep_time_sec);
ESP_ERROR_CHECK(esp_timer_start_once(s_oneshot_timer, before_deep_sleep_time_sec * 1000000));
} else {
ESP_LOGI(TAG, "Network steering was not successful (status: %d)", err_status);
esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
}
break;
case ESP_ZB_COMMON_SIGNAL_CAN_SLEEP:
ESP_LOGI(TAG, "Can sleep");
break;
default:
ESP_LOGI(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status));
break;
}
}

static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message)
{
esp_err_t ret = ESP_OK;
bool light_state = 0;

ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");
ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG, "Received message: error status(%d)",
message->info.status);
ESP_LOGI(TAG, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster,
message->attribute.id, message->attribute.data.size);
if (message->info.dst_endpoint == HA_ESP_LIGHT_ENDPOINT) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
light_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : light_state;
ESP_LOGI(TAG, "Light sets to %s", light_state ? "On" : "Off");
}
}
}
return ret;
}

static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message)
{
esp_err_t ret = ESP_OK;
switch (callback_id) {
case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message);
break;
default:
ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id);
break;
}
return ret;
}

static void esp_zb_task(void *pvParameters)
{
/* initialize Zigbee stack with Zigbee end-device config */
esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
esp_zb_init(&zb_nwk_cfg);
/* set the on-off light device config */
esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();
esp_zb_ep_list_t *esp_zb_on_off_light_ep = esp_zb_on_off_light_ep_create(HA_ESP_LIGHT_ENDPOINT, &light_cfg);
esp_zb_device_register(esp_zb_on_off_light_ep);
esp_zb_core_action_handler_register(zb_action_handler);
esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
ESP_ERROR_CHECK(esp_zb_start(false));
esp_zb_main_loop_iteration();
}

void app_main(void)
{
esp_zb_platform_config_t config = {
.radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(),
.host_config = ESP_ZB_DEFAULT_HOST_CONFIG(),
};
ESP_ERROR_CHECK(nvs_flash_init());
/* load Zigbee light_bulb platform config to initialization */
ESP_ERROR_CHECK(esp_zb_platform_config(&config));

zb_deep_sleep_init();

xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*
* Zigbee Sleepy end device Example
*
* This example code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/

#include "esp_zigbee_core.h"

/* Zigbee configuration */
#define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */
#define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN
#define ED_KEEP_ALIVE 4000 /* 4000 millisecond */
#define HA_ESP_LIGHT_ENDPOINT 10 /* esp light bulb device endpoint, used to process light controlling commands */
#define ESP_ZB_PRIMARY_CHANNEL_MASK (1<<13) /* Zigbee primary channel mask use in the example */

#define ESP_ZB_ZED_CONFIG() \
{ \
.esp_zb_role = ESP_ZB_DEVICE_TYPE_ED, \
.install_code_policy = INSTALLCODE_POLICY_ENABLE, \
.nwk_cfg.zed_cfg = { \
.ed_timeout = ED_AGING_TIMEOUT, \
.keep_alive = ED_KEEP_ALIVE, \
}, \
}

#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_NATIVE, \
}

#define ESP_ZB_DEFAULT_HOST_CONFIG() \
{ \
.host_connection_mode = HOST_CONNECTION_MODE_NONE, \
}
Loading

0 comments on commit c46797a

Please sign in to comment.