I’ve looked but I can’t find this answer anywhere. It’s probably a quite niche issue but I can’t figure it out.
I’ve got an RGB light that I stuck an ESP32 in to be able to control it. Originally this light also has a button for ordinary physical control, switching effects, turning it on and off etc. This is an IKEA AFTONSPARV Planet lamp by the way.
So I removed the original MCU, connected the PWM signals and the button input, made the yaml and everything is working as it should. But now what I want, is to be able to “simulate” a button press via Home Assistant/ESPHome web interface, in the same way as it’s received by ESPHome from the real button. Meaning the same reaction to multiple presses, double clicking etc. The only things I can find online is activating a switch from home assistant but this is an RGB light with a bunch of special configuration for the particular button presses.
Any hints are appreciated.
See here my current yaml:
substitutions:
name: saturn-lamp
friendly_name: Saturn Lamp
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
esphome:
name: ${name}
friendly_name: ${friendly_name}
web_server:
port: 80
<<: !include includes/common.yaml
globals:
- id: active_effect
type: int
restore_value: yes
initial_value: '0'
- id: active_color
type: int
restore_value: yes
initial_value: '0'
switch:
- platform: gpio
id: onboard_led
pin:
number: GPIO8
mode: OUTPUT
restore_mode: ALWAYS_OFF
binary_sensor:
- platform: gpio
name: "Saturn Button"
id: saturn_button
pin:
number: GPIO3
mode: INPUT_PULLUP
inverted: True
on_multi_click:
- timing:
- ON for at most 1s
- OFF for at most 0.3s
- ON for at most 1s
- OFF for at least 0.2s
then:
- lambda: !lambda |-
id(active_effect) += 1;
auto call = id(saturn_ring).turn_on();
switch (id(active_effect)) {
case 1:
call.set_effect("Fast Random Effect");
break;
case 2:
call.set_effect("Strobe");
break;
default:
id(active_effect) = 0;
call.set_effect("None");
break;
}
call.perform();
- timing:
- ON for at most 1s
- OFF for at least 0.5s
then:
- lambda: !lambda |-
id(active_color) += 1;
auto call = id(saturn_ring).turn_on();
switch (id(active_color)) {
case 1:
call.set_effect("None");
call.set_rgb(0.20,0.20,0.20);
break;
case 2:
call.set_effect("None");
call.set_rgb(1.0,0.0,0.0);
break;
case 3:
call.set_effect("None");
call.set_rgb(0.00,1.00,0.00);
break;
case 4:
call.set_effect("None");
call.set_rgb(0.0,0.0,1.0);
break;
default:
call.set_effect("None");
id(active_color) = 0;
call.set_rgb(0.00,0.00,0.00);
break;
}
call.perform();
output:
- platform: ledc
pin: GPIO10
id: pwm_2
frequency: 100 Hz
max_power: 100%
- platform: ledc
pin: GPIO20
id: pwm_1
frequency: 100 Hz
max_power: 100%
- platform: ledc
pin: GPIO21
id: pwm_3
frequency: 100 Hz
max_power: 100%
light:
- platform: rgb
red: pwm_1
green: pwm_2
blue: pwm_3
name: "Saturn Ring"
id: saturn_ring
effects:
- random:
name: "Slow Random Effect"
transition_length: 10s
update_interval: 10s
- random:
name: "Fast Random Effect"
transition_length: 4s
update_interval: 5s
- strobe:
name: "Police Strobe"
colors:
- state: True
brightness: 100%
red: 100%
green: 0%
blue: 0%
duration: 500ms
- state: False
duration: 250ms
- state: True
brightness: 100%
red: 0%
green: 0%
blue: 100%
duration: 500ms
Unfortunately not, since that only works on things that are configured as button: in ESPHome, while the physical button is a binary_sensor: as you can see:
But it does make me think, can I make a template button that somehow gets activated by the binary sensor? I could make all of this work if I made Home Assistant do all the work but I want it to be as self-contained as possible, so that all functionality still works even without HA, or WiFi.
Ah, yes, that might complicate it a bit, but not make it impossible.
You can configure a ‘template button’ in ESPHome, which it will expose as a Button (to HA). In its on_press… triggers, you’ll need to make the same calls to the other logic as does your binary_sensor. That would mean replicating all your event logic - not a good approach.
Alternatively, you can ‘push’ the binary sensor as though its GPIO were triggering it.
I believe here is the kind of code that will help you do this.
I would say move your main actions to an esphome script then you can have whatever you want calling that centralised script easily whether it be a virtual or physical button.
I liked this idea (since it was the easiest to modify to) but unfortunately that seems to be not compatible with my other requirement, the on_multi_click: functionality:
[on_multi_click] is an invalid option for [button.template]. Please check the indentation.
Looks like the Button Component — ESPHome only supports on_press: and nothing further (since normally it would be clicked from the web interface/HA so multi-click and hold isn’t even looked for probably).
It’s looking like I’m going to have to rewrite the whole thing from scratch to be able to do this, (doubling up the actions), which will have (mostly) the same end result but it’s not very elegant. So I will make a software button for single press, and another software button for double press. (I don’t need any other multi-click possibilities). Then copy-paste the actions (or put them in a script as @Mahko_Mahko suggested). Let me try this out and I’ll post my results.
Got it working by combining both of your solutions, unfortunately I can only mark one as a solution so first come first serve
My complete yaml is now as follows:
substitutions:
name: saturn-lamp
friendly_name: Saturn Lamp
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
esphome:
name: ${name}
friendly_name: ${friendly_name}
web_server:
port: 80
<<: !include includes/common.yaml
globals:
- id: active_effect
type: int
initial_value: '0'
- id: active_color
type: int
initial_value: '0'
switch:
- platform: gpio
id: onboard_led
pin:
number: GPIO8
mode: OUTPUT
ignore_strapping_warning: true
restore_mode: ALWAYS_OFF
script:
- id: single_button
mode: restart
then:
- lambda: !lambda |-
id(active_color) += 1;
auto call = id(saturn_ring).turn_on();
switch (id(active_color)) {
case 1:
call.set_effect("None");
call.set_rgb(1.0,1.0,1.0);
break;
case 2:
call.set_effect("None");
call.set_rgb(1.0,0.0,0.0);
break;
case 3:
call.set_effect("None");
call.set_rgb(1.0,1.0,0.0);
break;
case 4:
call.set_effect("None");
call.set_rgb(0.0,1.0,0.0);
break;
case 5:
call.set_effect("None");
call.set_rgb(0.0,1.0,1.0);
break;
case 6:
call.set_effect("None");
call.set_rgb(0.0,0.0,1.0);
break;
case 7:
call.set_effect("None");
call.set_rgb(1.0,0.0,1.0);
id(active_color) = 0;
break;
}
call.perform();
- id: double_button
mode: restart
then:
- lambda: !lambda |-
id(active_effect) += 1;
auto call = id(saturn_ring).turn_on();
switch (id(active_effect)) {
case 1:
call.set_effect("Slow Random Effect");
break;
case 2:
call.set_effect("Fast Random Effect");
break;
case 3:
call.set_effect("Police Strobe");
break;
case 4:
call.set_effect("Police Strobe Fast");
break;
default:
id(active_effect) = 0;
call.set_effect("None");
break;
}
call.perform();
button:
- platform: restart
name: "Restart ${friendly_name}"
- platform: safe_mode
name: "${friendly_name} Restart (Safe Mode)"
- platform: template
name: "Single Press"
id: single_press
on_press:
then:
- script.execute: single_button
- platform: template
name: "Double Press"
id: double_press
on_press:
then:
- script.execute: double_button
binary_sensor:
- platform: gpio
name: "Saturn Button"
id: saturn_button
pin:
number: GPIO3
mode: INPUT_PULLUP
inverted: True
on_multi_click:
- timing:
- ON for at most 1s
- OFF for at most 0.3s
- ON for at most 1s
- OFF for at least 0.2s
then:
- script.execute: double_button
- timing:
- ON for at most 1s
- OFF for at least 0.5s
then:
- script.execute: single_button
output:
- platform: ledc
pin: GPIO10
id: pwm_2
frequency: 100 Hz
max_power: 100%
- platform: ledc
pin: GPIO20
id: pwm_1
frequency: 100 Hz
max_power: 100%
- platform: ledc
pin: GPIO21
id: pwm_3
frequency: 100 Hz
max_power: 100%
light:
- platform: rgb
red: pwm_1
green: pwm_2
blue: pwm_3
name: "Saturn Ring"
id: saturn_ring
effects:
- random:
name: "Slow Random Effect"
transition_length: 30s
update_interval: 30s
- random:
name: "Fast Random Effect"
transition_length: 5s
update_interval: 5s
- strobe:
name: "Police Strobe"
colors:
- state: True
brightness: 100%
red: 100%
green: 0%
blue: 0%
duration: 500ms
- state: True
brightness: 100%
red: 0%
green: 0%
blue: 100%
duration: 500ms
- strobe:
name: "Police Strobe Fast"
colors:
- state: True
brightness: 100%
red: 100%
green: 0%
blue: 0%
duration: 250ms
- state: True
brightness: 100%
red: 0%
green: 0%
blue: 100%
duration: 250ms
So all the actions are now in a script, and I’ve just got a physical button and 2 separate software buttons that both call on to these scripts. This way if I change something I only have to change it once and everything I needed is in there.
And I can change the button functions and test them without physically being there.
(I have a camera in the room where the light is)
If you are really keen to mimic the button presses on the virtual button, you can have just the one virtual button which momentarily turns on and off a template binary sensor.
Then on that binary sensor you have the same press pattern logic as your physical button.
You could even move your button pattern press logic to this master binary sensor and just have both buttons pass on thier presses to this main one.
That would make the code more elegant, I like it. I hadn’t seen the template binary sensor before, I think that should do exactly what I want. I’ll try it out tomorrow and post the result once more. This whole solution will also help me with another ESP’d device I’ve got (a white noise speaker) that became unable to be controlled locally due to my previous solution. Thank you both for the help!
Appreciate that you’ve probably got your solution already, but wanted to share a similar problem I had (though it isn’t exactly the same I think).
My problem was that I wanted to define an action that could be fired by a button press (templated), but also in other actions. I don’t see a button.press action in esphome, so I ended up firing the button in a lambda…