Long-press a button on ESP32 and execute while button is still pressed

I’ve got a sensor I’m developing that will sound an alarm when something happens. I’ve got a physical momentary button wired to the GPIO and I have programmed a binary_sensor to read that input. When the button is pushed, it snoozes the alarm for a preset amount of time. This works great. Now I’m looking to add functionality to be able to cancel that snooze by long-pressing the same button. However, I don’t like the idea of setting up on_click to a specified time because I want the siren to chirp to let me know that I’ve held the button long enough. Sort of like when you reset most consumer electronics devices: you hold the reset button until you see a light flash or hear a beep, and then you let go of the reset button. This is the behavior I desire.

I tried adding the while block and all the code after it, but it didn’t seem to have any effect. Anyone have suggestions on what I’m doing wrong or a different method to achieve this?

binary_sensor:
  # Physical snooze button for alarm (if primary pump isn't working)
  - platform: gpio
    pin:
      number: 23
      mode:
        input: true
        pullup: true
      inverted: true
    name: "${friendly_name} Alarm Snooze Physical Button"
    id: physical_snooze_button
    internal: true
    on_press:
      # Set snooze by pressing the virtual snooze button
      - button.press: virtual_snooze_button
      # If the physical snooze is held, cancel the snooze after 3 sec & chirp the alarm
      - while:
          condition: 
            binary_sensor.is_on: physical_snooze_button
          then:
          - lambda: |-
              static uint32_t begin_press = 0;
              if (begin_press == 0) {
                // button just pressed
                begin_press = millis();
              } else if (millis() - begin_press > 3000) {
                // button held for 3s, so cancel the snooze
                id(snooze_expiration_timestamp) = 0.0;
                // Chirp the alarm so you know the snooze is reset
                id(alarm_chirp).execute();
              }

Turns out the while loop caused my ESP to crash after a few hundred milliseconds. I added a delay after the lambda block and it runs without crashing now. And once I realized I could do that, I removed the millis() functions and just used a simple loop counter.

It would be nice if ESPHome provided an on_hold functionality to allow for this use case more easily, but the workaround is easy enough.

Here’s the simplified version of code I ended up with:

binary_sensor:
  # Physical snooze button for alarm (if primary pump isn't working)
  - platform: gpio
    pin:
      number: 23
      mode:
        input: true
        pullup: true
      inverted: true
    id: physical_snooze_button
    internal: true
    on_press:
      # Set snooze by pressing the virtual snooze button
      - button.press: virtual_snooze_button
      # If the physical snooze is held, cancel the snooze after 3 sec & chirp the alarm
      - while:
          condition: 
            binary_sensor.is_on: physical_snooze_button
          then:
          - lambda: |-
              if (id(alarm_snooze_loops) == 3) {
                // button held for 3s, so cancel the snooze
                id(snooze_expiration_timestamp) = 0.0;
                // Chirp the alarm so you know the snooze is reset
                id(alarm_chirp).execute();
              }
              id(alarm_snooze_loops) += 1;
          - delay: 1s
    on_release:
     - lambda: id(alarm_snooze_loops) = 0;

globals:
  - id: alarm_snooze_loops
    type: int
    initial_value: '0'
    restore_value: false
1 Like