Hi everyone!
Recently I got into ESPHome and for a little project I want to set up an ESP32-C6 via Thread to control a couple of connected LEDs via GPIO pin. This generally works very well, even the configuration a MTD with a given poll period.
Since I need to power the device via battery I have to play around with the deep sleep component. Currently I am trying to sleep for 90s and then wait until the API is connected again and some data could be exchanged (10s wait time after connection has been established).
This leads to the following behavior: While the entities (the LED switch and battery voltage metering) keep their value during deep sleep, they switch to “unavailable” as soon as the device wakes up from deep sleep and tries to connect to the network. Also if I toggle the LED switch during deep sleep, the LED does not turn on when the device wakes up again and (theoretically) receives queued thread messages.
This worked flawlessly without the deep sleep component, so some kind of interaction is going on there I assume.
I have read some other topics and the FAQ (which covers the “unavailable” entity problem) but e.g. recreating the device did not help.
Now I hope I can learn some more about the interaction of deep sleep with a minimal thread device and maybe there are some issues in my configuration left. I will try to post some yaml that captures the essence of my setup.
I am using the latest version of ESPHome.
esphome:
name: soldered-sleep
friendly_name: Soldered Sleep
on_boot:
priority: -100 # Run after other components are initialized
then:
- logger.log: "On boot"
- script.execute: enter_deep_sleep_when_ready
esp32:
board: esp32-c6-devkitc-1
framework:
type: esp-idf
openthread:
tlv: <connection string>
device_type: MTD
# here I am not sure if I have to set it that high to communicate
# to the thread network that it takes a while to poll again
poll_period: 120s
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: <key>
# I think ota isn't supported with MTD anyways, maybe I could remove it?
ota:
- platform: esphome
password: <key>
network:
enable_ipv6: true
switch:
- platform: gpio
name: "LED"
pin: GPIO17
id: led
deep_sleep:
sleep_duration: 90s
# I think I need that for the first initial boot, but I'm not sure
# It doesn't seem to hurt
run_duration: 60s
id: deep_sleep_1
# Script to enter deep sleep only after API connection
script:
- id: enter_deep_sleep_when_ready
then:
- deep_sleep.prevent: deep_sleep_1
- wait_until:
api.connected:
- delay: 10s # Give some time for data exchange
# not sure if I need to allow it before entering deep sleep again
- deep_sleep.allow: deep_sleep_1
- deep_sleep.enter: deep_sleep_1
Best regards!
Bjoern
Hi! I’m also tinkering with a sleepy device based on the Esp32c6 and the Thread.
I’m using the deep sleep component with a 10s wake-up interval so the device has time to wake up and send a heartbeat data packet (in my case, this is the battery level).
Example:
deep_sleep:
id: deep_sleep_1
run_duration: 10s # Wake-up time
sleep_duration: 60s # Sleep time
During deep sleep, the device completely turns off the radio module and can’t receive your data packet signaling the need to turn on the LED. The device becomes “unreachable” due to the long sleep interval. As far as I can tell, the maximum stable interval is 60s.
poll_period in Open Thread is the minimum interval after which a data packet will be sent.
I think this isn’t supported with MTD anyway; maybe I could remove it?
OTA is supported by sleeping devices, but you need to wake up the device to initiate the OTA update.
Try something like this:
esphome:
name: soldered-sleep
friendly_name: Soldered Sleep
on_boot:
priority: -100 # Run after other components are initialized
then:
- logger.log: "On boot"
- script.execute: enter_deep_sleep_when_ready
esp32:
board: esp32-c6-devkitc-1
framework:
type: esp-idf
openthread:
tlv: <connection string>
device_type: MTD
# poll_period: 120s Delete the poll line. The device will connect to the network and send its status anyway. We'll set the sleep interval in the Deep sleep section.
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: <key>
ota:
- platform: esphome
password: <key>
network:
enable_ipv6: true
switch:
- platform: gpio
name: "LED"
pin: GPIO17
id: led
deep_sleep:
sleep_duration: 60s # Sleep interval - 60s - standard for EspHome API
run_duration: 10s # The operating time is 10 seconds - it should be enough to check in on the network
id: deep_sleep_1
# Script to enter deep sleep only after API connection
#script:
# - id: enter_deep_sleep_when_ready
# then:
# - deep_sleep.prevent: deep_sleep_1
# - wait_until:
# api.connected:
# - delay: 10s # Give some time for data exchange
# # not sure if I need to allow it before entering deep sleep again
# - deep_sleep.allow: deep_sleep_1
# - deep_sleep.enter: deep_sleep_1
# Not needed. The Deep Sleep component will automatically wake and sleep the device based on the specified timers.
I don’t quite understand the purpose of your device. Typically, battery-powered devices only send data to Home Assistant (temperature sensors, motion sensors, buttons, etc.). You want to control a battery-powered device, right? Then you could try reducing the sleep time (sleep_duration:), but this will reduce battery life.
Here’s my working configuration for a sleep device. It’s a soldering iron presence sensor on a stand (for automating a smoke exhauster). I’m using a TTP223-based module (touch sensor). So far, the “unavailable” state hasn’t been detected during sleep. The device also wakes up when the sensor is triggered (soldering iron is removed or placed).
Maybe some of this will be useful to you 
Example of a sleeping device using light sleep:
esphome:
name: soldering-iron-stand
friendly_name: Soldering iron stand
on_boot:
priority: -10
then:
- script.execute: measure_battery
esp32:
board: esp32-c6-devkitc-1
framework:
type: esp-idf
sdkconfig_options:
# Enable Automatic Power Management (DFS) at the SDK level
CONFIG_PM_ENABLE: "y"
# Enable Light Sleep support for Thread
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP: "y"
# Home Assistant API
api:
encryption:
key: "***"
ota:
- platform: esphome
password: "***"
network:
enable_ipv6: true
openthread:
device_type: MTD
tlv: !secret thread_tlv
globals:
- id: last_activity_time
type: uint32_t
restore_value: no
initial_value: '0'
- id: sleep_timeout_ms
type: uint32_t
restore_value: no
initial_value: '120000'
- id: last_battery_percentage
type: float
restore_value: yes
initial_value: '100.0'
output:
- platform: gpio
pin: GPIO0
id: battery_enable_pin
inverted: False
binary_sensor:
- platform: gpio
id: button_gpio
pin:
number: GPIO2
mode: INPUT_PULLDOWN
allow_other_uses: True
name: "Soldering iron on a stand"
device_class: occupancy
filters:
- delayed_on: 100ms
- delayed_off: 200ms
on_press:
- script.execute: measure_battery
on_release:
- script.execute: measure_battery
sensor:
- platform: adc
pin: GPIO1
id: battery_voltage
update_interval: never
attenuation: 12db
internal: True
filters:
- multiply: 1.2422
- median:
window_size: 5
send_every: 5
- or:
- throttle: 30min
- delta: 0.05
- platform: template
name: "Battery"
id: battery_percentage
update_interval: never
unit_of_measurement: "%"
accuracy_decimals: 0
device_class: "battery"
lambda: |-
float v = id(battery_voltage).state;
if (isnan(v) || v < 2.75f) return {};
float pct;
if (v >= 4.15f) pct = 100.0f;
else if (v >= 4.0f) pct = 90.0f + (v - 4.0f) * 24.5f;
else if (v >= 3.7f) pct = 20.0f + (v - 3.7f) * 143.5f;
else if (v >= 3.5f) pct = 5.0f + (v - 3.5f) * 235.7f;
else if (v >= 3.0f) pct = (v - 3.0f) * 12.3f;
else pct = 0.0f;
if (pct > id(last_battery_percentage) && (pct - id(last_battery_percentage)) < 8.0f) {
pct = id(last_battery_percentage);
} else {
id(last_battery_percentage) = pct;
}
return pct;
script:
- id: measure_battery
mode: restart
then:
- output.turn_on: battery_enable_pin
- delay: 10ms
- repeat:
count: 5
then:
- component.update: battery_voltage
- component.update: battery_percentage
- output.turn_off: battery_enable_pin