Use a binary sensor to wake up from deep sleep

Hi Community,
i am currently struggeling with the following problem in ESPHome:

I want to wake up a ESP32 when a Binary Sensor is set.
Then i want to go into deep sleep again.

This is my setup:

deep_sleep:
  run_duration: 60s
  sleep_duration: 60s
  wakeup_pin: 2

binary_sensor:
  - platform: gpio
    #pin: GPIO02
    name: "Testbinary"
    pin:
      number: 2
      #allow_other_uses: true

When i try to upload i am getting an error, that pin 2 is used in multiple places, even when option allow_other_uses is set to true.

Thank you for your help
Mario

Use the pin schema for the wakeup pin as well:

deep_sleep:
  run_duration: 60s
  sleep_duration: 60s
  wakeup_pin: 
      number: 2
      allow_other_uses: true

binary_sensor:
  - platform: gpio
    #pin: GPIO02
    name: "Testbinary"
    pin:
      number: 2
      allow_other_uses: true

Works great!
Thank you very much!

this brings me to a follow up question.

So the ESP is going into deep sleep as expected and wakes up when i press the button - so far so good.

i did set up a Automation where i increment a Counter for every press of the button. Works fine when ESP is awake, but desn’t recognize the press that woke up the ESP.

Any ideas here?

Mario

If it’s an ESP32, you can use wakeup_cause to determine if the button push did the wake. You would then increment your counter. If it’s an ESP8266 then I don’t know of a way.

If the ESP is in deep sleep, it will not be connected to HA, and nothing you do in HA will wake it. You either have to wake it with a physical thing connected to the ESP (i.e. the thing you have on GPIO2) or via a schedule that is set on the ESP in the deep sleep config.

Hi, thanks for your suggest.
Is it possibile to use also esp32_ext1_wakeup with same pin number?

tbinary_sensor:
  - platform: gpio
    name: "Door Contact"
    id: contact
    pin:
      number: 4
      mode: INPUT_PULLUP
      inverted: true
      allow_other_uses: true
    device_class: door

deep_sleep:
  run_duration: 5s
  esp32_ext1_wakeup:
      pins: [0,4]
      mode: ALL_LOW

This is the errors during compilation:

Failed config

deep_sleep: [source /config/simple-contact-sleep.yaml:66]
  
  Pin 4 is used in multiple places.
  run_duration: 5s
  esp32_ext1_wakeup: 
    pins: 
      - number: 0
        mode: 
          input: True
          output: False
          open_drain: False
          pullup: False
          pulldown: False
        inverted: False
        ignore_pin_validation_error: False

See allow_other_uses: in the Pin Schema docs;

Well, that was somewhat embarrassing. I had a situation where my ESP32 rebooted immediately on entering deep_sleep. I trimmed test yaml code down and documented many steps with full logs - that’s why I deleted those 3 posts … too long !

Deep-sleep was OK
Binary_sensor: was OK for GPIO7
… but put them together to wake from deep_sleep when the first bucket of rain tips … well no.

Long story short …

  1. get the sensor working correctly,
  2. then copy the entire binary_sensor: pin: section (including inverted: and pullup mode if used) to deep_sleep: wakeup_pin: section,
  3. remember to add allow_other_uses: true to both.
  4. note that the pulse which triggered the wakeup wasn’t counted, but you can add it from the on_boot: If there was more than one tip in the second it takes ESP32 to wake up, well sorry.

Yes that bit is a trap for the unwary…

I forgot one thing … In the post above I used a binary_sensor so I could see each tip of the rain gauge as it happened, and got that working with deep_sleep … but in the greenhouse I don’t want to record each individual bucket tip - instead I planned to use pulse_counter and measure the number of tips per minute. So does the same code from binary_sensor above work the same in a pulse_counter ? Not quite.

Curiously during on_boot: GPIO7 is now reported as ON - however that is not triggering immediate wakeup from deep_sleep … so OK.

I have previously worked out code to keep the ESP32 awake while rain is happening and for 5 minutes after. This works well, and I have added it here.

When the rain gauge bucket tips while the ESP32 is in deep_sleep it does wake up with wakeup_cause = 2. However the pulse did not get counted in the pulse_counter. Fortunately we can detect this scenario in on_boot: and do something about it ourselves - hopefully before more pulses are detected.

Pulses which happen after the ESP32 wakes but before Wi-fi is established are counted.

FYI this is my test YAML code:

#####################################################################
#
# The ESP32-S3-mini board is CURRENTLY used for debugging parts of the greenhouse code
#
#####################################################################

substitutions:
  devicename:     "s3-test"                     # last octet of the IP Address
  deviceIP:       "123"                     # last octet of the IP Address
  wifi_ssid:      !secret upstairs_ssid     ### which wi-fi to connect to
  wifi_password:  !secret upstairs_password 
  update_interval_sensor:  "1 min"        # How often to measure and report sensor values
  update_interval_network: "5 min"        # How often to measure and report network values
  rain_after_timeout:      5              # how many minutes to wait after rain stops

esphome:
  name: $devicename
  platformio_options:
    board_build.flash_mode: dio
  on_boot:
    - priority: -100.0
      then:
        - logger.log: "########## ON_BOOT starts ##########"
        #  Was wakeup for reason 2 - ESP_SLEEP_WAKEUP_EXT0: Wakeup caused by external signal using RTC_IO
        - if:
            condition:
              lambda: 'return int(esp_sleep_get_wakeup_cause()) == 2;'
            then:
              # process the rain gauge pulse which woke us up
              - lambda: 'id(rain_gauge).publish_state(1.0);'
              - logger.log: "   >>>> RAIN detected while asleep. <<<<"
        - logger.log:
            format: ">>>>>> on_boot: initial values  pin GPIO7=%s,  esp_sleep_get_wakeup_cause()=%d "
            args: [ 'id(rain_gauge).state ? "ON" : "OFF"', esp_sleep_get_wakeup_cause() ]

  on_shutdown:
    - logger.log:
          format: ">>>>>> on_shutdown: logger values  pin GPIO7=%s,  wakeup_cause=%d "
          args: [ 'id(rain_gauge).state ? "ON" : "OFF"', esp_sleep_get_wakeup_cause() ]
    - logger.log: "##### on_shutdown -100 finished. "
    - logger.log: "---------------------------------------------------------------------"
    - logger.log: "                                                                     "

sensor:
  # rain gauge hardware sends pulses as the bucket tips.
  # this rain_gauge component returns the number of pulses in the preceding update_interval (1 minute)
  - platform: pulse_counter
    name: Rain Gauge
    id:   rain_gauge
    pin:
      number: GPIO7
      allow_other_uses: true
      inverted: true
      mode:          # prevent lots of ON/OFF events
        input: true
        pullup: true
    update_interval: 60s      # check the rain_gauge every minute 
    on_value:
      then:
#        - logger.log: "         ********** RAIN GAUGE on_value *****"
        - if:
            condition:
              lambda: 'return x > 0.0;'
            then:
              # there are rain pulses, so currently raining.  Keep re-starting the counter
              - logger.log:
                  format: ">>>>>> NEW RAIN DETECTED counter started, x=%f pulses"
                  args:   [ 'x' ]
              - number.set:
                  id: rain_after_time
                  value: 1.0
              # don't go into deep_sleep, because that will stop the rain counting
              - deep_sleep.prevent: deep_sleep_1
            else:
              - if:
                  condition:
                    lambda: 'return id(rain_after_time).state >= $rain_after_timeout;'
                  then:
                    # now 5 minutes since last rain detected. reset
                    - number.set:
                        id: rain_after_time
                        value: 0.0
#                    - logger.log:
#                        format: ">>>>>> RAIN TIMED OUT <<<<<<   Back to normal.  reset id(rain_after_time).sta>
#                        args:   [ 'id(rain_after_time).state' ]
                    - script.execute: check_deep_sleep
                  else:
                    # rain has recently stopped ... wait for timeout
                    - if:
                        condition:
                          lambda: 'return id(rain_after_time).state > 0.0;'
                        then:
                          # recently stopped raining, so increment timer
                          - number.increment:
                              id: rain_after_time
#                          - logger.log:
#                              format: ">>>>>> waiting for RAIN TIMEOUT.  Currently at %f minutes "
#                              args:   [ 'id(rain_after_time).state' ]

number:
  - platform: template
      # this is actually a counter of minutes since rain was last detected:
      #   0 means no rain detected (can go back to deep_sleep)
      #   1 is set whenever rain_gauge detects any rain
      #   2-99 is minutes since rain was last detected
    id: rain_after_time
    name: Rain_after_time
    optimistic: true
    min_value: 0
    max_value: 10
    step: 1

script:
  - id: check_deep_sleep
    then:
      # no outstanding reason to prevent deep_sleep, so...
      - logger.log: "<<<<< check-deep_sleep >>>>>  No active reasons to postpone deep_sleep, so activated"
      - logger.log: ""
      - deep_sleep.allow: deep_sleep_1

deep_sleep:
  id: deep_sleep_1
  run_duration:   120000 ms  # how long to run (be awake) = 2 minutes
  sleep_duration:  60000 ms  # how long to sleep for
  wakeup_pin:                # wake up if a rain gauge pulse is detected
    number: GPIO7
    allow_other_uses: true
    inverted: true
    mode:                    # prevent lots of ON/OFF events
      input: true
      pullup: false

esp32:
  board: esp32-s3-devkitm-1  # ESP32-S3-zero from Core Electronics
  variant: esp32s3
  framework:
    type: esp-idf
    version: recommended

logger:
  level: DEBUG      # VERBOSE

packages:
  common_wifi: !include _common_wifi.yaml      # uses the  deviceIP:  substitution

Now to worry about how much keeping the ESP32 awake for the duration of rain will run down the battery :wink:


UPDATE: I found 2 problems with the interaction of deep_sleep and pulse_counter:

  1. on_boot: priority -100 is performed after all the setting up is done, including after wi-fi is established (which at my test bench can be a while) … but I want to capture that first bucket tip asap before the pulse_counter starts up. Moving that code to priority 600 seems to do the trick, even if I can’t see my debugging statements.
  2. the updating of pulse_counter and timing of the deep_sleep awake period are not synchronised, so there could be bucket tips (pulses) which have been counted but not yet published when the ESP32 shuts down. Github’s AI copilot gave several solutions, and apologised every time it admitted that it’s “solution” was based on invalid code.
    It is not possible to publish the pulse_counter sensor in on_shutdown; so I think I will have to begin deep_sleep just after the pulse-counter is published.
1 Like

Beginning deep_sleep just after the pulse_counter is published will mean not using the deep-sleep run_duration.
I have been testing this, and present my example code.

I have created a number template awake_timer to count down the time before beginning deep_sleep. In on_boot it is set to 2 minutes, so that I default to take 2 samples (with update_interval 60 seconds) before going back to deep_sleep.

My rain_gauge uses a pulse_counter so that I don’t have to count each individual pulse. Every 60 seconds it publishes the number of pulses that it has counted. To minimise the chance of deep_sleep starting while there are pulses which have been counted but not published, I begin deep_sleep from the on_value: of the rain_gauge.

If there are pulses, it is raining, and so I reset the awake_time … actually I choose to leave the ESP32 awake for 5 minutes to make sure the rain really has stopped.

The GPIO pin which the pulse_counter is connected to can also be used to wake the ESP32. Make sure you have the same pin: parameters and allow_other_uses: true on both the rain_gauge and deep_sleep components. Note that the pulse which wakes the ESP32 - and any other pulses which happen before the ESP is sufficiently awake - will not be counted, and so I have moved it earlier in the on_boot priority. If you need better accuracy the suggestion is to use a hardware counter.

#####################################################################
#
# The ESP32-S3-mini board is CURRENTLY used for debugging parts of the greenhouse code
#
#####################################################################

substitutions:
  devicename:     "s3-test"                     # last octet of the IP Address
  deviceIP:       "123"                     # last octet of the IP Address
  wifi_ssid:      !secret upstairs_ssid     ### which wi-fi to connect to
  wifi_password:  !secret upstairs_password
  update_interval_sensor:  "1 min"        # How often to measure and report sensor values
  update_interval_network: "5 min"        # How often to measure and report network values
  rain_after_timeout:      5              # how many minutes to wait after rain stops
  awake_time_limit:        2              # how many minutes (rain_gauge cycles) before going back to sleep

esphome:
  name: $devicename
  platformio_options:
    board_build.flash_mode: dio
  on_boot:
    - priority: 600            # most sensors setup, but before wi-fi connected
      then:
        #  2 - ESP_SLEEP_WAKEUP_EXT0: Wakeup caused by external signal using RTC_IO
        - lambda: |-
            id(awake_timer).state = $awake_time_limit;        // set default time to be awake (in minutes)
            if ( int(esp_sleep_get_wakeup_cause()) == 2 ) {
                      // process the rain gauge pulse which woke us up asap so we don't miss more tips
              ESP_LOGD("", "   >>>> RAIN detected while asleep. <<<<" );
              id(rain_gauge).publish_state(1.0);
                      // hopefully the next tip will be recorded by the pulse_counter
              ESP_LOGD(" ", " ");
            };

  on_shutdown:
    - logger.log: "##### on_shutdown -100 finished. "
    - logger.log: "---------------------------------------------------------------------"
    - logger.log: "                                                                     "

sensor:
    # rain gauge hardware sends pulses as the bucket tips.
    # this rain_guage component returns the number of pulses in the preceeding update_interval (1 minute)
  - platform: pulse_counter
    name: Rain Gauge
    id:   rain_gauge
    pin:
      number: GPIO7
      allow_other_uses: true
      inverted: true
      mode:          # prevent lots of ON/OFF events
        input: true
        pullup: true
    update_interval: 60s      # check the rain_gauge every minute 
    on_value:
      then:
        - lambda: |-
            if ( x > 0.0 ) {
                      // YES, rain was detected !
              ESP_LOGD(" ", ">>>>>> NEW RAIN DETECTED counter started, x=%f pulses", x );
              id(deep_sleep_1).prevent_deep_sleep();            // don't go into deep_sleep, because that will stop the rain counting
              id(awake_timer).state = $rain_after_timeout;      // how many minutes to wait after rain stops
            } else if ( id(awake_timer).state < 0.5 ) {
                      // we have timed out, so go to sleep
              ESP_LOGD(" ", ">>>>>> RAIN TIMED OUT <<<<<<   Back to normal.  " );
                      // there was no rain this iteration, so doesn't matter if we sleep before the publish 
                      //    is sent to HA
              id(check_deep_sleep).execute();
            } else {
                      // one more iteration through the loop
              id(awake_timer).state-- ;
            };
        - logger.log: " "

number:
  - platform: template
      # This is actually a counter of minutes to stay awake - counting DOWN.
      #   By default it is set in on_boot:; but is reset when rain is detected:
      #   0 means it is time to go back to deep_sleep
    id: awake_timer
    optimistic: true
    min_value: 0
    max_value: 10
    step: 1

script:
  - id: check_deep_sleep
    then:
      # no outstanding reason to prevent deep_sleep, so... start deep_sleep 
      - logger.log: "<<<<< check-deep_sleep >>>>>  No active reasons to postpone deep_sleep, so activated"
      - deep_sleep.enter:
          id: deep_sleep_1
          sleep_duration: 5min

deep_sleep:
  id: deep_sleep_1
  # timings for deep_sleep moved to be driven by rain_gauge
  wakeup_pin:                # wake up if a rain gauge pulse is detected
    number: GPIO7
    allow_other_uses: true
    inverted: true
    mode:                    # prevent lots of ON/OFF events
      input: true
      pullup: false

esp32:
  board: esp32-s3-devkitm-1  # ESP32-S3-zero from Core Electronics
  variant: esp32s3
  framework:
    type: esp-idf
    version: recommended

logger:
  level: DEBUG      # VERBOSE

packages:
  common_wifi: !include _common_wifi.yaml      # uses the  deviceIP:  substitution