Long button press via GPIO binary sensor -> How?

Hello Community,

I am trying to use a simple momentary button as a switch with double functionality. The switch is connected via one of the GPIO pins of the raspberry Pi. A short press (click) should toggle the main room light on and off whereas and a long button press (>=2s) should trigger the “Ambient Light” scene or when it is already triggered turn all light off.

Unfortunately what I tried didn’t work out so far and besides solutions for ESPHome and xiaomi_aqara my search luck was not with me, so your help is highly appreciated!

What did you try?

What comes to mind is two automations.

  1. Long press (more than 3 seconds):
trigger
  platform: state
  entity_id: binary_sensor.gpio_xx
  to: 'on'
  for:
    seconds: 3 
action:
...
  1. Short press (less than 1 second):
trigger
  platform: state
  entity_id: binary_sensor.gpio_xx
  to: 'off' 
condition:
  condition: template
  value_template: "{{ (as_timestamp(now())-as_timestamp(states.binary_sensor.gpio_xx.last_changed)) < 1 }}"
action:
...

There has been work done on the following issue that may prevent the short press being detected but no feedback as to whether or not it has been fixed:

https://github.com/home-assistant/home-assistant/issues/10498

@tom_l thank you very much for your fast response! I already tried to solve it with two automations, one with a for condition but I was missing the template condition. Thank you for that hint.

Unfortunately it is not working completely as it should, at least not yet. A short button press works just fine and triggers the “Light Mode Short” automation as expected. But regardless of one single short or long button press the “Light Mode Short automation” action gets always triggered. So even a long button press triggers it. How can this effectively be avoided?

configuration.yaml

#...
binary_sensor:
  - platform: rpi_gpio
    ports:
      5: Light Mode Button
    invert_logic: true  # button is NC, this makes it act NO
    bouncetime: 50  # just to be sure I added a hardware low-pass filter as well
    pull_mode: "UP"
#...

automations.yaml

- alias: Light Mode Short  # toggle main light on and off
  trigger:
    - platform: state
      entity_id: binary_sensor.light_mode_button
      #from: 'off'
      #to: 'on'
      from: 'on'
      to: 'off'
  condition:
    condition: template
    value_template: "{{ (as_timestamp(now())-as_timestamp(states.binary_sensor.light_mode_button.last_changed)) < 1 }}"
  action:
    - service: switch.toggle
      entity_id: switch.sonoff_basic_relay

- alias: Light Mode Long  # scene Ambient (ToDo: toggle between Ambient and Off)
  trigger:
    platform: state
    entity_id: binary_sensor.light_mode_button
    from: 'off'
    to: 'on'
    #from: 'on'
    #to: 'off'
    for:
      seconds: 2
  action:
    - service: scene.turn_on
      entity_id: scene.Ambient
1 Like

Try the two automations I posted. This should avoid the short press triggering when a long press is done because it only triggers when the button is released and then checks how long it was held for using the condition.

I did that and to make sure I tested it again (also removing the “from: ‘…’” in my code posted above).

The result of a Long button press is that the right automation “Light Mode Long” gets triggered but also as soon as I release the button the “Light Mode Short” automation gets triggered.

Hey there @tom_l,
I tried your code like this but unfortunately it does not work, any clues?

Short press automation:

- id: '1577539526085'
  alias: DN BR Button 1s M
  description: ''
  trigger:
  - entity_id: binary_sensor.dn_br_button
    from: 'on'
    platform: state
    to: 'off'
  condition:
  - condition: template
    value_template: "{{ (as_timestamp(now())-as_timestamp(states.binary_sensor.dn_br_button.last_changed)) < 1 }}"
#  - condition: state
#    entity_id: input_select.dn_br_radiator_mode
#    state: M
  action:
  - data:
      entity_id: switch.radiator_dn_br_timer
    service: switch.toggle

Long press automation:

- id: '1577539109668'
  alias: DN BR Button on 3s
  description: ''
  trigger:
  - entity_id: binary_sensor.dn_br_button
    for: 00:00:03
    from: 'off'
    platform: state
    to: 'on'
  condition: []
  action:
...

The long press one works correctly, only triggers if i push the button for more than 3 sec but the short press one always triggers. It does when i short press the button but it also does when i let it go, after pushing it for more than 3sec.
Which means it triggers right after the long press one.

It’s like the condition is ignored and always triggers when i let go of the button, irregardless of the condition set.
Any ideas?
Thanks in advance.

The template editor correctly gives me false for the above statement {{ (as_timestamp(now())-as_timestamp(states.binary_sensor.dn_br_button.last_changed)) < 1 }}

Edit: i think that after a long press, say 5-6 sec, the statement is also true, as it counts the last_changed moment as the release of the button, so it is again under one second. Right?

Yeah not sure what I was thinking there. That short press template will always be true due to the off transition.

The template editor wont show it as true unless you you refresh the editor right after pressing the button.

I think this will work as a short press trigger (less than 1 second) but is untested:

trigger
  platform: state
  entity_id: binary_sensor.gpio_xx
  to: 'on' 
action:
- delay:
    seconds: 1
- condition: state
  entity_id: binary_sensor.gpio_xx
  state: off
- # your actions here

Hey there, Thanks for the immediate input :slight_smile:

I took a different approach to differentiate long and short presses of a button, using hass variables add-on

My radiator configuration is a relay hooked up on my dumb thermostat that bypasses it.
Near the thermostat there’s a project box with an ESPhome flashed nodemcu, with

  • a button switch,
  • a relay, a DHT sensor,
  • a two line lcd screen that shows temp/humidity/status and operation mode (thermostat/timer) and
  • a PIR sensor (to turn the lcd backlight on/off - because why not?)

It’s ugly as hell but its mine and i love it :stuck_out_tongue:

It uses long press to switch between timer or thermostat operation and short press to enable whichever mode is active:

Here’s just the long/short press stuff:

#defining a variable which will store the starting time of a press
variable: #https://github.com/rogro82/hass-variables
  dn_br_button_last_press:
    value: 0

automation:
#here I store the starting time to the said variable
  - id: "dn_br_button_set_press_time"
    alias: DN BR Button set press time
    trigger:
    - entity_id: binary_sensor.dn_br_button
      from: 'off'
      platform: state
      to: 'on'
    condition: []
    action:
    - service: variable.set_variable
      data:
        variable: "dn_br_button_last_press"
        value_template: "{{ as_timestamp(now()) }}"
  
#and this handles the button release, using a script which will do different things according to the time that has passed    
  - id: "dn_br_button_release"
    alias: DN BR Button Release
    trigger:
    - entity_id: binary_sensor.dn_br_button
      from: 'on'
      platform: state
      to: 'off'
    condition: []
    action:
    - service: script.turn_on
      data:
        entity_id: script.dn_br_button_release

script:
#choosing which script to use depending on time
  dn_br_button_release:
    alias: "dn br button release script"
    sequence:
      - service: script.turn_on
        data_template:
          entity_id: >
            {% if (  (as_timestamp(now())|int - states.variable.dn_br_button_last_press.state|int)>3) %}
              script.dn_br_long_press
            {% else %}
              script.dn_br_short_press
            {% endif %}

  #long press script does something
  dn_br_long_press:
    alias: "dn br button long press script"
    sequence:
      - data:
          entity_id: switch.dn_br_radiator_switch
        service: switch.turn_off
      - data:
          entity_id: switch.radiator_dn_br_timer
        service: switch.turn_off
      - data:
          entity_id: switch.radiator_upstairs_thermostat
        service: switch.turn_off
      - data:
          entity_id: input_select.dn_br_radiator_mode
        service: input_select.select_next

  #short press script does something else
  dn_br_short_press:
    alias: "dn br button short press script"
    sequence:
      - service: switch.toggle
        data_template:
          entity_id: >
            {% if is_state("input_select.dn_br_radiator_mode", "M") %}
              switch.radiator_dn_br_timer
            {% else %}
              switch.radiator_dn_br_thermostat
            {% endif %}

Both of those actions fire when the user releases the button.
There is also the option to fire the long press action while the button is still being pressed but the pre-set time has passed, which feels nice. That will have to be done by removing the long press script and handling its functionality with a second automation that would trigger on binary_sensor testing from off -> on and adding for: 00.00.03 for example. The automation would fire while the button is still depressed and nothing will happen when you finally let the button go. (have not implemented it though, im still just imagining it :stuck_out_tongue: )

Here is my full config, in case someone wants something similar:

# set variable
variable: #https://github.com/rogro82/hass-variables
  dn_br_button_last_press:
    value: 0

#set input to select automatic (thermostat) or manual (timer) mode
input_select:
  
  dn_br_radiator_mode:
    name: dn_br_radiator_mode
    initial: M
    options:
      - M
      - A

#timer duration set through UI
input_number:
  radiator_timer_dn_br:
    name: radiator_timer_dn_br
    initial: 90
    min: 15
    max: 180
    step: 15
    mode: box
    icon: mdi:camera-timer



switch:

  - platform: template
    switches:
#main switch that handles on/off    with timer
      radiator_dn_br_timer:
        value_template: "{{ is_state('script.dn_br_radiator_auto_on' , 'on') }}"
        turn_on:
        - service: script.turn_on
          entity_id: script.dn_br_radiator_auto_on
        - service: climate.turn_off
          entity_id: climate.climate_dn_br
        - service: input_select.select_option
          data:
            entity_id: input_select.dn_br_radiator_mod
            option: M
        turn_off:
        - service: script.turn_on
          entity_id: script.dn_br_radiator_auto_off
        - service: script.turn_off
          entity_id: script.dn_br_radiator_auto_on
          
     
      #switch that turns radiator on/off with thermostat and kills all other stuff    
      radiator_dn_br_thermostat:
        value_template: "{{ is_state('climate.climate_dn_br' , 'idle') or is_state('climate.climate_dn_br' , 'heat') }}"
        turn_on:
        - service: script.turn_on
          entity_id: script.dn_br_radiator_auto_off 
        - service: script.turn_off
          entity_id: script.dn_br_radiator_auto_on
        - service: climate.turn_on
          entity_id: climate.climate_dn_br

        turn_off:
        - service: climate.turn_off
          entity_id: climate.climate_dn_br


#climate being used for thermostat operation

climate:
  - platform: generic_thermostat
    name: Climate DN BR
    heater: switch.dn_br_radiator_switch
    target_sensor: sensor.dn_br_sensor_temperature




script:
  dn_br_radiator_auto_on:
    sequence: 
      - service: switch.turn_on
        entity_id: switch.dn_br_radiator_switch
      - delay: '00:{{ states.input_number.radiator_timer_dn_br.state | int }}:00'
        # seconds: 3600
      - service: switch.turn_off
        entity_id: switch.dn_br_radiator_switch
        
        # - delay: '00:{{ states.input_number.radiator_timer_upstairs.state | int }}:00'
  
  ######## SIMPLE OFF  
  dn_br_radiator_auto_off:
    sequence:
      - service: switch.turn_off
        entity_id: switch.dn_br_radiator_switch
      - service: script.turn_off
        entity_id: script.dn_br_radiator_auto_on
  
  dn_br_button_release:
    alias: "dn br button release script"
    sequence:
      - service: script.turn_on
        data_template:
          entity_id: >
            {% if (  (as_timestamp(now())|int - states.variable.dn_br_button_last_press.state|int)>3) %}
              script.dn_br_long_press
            {% else %}
              script.dn_br_short_press
            {% endif %}
    
  dn_br_long_press:
    alias: "dn br button long press script"
    sequence:
      - data:
          entity_id: switch.dn_br_radiator_switch
        service: switch.turn_off
      - data:
          entity_id: switch.radiator_dn_br_timer
        service: switch.turn_off
      - data:
          entity_id: switch.radiator_upstairs_thermostat
        service: switch.turn_off
      - data:
          entity_id: input_select.dn_br_radiator_mode
        service: input_select.select_next
  dn_br_short_press:
    alias: "dn br button short press script"
    sequence:
      - service: switch.toggle
        data_template:
          entity_id: >
            {% if is_state("input_select.dn_br_radiator_mode", "M") %}
              switch.radiator_dn_br_timer
            {% else %}
              switch.radiator_dn_br_thermostat
            {% endif %}
            



automation:
  - id: "dn_br_button_set_press_time"
    alias: DN BR Button set press time
    trigger:
    - entity_id: binary_sensor.dn_br_button
      from: 'off'
      platform: state
      to: 'on'
    condition: []
    action:
    - service: variable.set_variable
      data:
        variable: "dn_br_button_last_press"
        value_template: "{{ as_timestamp(now()) }}"
      
  - id: "dn_br_button_release"
    alias: DN BR Button Release
    trigger:
    - entity_id: binary_sensor.dn_br_button
      from: 'on'
      platform: state
      to: 'off'
    condition: []
    action:
    - service: script.turn_on
      data:
        entity_id: script.dn_br_button_release

Let me know if you are also interested in the ESPhome yaml.

Thanks!

1 Like

@krash You are No.1 :+1:
I was trying to implement sthg like this to reboot/shutdown hassio host on RPi with SSD installed in the case with X820.
Your solution is far way better than mine :grinning:

Happy to help buddy :slight_smile:

@krash After some testing I must tell it works in the same way as my simplier solution, i.e. not reliable.
It is caused by GPIO binary_sensor nature. It does not behave as a push button but toggles every push.

Nonetheless I’ve found a simple trick - 1uF electrolytic capacitor between GPIOs (in my case 17 and GND) now button works like a charm :grinning:

In parallel to the push button?
Can you explain it better for the dummy me?

Yes. In parallel to the push button.
:heavy_minus_sign: to GND and :heavy_plus_sign: to GPIO of interest.

Hassio config:

binary_sensor:
  - platform: rpi_gpio
    ports:
      17: dn_br_button
    invert_logic: true
    pull_mode: 'UP'
    bouncetime: 50

Rest works with your variables and my automations :wink:.

automation:
  - id: 'elapsed_time'
    alias: Elapsed Time
    trigger:
      platform: time_pattern
      seconds: '*'
    condition:
      condition: state
      entity_id: binary_sensor.dn_br_button
      state: 'on'
    action:
      service: homeassistant.update_entity
      entity_id: sensor.elapsed_time

  - id: "fire_service_long_press"    
    alias: "dn br button fire service after time elapsed "
    trigger:
    - entity_id: sensor.elapsed_time
      platform: numeric_state
      above: 5
    action:
    - service: script.turn_on
      entity_id:
            script.dn_br_long_press

  - id: "fire_service_short_press"    
    alias: "dn br button fire service after short press "
    trigger:
    - entity_id: binary_sensor.dn_br_button
      from: 'on'
      platform: state
      to: 'off'
    condition:
    - condition: template
      value_template: '{{ ( (as_timestamp(now())|int - states.variable.dn_br_button_last_press.state|int)<2) }}'
    action:
    - service: script.turn_on
      entity_id:
            script.dn_br_short_press

Short press fires after the button release
Long press fires if lasts “longer than”

Still testing on my spare RPi, so far no problem, no “phantom” press …

Hello people
I need to do the following. Pressing a button for at least 1 second it turns on a lamp if I press it for less than a second it turns on another lamp. The long press is working but the short press is not working. Can someone help me

- id: '1589717840420'
  alias: botao4
  trigger:
  - entity_id: binary_sensor.botao_saida_4
    from: 'on'
    platform: state
    to: 'off'
  condition:
  - condition: template
    value_template: '{{ ( (as_timestamp(now())|int - states.binary_sensor.botao_saida_4.state|int)<1) }}'
  action:
  - data:
      entity_id: switch.saida_4
    service: homeassistant.toggle

- id: '1589725647883'
  alias: long press
  description: ''
  trigger:
  - entity_id: binary_sensor.botao_saida_4
    for: '1'
    from: 'off'
    platform: state
    to: 'on'
  condition: []
  action:
  - data: {}
    entity_id: switch.saida_3
    service: switch.toggle