ESPHome configuration help - enter deep sleep after all sensors read or after time out

Goal: more intelligent awake time.
When all MQTT data has been published, do something (in this case, enter deep sleep).

Hi,

I have a few sensors connected to an ESP8266 D1 Mini and I’m using a basic deep sleep config to reduce the effect of self heating on the temperature sensor. I currently have run_duration set to 1s, but I’d like to find a way to make this parameter a bit smarter.

Can somebody please help me find a way to configure the ESP to enter deep sleep as soon as all sensors have returned data and reported to the MQTT broker, OR, the ESP times out after having been awake for 1 second (run_duration)? I’m not experienced with Lambda functions, but I assume that and a script is what will be needed. Although, a YAML approach would be far easier and preferred.

My code:

################################################################################
# Define variables
################################################################################

substitutions:
  # Device Properties
  devicename: dev-air-quality
  friendly_name: Dev-Air-Quality
  device_topic: AirQuality
  device_sleephelper_topic: admin/esphome_disable_deep_sleep
  
  # Sleep Properties
  sleep_time: 2min
  run_time: 1s
  # The sensor will report data on boot, and then again for each sensor on their independent update_interval parameters until put to sleep again.
  # Set update interval to a longer time than run_time to ensure only 1 data update per wake.
  # I suspect that the update interval still has to be shorter than sleep time though. Otherwise, the device seems to wake up and not publish.
  global_update_interval: $sleep_time

  # Pinout
  # Wake        D0  # GPIO16  - Wake (deep sleep) - high at boot.
  pin_i2c_scl:  D1  # GPIO5
  pin_i2c_sda:  D2  # GPIO4
  # Reserved    D3  # GPIO0   - biased high - connected to flash button 
  # Reserved    D4  # GPIO2   - biased high - connected to on-board LED
  pin_rx:       D5  # GPIO14
  pin_tx:       D6  # GPIO12
  pin_dht:      D7  # GPIO13
  #             D8  # GPIO15  - biased low
  #             RX  # GPIO3
  #             TX  # GPIO1   - debug output at boot
  #             A0  # ADC0


################################################################################
# Define Device
################################################################################

esphome:
  name: $devicename

esp8266:
  board: d1_mini

# Enable logging
logger:
  
# Enable Over-The-Air flashing
ota:
  #safe_mode: True
  password: !secret ota_password


################################################################################
# Power
################################################################################

deep_sleep:
  id: deep_sleep_control
  sleep_duration: $sleep_time
  run_duration: $run_time


################################################################################
# Wireless Access
################################################################################

wifi:
  id: wifi_client
  power_save_mode: LIGHT #There are other options. Just checking if this helps with self heating.
  fast_connect: true # Skip wifi scan to save time.
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
  # Set a static IP address.
  manual_ip:
    static_ip: 192.168.86.200
    gateway: 192.168.86.1
    subnet: 255.255.255.0
  # If changing the name or the IP address of the device, this provides the old address so that ESPHome knows which device to overwrite.
  use_address: "192.168.86.200"


################################################################################
# Home Assistant Connection
################################################################################

# Enable MQTT
mqtt:
  id: mqtt_client
  broker: !secret mqtt_broker
  port: !secret mqtt_port
  username: !secret mqtt_user
  password: !secret mqtt_password
  discovery: true
  discovery_retain: true
  # Set empty birth and will messages to avoid showing unavailable during sleep
  birth_message:
  will_message:
  topic_prefix: $device_topic
  on_message:
    - topic: $device_sleephelper_topic
      qos: 0    #1
      payload: 'on'
      then:
        - deep_sleep.prevent: deep_sleep_control
    - topic: $device_sleephelper_topic
      qos: 0    #1
      payload: 'off'
      then:
        - deep_sleep.enter: deep_sleep_control


################################################################################
# Communication Protocols
################################################################################

i2c:
  sda: $pin_i2c_sda
  scl: $pin_i2c_scl
  id: bus_i2c
  scan: False       # Save time by skipping the I2C scan on startup since all addresses are known. Defaults to True
  frequency: 50kHz  # Defaults to 50kHz, SCD30 can't handle more than 100kHz.


################################################################################
# Functionality
################################################################################

# Air Quality, Temperature and Humidity.
sensor:
  # DHT11 Temperature and Humidity Sensor
  - platform: dht
    model: DHT22 #DHT11
    pin: $pin_dht
    temperature:
      name: "$friendly_name Temperature"
      id: temperature
    humidity:
      name: "$friendly_name Humidity"
      id: humidity
    update_interval: $global_update_interval
    
  # CO2, Temperature and Humidity
  - platform: scd30
    i2c_id: bus_i2c
    address: 0x61
    automatic_self_calibration: true
    update_interval: $global_update_interval
    co2:
      name: "$friendly_name CO2"
      id: co2
      accuracy_decimals: 1
    temperature:
      name: "$friendly_name Temperature SCD"
      id: temperature_scd
      accuracy_decimals: 2
      filters:
        - offset: 0
    humidity:
      name: "$friendly_name Humidity SCD"
      id: humidity_scd
      accuracy_decimals: 1
    #temperature_offset: 1.5 °C
    
  # TVOC, Estimated CO2
  - platform: sgp30
    i2c_id: bus_i2c
    address: 0x58
    store_baseline: yes
    update_interval: $global_update_interval
    eco2:
      name: "$friendly_name eCO2"
      id: eco2
      accuracy_decimals: 1
    tvoc:
      name: "$friendly_name TVOC"
      id: tvoc
      accuracy_decimals: 1

I’ve considered setting each sensor to update_interval: never and then adding a script to either on_boot or on_loop that calls component.update on each sensor before calling deep_sleep.enter.
But how would I make sure the broker has actually received the data before the device enters deep sleep mode?
And would I just leave run_duration set to 1s to provide the time out function?

I could also try find a way to make node red tell the ESPHome device to enter deep sleep once data has been received. I’m not sure how I would do that yet.

Is there a “best practice” way to achieve this?

I think I’ve answered my own question in another one of my posts. It has a completely different purpose but I think I can use a similar method to call the deep_sleep.enter function after all sensors have been read. I’ll modify my code in the next week or two and post it here when it’s ready.

Did you find any solution to trigger update before deep sleep? Your API vs MQTT analyses is pretty cool, but I did not see the solution to ensure the value has been transmitted before entering deep sleep.
Currently I use:

 on_boot:
    then:
      - wait_until:
           condition:
             api.connected:
      - component.update: bme280_sensor
      - component.update: wifi_signal_db
      - component.update: vcc
      - delay: 1s
      - script.execute: consider_deep_sleep

As soon as there is a working esp-now component, I will replace api with esp-now. :slight_smile:

1 Like

Wanted to share my solution in case it may help anyone else who is trying to accomplish a similar thing :smile:

This solution avoids using any explicit delays/sleeps.

mqtt:
  id: mqtt_client
  on_connect:
    then:
      - script.execute: check_all_published

script:
  - id: check_all_published
    mode: restart
    then:
      - if:
          condition:
            lambda: 'return !id(ota_mode);'
          then:
            - lambda: |-
                static bool temp_published = false;
                static bool pressure_published = false;
                static bool humidity_published = false;

                if (!temp_published && id(${node_id}_temperature).has_state()) {
                  temp_published = true;
                }
                if (!pressure_published && id(${node_id}_pressure).has_state()) {
                  pressure_published = true;
                }
                if (!humidity_published && id(${node_id}_humidity).has_state()) {
                  humidity_published = true;
                }

                if (temp_published && pressure_published && humidity_published && id(mqtt_client)->is_connected()) {
                  id(deep_sleep_1).set_run_duration(0);
                  id(deep_sleep_1).set_sleep_duration(1000 * 60 * 5); // 5 minutes
                  id(deep_sleep_1).begin_sleep();
                }

sensor:
  - platform: bme280_i2c
    temperature:
      name: Temperature
      id: ${node_id}_temperature
      oversampling: 2x
      on_value:
        then:
          - script.execute: check_all_published
    pressure:
      name: Pressure
      id: ${node_id}_pressure
      oversampling: 2x
      on_value:
        then:
          - script.execute: check_all_published
    humidity:
      name: Humidity
      id: ${node_id}_humidity
      oversampling: 2x
      on_value:
        then:
          - script.execute: check_all_published
    address: 0x76

1 Like