Closed torianironfist closed 2 years ago
Hey there @esphome/core, mind taking a look at this issue as it has been labeled with an integration (binary_sensor
) you are listed as a code owner for? Thanks!
(message by CodeOwnersMention)
If anyone else runs into this, I was able to create a workaround with lambdas. This is only set up to distinguish between single and double clicks, but could be extrapolated further if more complex patterns were needed.
esphome:
on_boot:
then:
- lambda: |-
id(multi_click_time) = millis();
globals:
- id: multi_click_time
type: int
restore_value: no
- id: single_click
type: bool
restore_value: no
binary_sensor:
- platform: gpio
pin:
number: GPIO5
id: switch
on_state:
then:
- if:
condition:
lambda: 'return millis() - id(multi_click_time) > 500;'
then:
- lambda: 'id(multi_click_time) = millis(); id(single_click) = true;'
else:
- lambda: 'id(multi_click_time) = millis();'
- logger.log: "Double Click Detected!"
# Double click action here
- delay: .5s
- if:
condition:
lambda: 'return id(single_click) == true && millis() - id(multi_click_time) > 500;'
then:
- lambda: 'id(single_click) = false;'
- logger.log: "Single Click Detected!"
# Single click action here
else:
- lambda: 'id(single_click) = false;'
Something I have done to get around this was to make a template binary sensor, and turn that binary sensor on, delay 50ms and turn back off again, so basically turning every state change (off to on or on to off) into a click and doing the timing logic in that template binary sensor.
binary_sensor:
- platform: gpio
id: raw_switch
pin:
number: 0
mode: INPUT_PULLUP
on_state:
then:
- binary_sensor.template.publish:
id: switch_clicker
state: ON
- delay: 100ms
- binary_sensor.template.publish:
id: switch_clicker
state: OFF
- platform: template
id: switch_clicker
name: ${friendly_name}
on_multi_click:
- timing:
- ON for at most 0.2s
- OFF for at least 0.5s
then:
# single "click" actions - up or down once only
- timing:
- ON for at most 0.2s
- OFF for at most 0.5s
- ON for at most 0.2s
- OFF for at least 0.5s
then:
# double click actions - up down, or down up
Making the template into a faux momentary switch. Brilliant. That is much easier to read than my code. Thanks very much.
this is great, thx!
This is such an easy work around and this issue doesn't seem to be affecting many people, so I am just going to close this.
For those looking for a complete example, this will give you three entities which binary state will be toggled on a single, double or tripple click.
binary_sensor:
# Wall Swtich 1
- platform: gpio
pin:
number: GPIO14
mode: INPUT
id: sensorid1
filters:
- delayed_on_off: 80ms # Debounce filter to eliminate random false switching.
on_state:
then:
- binary_sensor.template.publish:
id: switch_clicker
state: ON
- delay: 100ms
- binary_sensor.template.publish:
id: switch_clicker
state: OFF
- platform: template
id: switch_clicker
on_multi_click:
- timing:
- ON for at most 0.5s
- OFF for at least 0.5s
then:
- logger.log: "Single Click"
- lambda: |-
if (id(template_sensor_1).state) {
id(template_sensor_1).publish_state(false);
} else {
id(template_sensor_1).publish_state(true);
}
- timing:
- ON for at most 0.5s
- OFF for at most 0.5s
- ON for at most 0.5s
- OFF for at least 0.5s
then:
- logger.log: "Double Click"
- lambda: |-
if (id(template_sensor_2).state) {
id(template_sensor_2).publish_state(false);
} else {
id(template_sensor_2).publish_state(true);
}
- timing:
- ON for at most 0.5s
- OFF for at most 0.5s
- ON for at most 0.5s
- OFF for at most 0.5s
- ON for at most 0.5s
- OFF for at least 0.2s
then:
- logger.log: "Tripple Click"
- lambda: |-
if (id(template_sensor_3).state) {
id(template_sensor_3).publish_state(false);
} else {
id(template_sensor_3).publish_state(true);
}
- platform: template
name: Remote 1 $friendly_name
id: template_sensor_1
- platform: template
name: Remote 2 $friendly_name
id: template_sensor_2
- platform: template
name: Remote 3 $friendly_name
id: template_sensor_3
Hi guys. I came across the same problem but I use the mult-click for a LED strip. The idea was a single press turns it on or off, double click goes to max-min brightness of the LED strip and hold slowly dims up or down the strip till you let go. Besides physical push buttons I use HA too. On both sides HA and wall buttons I have that error and it turns off the LED strip Could you point me to where I messed up here ?
esphome:
name: yard
esp32:
board: esp32dev
# Enable Home Assistant API
api:
#password: !secret api_password
ota:
password: "d38824f1345be0b6847b1085aa7929da"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Yard Fallback Hotspot"
password: "075664688s"
captive_portal:
status_led:
pin:
number: 2
inverted: True
# Enable logging
debug:
logger:
level: VERBOSE #Change the log level as required; during testing verbose is helpful.
baud_rate: 0
globals:
# Za Tenda led strip globals
- id: light_tenda_min_brightness #min brighness for Tenda led
type: float
restore_value: no
initial_value: '0.05'
- id: light_tenda_max_brightness #max brightness za Tenda LED
type: float
restore_value: no
initial_value: '1.0'
- id: dim_direction_tenda #nasoka (pojachuva ili stishuva led lentata) dirrection going up or down
type: int
restore_value: no
initial_value: '1'
- id: TENDA_loop_stop # kraj chitanje taster na najmal ili najgolem brightness (stops loop if at max or min)
type: bool
restore_value: no
initial_value: 'false'
- id: step #dimmer steps
type: float
restore_value: no
initial_value: '0.02'
# sensor to report wifi RSSI
binary_sensor:
# Tenda taster: button for LED strip
- platform: gpio
pin:
number: GPIO36
mode: INPUT
inverted: True
name: "Tenda Taster"
id: light_tenda_button
# we use multi_click to get both single and double click
# single click to turn on and off (toggle)
# double click to set either max or min brightness: if currently off then turn on with brightness=max. If currently on, then if currently brigthness equals max then set to min, else set to max
on_multi_click:
- timing:
- ON for at most 1s
- OFF for at most 1s
- ON for at most 1s
- OFF for at least 0.2s
then:
- logger.log: "on_multi_click: dupli klik max min"
- if:
condition:
light.is_off: tenda_led
then:
- light.turn_on:
id: tenda_led
brightness: !lambda |-
return id(light_tenda_max_brightness);
- delay: 1s
- if:
condition:
light.is_on: tenda_led
then:
- light.turn_on:
id: tenda_led
# if currently off then turn on with brightness=max. If currently on, then if currently brigthness equals max then set to min, else set to max:
brightness: !lambda |-
float brightness;
id(tenda_led).current_values_as_brightness(&brightness);
if (brightness >= id(light_tenda_max_brightness)) {
return id(light_tenda_min_brightness);
}
else {
return id(light_tenda_max_brightness);
}
- delay: 1s
# single click to turn on and off (toggle):
- timing:
- ON for at most 1s
- OFF for at least 0.5s
then:
- logger.log: "on_multi_click: pali ili gasi eden stisok"
- light.toggle: tenda_led
- delay: 1s
# Long press to dim up and down:
# on_multi_click:
- timing:
- ON for at least 2s
- OFF for at least 0.5s
then:
- logger.log: "pijachuva ili stishuva"
# we need this delay (and then to look for binary_sensor.is_on) to ensure it's not a multi-click
- delay: 0.8s
- if:
condition:
and:
- light.is_off: tenda_led
- binary_sensor.is_on: light_tenda_button
then:
# if the light was off we also want the long press to turn it on and then start dimming
- light.toggle: tenda_led
# - delay: 0.75s
#
# this sets the globales variable that will stop the while loop from needlessly running if the buttons keeps being pressed but ar are already at max or min value
- lambda: |-
id(TENDA_loop_stop) = false;
- while:
# keep on going as long as the button is pressed and when the max/min threshold is not exceeded yet (indicated by the global var of type bool)
condition:
and:
- binary_sensor.is_on: light_tenda_button
- lambda: |-
return(!id(TENDA_loop_stop));
then:
- light.dim_relative:
id: tenda_led
# This lambda will dim up or down depending on the dim direction
# if dim = up, it will keep increasing brightness with global var 'step' (so we can easily change this in a central place) as long as the button keeps being pressed until max_brightness would be exceed
# in the case it will set it to max brightness, beep the buzzer once (the buzzer is defined as a light with effect "beeponce") and then stop the loop - this both prevent further needless loops as well as ensures the buzzer only buzzes once
# if dim = down, it will keep decreasing brightness with global var 'step' (so we can easily change this in a central place) as long as the button keeps being pressed until min_brightness would be exceed
# in the case it will set it to min brightness, beep the buzzer once (the buzzer is defined as a light with effect "beeponce") and then stop the loop - this both prevent further needless loops as well as ensures the buzzer only buzzes once
relative_brightness: !lambda |-
float brightness;
float gamma;
id(tenda_led).current_values_as_brightness(&brightness);
// this is the gamma corrected value - we need to 'un'compensate for gamma to get a lineair %value
gamma = id(tenda_led).get_gamma_correct();
brightness = powf(brightness,1/gamma);
//id(tenda_led).dump_config();
ESP_LOGD("main", "while loop, dim=%d, brightness=%6.4lf, step=%6.4lf, max=%6.4lf, gamma=%6.4lf", id(dim_direction_tenda), brightness,id(step),id(light_tenda_max_brightness),gamma);
if (brightness >= id(light_tenda_max_brightness)) {
// if we are at max already, no point in dimming up - this will ever happen at loop start because of the loopstop break, so no issues with the loop
id(dim_direction_tenda) = '0';
}
else if (brightness <= id(light_tenda_min_brightness)) {
// if we are at min already, no point in dimming down - this will ever happen at loop start because of the loopstop break, so no issues with the loop
id(dim_direction_tenda) = '1';
}
if (id(dim_direction_tenda) == '1') {
if ((id(step) + brightness) <= id(light_tenda_max_brightness)) {
return(id(step));
}
else {
// auto call = id(light_buzzer).turn_on();
// call.set_effect("beeponce");
// call.perform();
id(TENDA_loop_stop) = true;
return(id(light_tenda_max_brightness)- brightness);
}
}
else if (id(dim_direction_tenda) != '1') {
if ((brightness - id(step)) >= id(light_tenda_min_brightness)) {
return(-id(step));
}
else {
// auto call = id(light_buzzer).turn_on();
// call.set_effect("beeponce");
// call.perform();
id(TENDA_loop_stop) = true;
return(id(light_tenda_min_brightness)- brightness);
}
}
// the else is strictly speaking not needed as the loop should break before this condition is true but if for whatever reason that would not happen at least we are returning something
else {
return(0);
}
transition_length: 0.05s
- delay: 0.5s
# now toggle the dim up/down direction so next time we do the opposite action
- globals.set:
id: dim_direction_tenda
value: !lambda |-
if (id(dim_direction_tenda) == '1') {
return('0');
} else {
return('1');
}
- platform: gpio
pin:
number: GPIO39
mode: INPUT
inverted: True
filters:
- delayed_on: 50ms
name: "Radio Taster"
id: button_radio
on_press:
then:
- if:
condition:
light.is_off: light_radio
then:
- light.turn_on: light_radio
- delay: 1s
else:
- light.turn_off: light_radio
- delay: 1s
- platform: gpio
pin:
number: GPIO34
mode: INPUT
inverted: True
filters:
- delayed_on: 50ms
name: "Fasada Kupatilo Taster"
id: button_fasada_kupatilo
on_press:
then:
- if:
condition:
light.is_off: light_fasada_kupatilo
then:
- light.turn_on: light_fasada_kupatilo
- delay: 1s
else:
- light.turn_off: light_fasada_kupatilo
- delay: 1s
- platform: gpio
pin:
number: GPIO35
mode: INPUT
id: fontana_switch
inverted: True
filters:
- delayed_on: 50ms
name: "Fontana Taster"
on_press:
then:
- if:
condition:
switch.is_off: fontana
then:
- switch.turn_on: fontana
- delay: 1s
else:
- switch.turn_off: fontana
- delay: 1s
- platform: gpio
pin:
number: GPIO13
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on: 50ms
name: "Fasada Splana Taster"
id: button_fasada_spalna
on_press:
then:
- if:
condition:
light.is_off: fasada_spalna
then:
- light.turn_on:
id: "fasada_spalna"
else:
- light.turn_off: fasada_spalna
- delay: 1s
# - platform: gpio
# pin:
# number: 15
# mode: INPUT_PULLUP
# inverted: True
# name: "Button 3"
# filters:
# - delayed_on: 50ms
# internal: True
# id: Switch_1_button
# on_click:
# then:
# - switch.toggle: "switch_1"
# - delay: 1s
#
#switch:
# - platform: gpio
# pin: 12
# name: "Switch 1"
# id: "switch_1"
# this provides fr a possibility to rstart from the web console or Home automation should we ever need it
# - platform: restart
# name: "Restart"
output:
- platform: gpio
pin: GPIO32
id: radio
inverted: true
- platform: gpio
pin: GPIO33
id: fasada_kupatilo
inverted: true
- platform: ledc
pin: GPIO22
frequency: 2441 Hz #2000 Hz promeneto
id: pwm_w1
- platform: gpio
pin: GPIO26
id: light_fasada_spalna
inverted: true
light:
- platform: binary
name: "Fasada Svetilki Spalna"
output: light_fasada_spalna
id: fasada_spalna
- platform: binary
name: "Radio"
output: radio
id: light_radio
icon: "mdi:speaker-wireless"
- platform: binary
name: "Fasada Kupatilo Svetilki"
output: fasada_kupatilo
id: light_fasada_kupatilo
- platform: monochromatic
name: "Tenda"
output: pwm_w1
gamma_correct: 2.0
id: tenda_led
switch:
- platform: gpio
pin: GPIO25
inverted: true
id: fontana
icon: "mdi:fountain"
name: "Fontana"
[18:10:39][D][main:165]: while loop, dim=48, brightness=0.3400, step=0.0200, max=1.0000, gamma=2.0000
[18:10:39][D][light:035]: 'Tenda' Setting:
[18:10:39][D][light:050]: Brightness: 32%
[18:10:39][D][light:084]: Transition length: 0.1s
[18:10:40][D][main:165]: while loop, dim=48, brightness=0.3200, step=0.0200, max=1.0000, gamma=2.0000
[18:10:40][D][light:035]: 'Tenda' Setting:
[18:10:40][D][light:050]: Brightness: 30%
[18:10:40][D][light:084]: Transition length: 0.1s
[18:10:40][D][binary_sensor:036]: 'Tenda Taster': Sending state OFF
[18:10:40][V][binary_sensor.automation:035]: Multi Click: action not started because first level does not match!
[18:10:40][V][binary_sensor.automation:035]: Multi Click: action not started because first level does not match!
[18:10:40][V][binary_sensor.automation:062]: C i=1 min=500
[18:10:40][V][binary_sensor.automation:101]: Multi Click: Hooray, multi click is valid. Triggering!
[18:10:40][D][main:152]: pijachuva ili stishuva
[18:10:46][D][binary_sensor:036]: 'Tenda Taster': Sending state ON
[18:10:46][V][binary_sensor.automation:025]: START min=0 max=1000
[18:10:46][V][binary_sensor.automation:026]: Multi Click: Starting multi click action!
[18:10:46][V][binary_sensor.automation:025]: START min=0 max=1000
[18:10:46][V][binary_sensor.automation:026]: Multi Click: Starting multi click action!
[18:10:46][V][binary_sensor.automation:025]: START min=2000 max=4294967294
[18:10:46][V][binary_sensor.automation:026]: Multi Click: Starting multi click action!
[18:10:46][V][component:199]: Component gpio.binary_sensor took a long time for an operation (0.06 s).
[18:10:46][V][component:200]: Components should block for at most 20-30ms.
[18:10:46][D][binary_sensor:036]: 'Tenda Taster': Sending state OFF
[18:10:46][V][binary_sensor.automation:054]: A i=1 min=0 max=1000
[18:10:46][V][binary_sensor.automation:062]: C i=1 min=500
[18:10:46][V][binary_sensor.automation:071]: Multi Click: Invalid length of press, starting cooldown of 1000 ms...
[18:10:46][V][binary_sensor.automation:101]: Multi Click: Hooray, multi click is valid. Triggering!
[18:10:46][D][main:130]: on_multi_click: pali ili gasi eden stisok
[18:10:46][D][light:035]: 'Tenda' Setting:
[18:10:46][D][light:046]: State: OFF
[18:10:46][D][light:084]: Transition length: 1.0s
[18:10:47][V][binary_sensor.automation:095]: Multi Click: You waited too long to PRESS.
[18:10:47][V][binary_sensor.automation:071]: Multi Click: Invalid length of press, starting cooldown of 1000 ms...
[18:10:47][V][binary_sensor.automation:074]: Multi Click: Cooldown ended, matching is now enabled again.
[18:10:48][V][binary_sensor.automation:074]: Multi Click: Cooldown ended, matching is now enabled again.
[18:11:27][D][esp32.preferences:114]: Saving preferences to flash...
The problem
I have a standard toggle (not momentary) switch at the top of my basement stairs with a Shelly 1PM installed. I would like to use this switch to switch the relay normally when switched once and turn off all the basement lights when switched twice quickly. I wrote some yaml for a binary sensor using on_multi_click.
It works with a couple problems, the first switch after booting does not trigger an action and the next switch after the "double click" action also does not trigger.
As far as I can tell, the issue is starting a multi-click timing with the "at least" grammar. When first booted or flashed, the timing is not initialized, so it cannot time "at least" until the switch has been toggled once and it appears a similar thing happens after the "double click", it no longer keeps track of the timing between the last switch.
Which version of ESPHome has the issue?
2021.12.0
What type of installation are you using?
Docker
Which version of Home Assistant has the issue?
2021.12.1
What platform are you using?
ESP8266
Board
Shelly 1PM
Component causing the issue
Binary Sensor
Example YAML snippet
Anything in the logs that might be useful for us?
Additional information
No response