EspHome RGB light effects control with local button

Hi EspHome Gurus,

I am configuring EspHome (on ESP8266) as RGB controller for WS2812B LEDs (using platform: neopixelbus) with a few light effects and with local buttons for local control (while still integrated with HomeAssistant).
I am looking to use two buttons. One button for power on/off and second one for switching effects

I need help how to configure the ESPHomel config to allow me to switch to the next effect with the local button, essentially looping thru the list.
I can control the single button with single activity (e.g. on/off) but I am unable to make it work for single button to go iterate or loop thru a list of effects and select one of them.

I was unable to find this particular situation. I have seen local button solutions for on/off and for brightness up/down but not for effects.

Can you help with example or to point me in the right direction if this has been discussed?
Thanks in advance

1 Like

You can use Global Variables of Automations to storage the actual effect:

You can add ā€œif/elseā€ conditions depend the actual effect to set the next.

Hi Miguel,
Thank you for the reply and the resource.

I have not used global variables and/or lambdas with EspHome and even after reading it and then looking at examples, I am unclear how to link and use those for my scenario. I read thru
Would you be able to provide a starter example that I hope to learn from and build further?

The below is not the actual code, it is for description.

light:
  - platform: neopixelbus
    type: grb
    pin: GPIO3
    num_leds: 121
    name: "rgb_lights"
    effects: 
      - Effect_1:
      - Effect_2:
      - Effect_3:

binary_sensor:
  - platform: gpio
    name: "effects"  # push button for scrolling thru the pre-configured effects with each press
    # the following is description of the desired outcome
	on_click one:
		id: rgb_lights
        effect: Effect_1
	on_click two:
		id: rgb_lights
        effect: Effect_2
	on_click three:
		id: rgb_lights
        effect: Effect_2
	#next click goes back to Effect_1#

I was also thinking about some loop in which while the button is pressed, the effects will change and once the button is released, the current effect to continue as selected.

So much to learn here.
Much appreciated.

I canā€™t try now, but, I think that should look that:

First, we add the global variable:

 globals:
   - id: actual_effect
     type: int
     restore_value: no
     initial_value: '0'

And then, change the onClick

binary_sensor:
  - platform: gpio
    name: "effects"  # push button for scrolling thru the pre-configured effects with each press
    # the following is description of the desired outcome
	on_click: 
	  - lambda: |- 
              auto call = id(rgb_lights).turn_on();

              if (id(actual_effect) == 0) 
                 call.set_effect("Effect 1");

              if (id(actual_effect) == 2)
                 call.set_effect("Effect 2"):
         //Other effects

              if (id(actual_effect) < 10) //Or your maximums effects
                  id(actual_effect) = 0;
              else
                 id(actual_effect) += 1;
    
             call.perform();


2 Likes

Hi Miguel,

Thank you very much for the example code. It works and it does what i was looking to do.
Now I can scroll thru the number of pre-configured LED effects with the push of a local button. :slight_smile:
For my case I had to modify the if/else loop as below to do what I needed.

	//Other effects
	if (id(actual_effect) < 10) //Or your maximums effects
		id(actual_effect) += 1;
	else
		id(actual_effect) = 0;

	call.perform();

The best part is that with your help and example I am learning.
I really appreciate your help. Thank You.

Cheers

Great.

Yeah, my fault, I wrote the condition reverseā€¦ I first wrote ā€œid(actual_effect) == 10ā€ and after I changed but not change the expressionā€¦ xD

Hello everybody
Is it possible to trace the effect in progress?
I have an RGBW LED strip that illuminates the relaxation area of the house with different effects: massage, meditation, chakras ā€¦
The module I built consists of two buttons to vary the brightness, a button to change the effect and a led that indicates the effect in progress, flashing a number of times for each effect.
The effect change is based on the example of MiguelAngelLV, thanks for the idea.
The problem is that if I change effect from the HA front-end, the value of the variable no longer matches the effect being executed.
I looked at the ESPhome API but couldnā€™t find anything, and before going through the code I was wondering if anyone knows a solution.
Thank you

Hi,

I would like to make also a local switch, but it donā€™t work here. I have this error message and what I try it donā€™t work.

ERROR Error while reading config: Invalid YAML syntax:

while scanning a simple key
  in "/config/esphome/sl-sofie.yaml", line 57, column 7:
          auto call = id(rgb_lights).turn_ ... 
          ^
could not find expected ':'
  in "/config/esphome/sl-sofie.yaml", line 58, column 7:
          if (id(actual_effect) == 0) 
          ^

Please could you help me?

globals:
  - id: actual_effect
    type: int
    restore_value: no
    initial_value: '0'

binary_sensor:
  - platform: gpio
    pin:
      number: D4
      mode: INPUT_PULLUP
      inverted: True
    name: "Sofie effect switch"
    on_click:
    - lambda:
      auto call = id(rgb_lights).turn_on();
      if (id(actual_effect) == 0) 
        call.set_effect("Rainbow Effect With Custom Values");
      if (id(actual_effect) == 1)
        call.set_effect("Strobe Effect With Custom Values"):
      if (id(actual_effect) < 1); //Or your maximums effects
        id(actual_effect) += 1;
      else
        id(actual_effect) = 0;
      call.perform();

Hi ,
I am sorry to ask but could someone explain why use all esphome mess to control ws2812b when you can use something like WLED ? What does esphome add here ?

I am not criticizing I am just asking because I use 4 esp32s with wled HA integration to control the ws2812b with lots of features beside the wled app .

I get the point to use esphome with DIY sensors but I am really having hard time understanding why use it with leds in the first place . I would appreciate your kind reply

thank you .

Well in my case itā€™s because the device Iā€™m building (a nightlight with music for my son) has to do more than just the LEDā€™s. Another advantage of using ESPHome is that you can program functionality that doesnā€™t require it to be connected, so it will even work if there is no network (for example if youā€™re not at home). Although I did just find out that switching between presets using a button is actually also possible in WLED, so for the other people in this thread it might actually be an option. Not for me though unless WLED can also send and receive UART commands.

For me the same reason. Iā€™m building leds on the ceiling in my little daughter bedroom, for her is the local switch.
I would also read the temperature of my floorheatingdistributor who is in her room.

Thx for the tip of WLED, I will check it and maybe split the system in two ESPs.

Hello guys an thank you both for the explanation .

Since you have an MCU around , just try WLED and see for yourself , It is very simple to install and use . If you donā€™t like it then I promise you I will start using esphome for at least one of my leds and I will post my project result .

[edit : Now I had a chance to test fastled and neopixel , i take what I said back , Using ESPhome for led is not bad ( It is very bad ) compared to WLED , it reminds me of assembly language vs C++ ]

I was working on this today and while the solution presented in the thread works, it is very manual. I believe I found a better solution which just crawls available effects in a loop, feel free to use it. Just replace <YOUR_LIGHT_ID>. Works well for my use at least and no need to code a specific case for each defined effect.

binary_sensor:
(your existing config)

    on_double_click:
      then:
        # add a small delay because on_click will always take precedence and strip will never turn off otherwise
        - delay: 500ms
        - light.toggle: <YOUR_LIGHT_ID>
    on_click:
      then:
        - lambda: |-
            static int effect_index = 0; // this value will persist across triggers
            id(<YOUR_LIGHT_ID>).turn_on().set_effect(id(<YOUR_LIGHT_ID>).get_effects().at(effect_index)->get_name().c_str()).perform();
            
            effect_index += 1;
            if (effect_index >= id(<YOUR_LIGHT_ID>).get_effects().size()) {
              effect_index = 0; // restart from the beginning once the last effect is reached
            }
            return;

3 Likes

Hello, how can I change your code so that the effects are turned on in a cycle randomly, and not sequentially?

Finally got it working!
My setup is with two touch sensors TTP223 and 27 WS2815B RGBs:

captive_portal:

status_led:
  pin:
    number: GPIO15
    inverted: true


light:
  - platform: neopixelbus
    id: light_1
    type: GRB
    variant: WS2811
    pin: GPIO16
    num_leds: 27
    name: "NeoPixel Light"
    effects:
      # Use default parameters:
      - random:
      # Customize parameters
      - random:
          name: "My Slow Random Effect"
          transition_length: 30s
          update_interval: 30s
      - random:
          name: "My Fast Random Effect"
          transition_length: 4s
          update_interval: 5s
      - random:
          name: Random Effect With Custom Values
          transition_length: 5s
          update_interval: 7s         
      - pulse:
          name: "Fast Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
          min_brightness: 0%
          max_brightness: 100%
      - pulse:
          name: "Slow Pulse"
          # transition_length: 1s      # defaults to 1s
          update_interval: 2s
      - strobe:
          name: Strobe Effect With Custom Values
          colors:
            - state: true
              brightness: 100%
              red: 100%
              green: 90%
              blue: 0%
              duration: 500ms
            - state: false
              duration: 250ms
            - state: true
              brightness: 100%
              red: 0%
              green: 100%
              blue: 0%
              duration: 500ms
      - flicker:
          name: Flicker Effect With Custom Values
          alpha: 95%
          intensity: 1.5%
      - addressable_rainbow:
          name: Rainbow Effect With Custom Values
          speed: 10
          width: 50
      - addressable_color_wipe:
          name: Color Wipe Effect With Custom Values
          colors:
            - red: 100%
              green: 100%
              blue: 100%
              num_leds: 1
            - red: 0%
              green: 0%
              blue: 0%
              num_leds: 1
          add_led_interval: 100ms
          reverse: false
      - addressable_scan:
          name: Scan Effect With Custom Values
          move_interval: 100ms
          scan_width: 1
      - addressable_twinkle:
          name: Twinkle Effect With Custom Values
          twinkle_probability: 5%
          progress_interval: 4ms

      - addressable_random_twinkle:
          name: Random Twinkle Effect With Custom Values
          twinkle_probability: 5%
          progress_interval: 32ms
      - addressable_fireworks:
          name: Fireworks Effect With Custom Values
          update_interval: 32ms
          spark_probability: 10%
          use_random_color: false
          fade_out_rate: 120

globals:
  - id: actual_effect
    type: int
    restore_value: yes
    initial_value: '0'
    
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO33
      mode: INPUT_PULLUP
      inverted: false
    name: "Mode effect switch"

    on_double_click:
      then:
        # add a small delay because on_click will always take precedence and strip will never turn off otherwise
        - delay: 500ms
        - light.toggle: light_1
        
    on_click:
      then:
        - lambda: |-
            static int effect_index = 0; 
            id(light_1).turn_on().set_effect(id(light_1).get_effects().at(effect_index)->get_name().c_str()).perform();
            
            effect_index += 1;
            if (effect_index >= id(light_1).get_effects().size()) {
              effect_index = 0;
            }
            return;

  - platform: gpio
    pin:
      number: GPIO18
      mode: INPUT_PULLUP
      inverted: false    
    name: "Power switch"

    on_click:
      then: 
        - light.toggle: # turns on effect
            id: light_1   

Big thanks!

1 Like

Hello, everyone. How are you?

I used this topic as a reference for my project, and it was very helpful! But I wanted some different features than what was shown here.

So, I have a single button, which when clicked turns on the lights, and the next click starts to cycle through the effects (dynamically). When it reaches the last effect, it returns to the default of the light on (effect ā€œNoneā€). To turn off the lights, simply double-click

In addition, I changed the way of identifying the single and double click, because this way I consider it to have a nicer ā€œvisualā€ look.

The big difference in my function is that if you change the effect to HA, it will continue the sequence of effects from the one selected in the interface, and not start over from the beginning. This provides continuity in the integration.

I hope you like it! I only copied part of the button code, the rest I imagine each person has their own application. Remember to change the ID of your light, and also the ID of the pin that the button is connected to.

#Set a global variable
globals:
- id: effect_index
  type: int
  initial_value: "0"

#Physical button to control the lights
binary_sensor:
  - platform: gpio
    pin:
      number: {{YOUR_BTN_PIN}}
      mode: INPUT_PULLUP
      inverted: True
    internal: True
    id: physical_btn
    on_multi_click:
      # double click
      - timing: 
        - ON for at most 0.5s
        - OFF for at most 0.7s
        - ON for at most 0.5s
        - OFF for at least 0.2s
        then:
          - delay: 500ms
          - light.turn_off: {{YOUR_LIGHTS_ID}}
          - lambda: |-
              id(effect_index)=0;
      # single click
      - timing:
        - ON for at most 1s
        - OFF for at least 0.5s
        then:
          if:
            condition:
              - light.is_on: {{YOUR_LIGHTS_ID}}
            then:
              - lambda: |-
                  std::string actual_effect=id({{YOUR_LIGHTS_ID}}).get_effect_name();
                  static int total_effects=id({{YOUR_LIGHTS_ID}}).get_effects().size();
                  std::string this_effect="";

                  if(actual_effect=="None") {
                    id(effect_index)=0;
                  } else {
                    for(int i = 0; i <= total_effects; i++) {
                      this_effect=id({{YOUR_LIGHTS_ID}}).get_effects().at(i)->get_name().c_str();
                      if (this_effect==actual_effect) {
                        id(effect_index)=i+1;
                        break;
                      }
                    }

                    if (id(effect_index)==total_effects) {
                      id(effect_index)=-1;
                    }
                  }

                  if (id(effect_index)==-1) {
                    id({{YOUR_LIGHTS_ID}}).turn_on().set_effect("None").perform();
                  } else {
                    id({{YOUR_LIGHTS_ID}}).turn_on().set_effect(id({{YOUR_LIGHTS_ID}}).get_effects().at(id(effect_index))->get_name().c_str()).perform();
                  }
                  return;
            else:
              - light.turn_on: {{YOUR_LIGHTS_ID}}
1 Like