When running from a battery it is often desirable to minimise the power consumption; particularly where the ESP32 is taking sensor reading on a regular basis but doing nothing between them.
Some (but not all) ESP32 models support deep_sleep, which allows ESPHome to power off the main CPU when it is not needed, and reboot it after a timer or other input.
I found a number of issues with deep_sleep – many are not problems, but just things which aren’t covered in much detail in the docs. This is my attempt to collect the various pieces I have learnt about deep_sleep in one place, and trying to explain in not-so-technical language. I welcome additions, comments and suggestions.
Intended deep-sleep operation
There is not much information about deep_sleep in the documentation, which gives the impression that its operation is very simple, and it can be:
- setup the sensors, etc as normal
- add the deep_sleep component, specifying how long it should sleep for, and how long it should be awake for.
This works well … assuming it is all you want. After the run_period, ESPHome turns the main CPU off. After a period of time or some other signal, the CPU is booted up, and starts again.
But if you want to further reduce battery consumption, or wake for other reasons, it gets more complicated …
deep_sleep is actually a shutdown, power-off and boot
The first thing to know is that ESP32’s deep_sleep are actually a shutdown and a boot operation.
- Calling deep_sleep_enter action runs esphome: on_shutdown;
- While “asleep”, the ESP32 is powered off and consequently cannot do anything.
- Esphome: on_boot: is the first thing run when the ESP32 wakes up.
Relationship between update-interval and run_duration
Each sensor has its own update_interval where you specify how often it should be updated – some things (like room temperature) a reading once an hour may be sufficient, but other things (like rainfall) you might like to know minute-by-minute
It gets a little confusing if you are wanting your ESP32 to deep_sleep, then take readings all at the same time when awake. If your ESP32 is awake 5 minutes then asleep for 1 hour, do you set update_interval for 1 hour or for 5 minutes ?
I created a substitution for update_interval and applied it to all my sensors. I had expected that setting all sensors to the same 5 minute interval they would all be read at the start, then there would be a pause until the start of the next 5 minute interval, telling me how long I need my ESP32 to be awake. But in practice ESPHome spreads the sensor reading at random through the common update_interval.
In my testing, setting an update_interval longer than the run_duration only reported some of the sensors before going back to sleep – and then next awake period didn’t continue with the sensors missed the previous time; but started again, usually reporting the same sensor as in the previous awake period. So, not a good option
Setting update_interval equal to the run_duration gave 1 result per sensor for each awake period. If you specify a 5 minute interval, ESPHome will take 5 minutes to gather all the sensor readings; even if they could be reported on in 30 seconds.
To determine the minimum time to report all your sensors will take some trial and error, and maybe add a little programming magic to ensure that deep_sleep doesn’t happen until all sensors are reported.
Setting update_interval less than the run_duration gave multiple readings per awake period, though sometimes the order that the sensors were read can change between update intervals.
Sending yaml or ESPHome updates
Consider also that the shorter the run_duration, the less time in which to send yaml configuration or ESPHome updates. There are a few threads on this forum which provide methods to pause deep_sleep to accomplish this … though most use MQTT messages as the trigger.
Wakeup Cause
ESP32’s deep_sleep is actually a shutdown and a boot operation – but maybe we want to know whether the ESP32 is booting because power is first turned on, because the sleep_duration time is up, because an external signal has caused a wakeup, or some other reason.
In ESPHome’s documentation for deep_sleep, the “ESP32 Wakeup Cause” section gives a yaml example for a template sensor to store the reason for the wakeup, which can then be used to determine the reason for the wakeup. Again, this code works and is useful to report the reason back to Home Assistant, but …
- Depending on your run_interval and this sensors update_interval it could be updated multiple times while awake - or not updated during the run_interval. If it is only returning esp_sleep_get_wakeup_cause() (as in the example code) not a problem since the same wakeup code will be returned each time. But a different matter if you want to use this sensor to trigger other actions once per run_duration, or want the actions performed once only, or guaranteed at the beginning of the run_duration.
- Similarly if you have other code which uses the wakeup cause sensor, remember that it may not be updated until after your other code uses it.
- If you find you are getting a ridiculously large number for wakeup cause code (like me), it could actually be a pointer to the location in memory of the wakeup cause function, rather than its value. Easily solved by referring the .state property like ‘id(wakeup_cause).state‘
Using on_boot:
I suggest setting the wakeup cause variable in your on_boot:, such as
esphome:
on_boot:
then:
- lambda: |-
id(wakeup_cause).state = esp_sleep_get_wakeup_cause();
id(num_wake_cycles) += 1;
if (esp_sleep_get_wakeup_cause == 0) {
// reason 0 - ESP_SLEEP_WAKEUP_UNDEFINED: probably power-on
ESP_LOGD("testing", ">>>>>> on_boot: power-on esp_sleep_get_wakeup_cause=%d, num_wake_cycles=%d", esp_sleep_get_wakeup_cause(), id(num_wake_cycles) );
} else {
// 4 - ESP_SLEEP_WAKEUP_TIMER: Wakeup caused by timer
ESP_LOGD("testing", ">>>>>> on_boot: wakeup esp_sleep_get_wakeup_cause=%d, num_wake_cycles=%d", esp_sleep_get_wakeup_cause(), id(num_wake_cycles) );
};
The problem with doing much in the on_boot: and on_shutdown: sections is that you don’t get to see any of the debugging logger.log or ESP_LOGD() statements unless you have your terminal program connected to the ESP32’s UART … a topic for a separate rant.
Note that there are several forum posts where the user has taken the approach of forcing all the sensor updates and processing in the on_boot: routine, ending by directly calling deep_sleep.enter. This does seem a particularly effective way to minimise the time that the ESP32 is awake … at the cost of being difficult to debug.
Extra increments of a counter
I also noticed that my counter of the number of wake cycles was being incremented twice. Turns out that sensors get updated silently in the startup (in that delay in the HA ESPHome Builder LOG before you start to see any log entries), so be aware of this.