Changing light effects for addressable LEDs

Hello.
It’s possible that I’m missing something really simple here…
but I thought that changing light effect for some addressable LEDs would have been simple… in reality, I can change effect only if I turn them off and then ON again; but I want to avoid that since I defined “on_turn_on” and “on_turn_off” to switch their power with a relay.

This is my whole config file:

esphome:
  name: garage-automation
  on_boot:
    priority: -100
    then:
      - script.execute: deactivate_outputs
      - wait_until:
          condition:
            wifi.connected:
      - script.execute: blink_onboard_led
      
esp32:
  # ESP-WROOM32 (micro-USB)
  board: esp32doit-devkit-v1
  framework:
    type: arduino

# Enable logging
logger:

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: "------------------"
  password: "------------------"
  
  # Optional manual IP
  manual_ip:
    static_ip: 192.168.1.123
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.2
    dns2: 192.168.1.1

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "------------------"
    password: "------------------"
    
globals:
  - id: current_state
    type: int
    restore_value: no
    initial_value: '0'  # Default state (both outputs OFF)

  - id: relay_timeout
    type: int
    initial_value: '5000'  # temp: final will be 60000 - Maximum relay timeout in milliseconds

output:
  - platform: gpio
    pin: GPIO2
    id: onboard_led

# Global functions    
script:
  - id: blink_onboard_led
    then:
      - output.turn_on: onboard_led
      - delay: 150ms
      - output.turn_off: onboard_led
      - delay: 100ms
      - output.turn_on: onboard_led
      - delay: 150ms
      - output.turn_off: onboard_led
      - delay: 100ms
      - output.turn_on: onboard_led
      - delay: 150ms
      - output.turn_off: onboard_led

  - id: deactivate_outputs
    then:
      - switch.turn_off: output_open
      - switch.turn_off: output_close
      - globals.set:
         id: current_state
         value: '0'  # OFF: Both outputs OFF
      - lambda: |-
         id(actuators_state).publish_state("OFF");
      - select.set:
         id: actuators_output_ctrl
         option: "OFF"

  - id: deactivation_on_timeout
    then:
      - delay: !lambda 'return id(relay_timeout);'  # Timeout
      - logger.log: "Timeout reached. Deactivating outputs.."
      - script.execute: deactivate_outputs
      - logger.log: "... and starting LED strip control."
      - script.execute: led_strip_control
          
  - id: activate_output_open
    then:
      - switch.turn_off: output_close
      - delay: 250ms
      - script.execute: led_strip_blue_red
      - switch.turn_on: output_open
      - globals.set:
         id: current_state
         value: '1'  # OPEN: Output 1 ON, Output 2 OFF
      - lambda: |-
         id(actuators_state).publish_state("OPEN");
      - select.set:
         id: actuators_output_ctrl
         option: "OPEN"
      - script.execute: deactivation_on_timeout

  - id: activate_output_close
    then:
      - switch.turn_off: output_open
      - delay: 250ms
      - script.execute: led_strip_blue_red
      - switch.turn_on: output_close
      - globals.set:
         id: current_state
         value: '2'  # CLOSE: Output 2 ON, Output 1 OFF
      - lambda: |-
         id(actuators_state).publish_state("CLOSE");
      - select.set:
         id: actuators_output_ctrl
         option: "CLOSE"
      - script.execute: deactivation_on_timeout

  - id: led_strip_static_green
    then:
      - light.turn_on:
          id: led_strip
          brightness: 100%
          red: 0%
          green: 100%
          blue: 0%

  - id: led_strip_blue_red
    then:
      - light.turn_on:
          id: led_strip
          effect: "Alternanza Blu e Rosso"

  - id: led_strip_control
    then:
      - if:
          condition:
            binary_sensor.is_on: button_door
          then:
            - logger.log: "Door opened, turning on (green) LED strip."
            #TODO
            # FIXME
            - script.execute: led_strip_static_green
            # FIXME
          else:
            - logger.log: "Door closed, turning off LED strip."
            - light.turn_off: led_strip

# Service for controlling outputs via Home Assistant
api:
  services:
    - service: set_state
      variables:
        state: int
      then:
        - logger.log: "Received state change request via service..."
        - if:
            condition:
              lambda: 'return state == 0;'
            then:
              - script.execute: deactivate_outputs
        - if:
            condition:
              lambda: 'return state == 1;'
            then:
              - logger.log: "...for value 1"
              - script.execute: activate_output_open
        - if:
            condition:
              lambda: 'return state == 2;'
            then:
              - logger.log: "...for value 2"
              - script.execute: activate_output_close

    - service: set_relay_timeout
      variables:
        timeout: int
      then:
        - globals.set:
            id: relay_timeout
            value: !lambda 'return timeout;'
        - logger.log:
            format: "Relay timeout updated to %d ms"
            args: ["timeout"]
                  
# HA Selection menu for controlling outputs
select:
  - platform: template
    name: "Controllo Attuatori"
    id: actuators_output_ctrl
    options:
      - "OFF"
      - "OPEN"
      - "CLOSE"
    icon: mdi:toggle-switch
    initial_option: "OFF"
    optimistic: true
    set_action:
      - logger.log:
          format: "Chosen option: %s"
          args: ["x.c_str()"]
      - if:
          condition:
            lambda: 'return x == "OFF";'
          then:
            - script.execute: deactivate_outputs
      - if:
          condition:
            lambda: 'return x == "OPEN";'
          then:
            - script.execute: activate_output_open
      - if:
          condition:
            lambda: 'return x == "CLOSE";'
          then:
            - script.execute: activate_output_close

# HA Switches
switch:
  - platform: gpio
    pin: GPIO25
    id: output_open
    #name: "Output Open"  # (hidden)
    restore_mode: ALWAYS_OFF

  - platform: gpio
    pin: GPIO26
    id: output_close
    #name: "Output Close"  # (hidden)
    restore_mode: ALWAYS_OFF

  - platform: gpio
    pin: GPIO27
    id: output_led_bars
    name: "Barre LED (interno)"
    restore_mode: ALWAYS_OFF

  - platform: gpio
    pin: GPIO14
    id: output_led_strip
    #name: "Striscia LED ON/OFF"  # (hidden)
    restore_mode: ALWAYS_OFF

# HA Buttons and sensors
binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO32
      mode: INPUT_PULLUP
      inverted: true
    id: button_open
    name: "Tasto OPEN"
    icon: "mdi:arrow-up-bold-box"
    on_press:
      - script.execute: activate_output_open
    on_release:
      - script.execute: deactivate_outputs

  - platform: gpio
    pin: 
      number: GPIO33
      mode: INPUT_PULLUP
      inverted: true
    id: button_close
    name: "Tasto CLOSE"
    icon: "mdi:arrow-down-bold-box"
    on_press:
      - script.execute: activate_output_close
    on_release:
      - script.execute: deactivate_outputs

  - platform: gpio
    pin: 
      number: GPIO13
      mode: INPUT_PULLUP
    id: button_door
    name: "Serranda"
    icon: "mdi:window-shutter"
    filters:
      - delayed_on_off: 5s
    on_press:
      - logger.log: "Door sensor opened"
    on_release:
      - logger.log: "Door sensor closed"      

# HA Text Sensor for showing actuators state
text_sensor:
  - platform: template
    name: "Stato Attuatori"
    id: actuators_state
    lambda: |-
      if (id(current_state) == 1) {
        return std::string("OPEN");
      } else if (id(current_state) == 2) {
        return std::string("CLOSE");
      } else {
        return std::string("OFF");
      }
    icon: "mdi:boom-gate-up"
    update_interval: never  # Disable automatic updates for performance

light:
  - platform: fastled_clockless
    id: led_strip
    chipset: WS2812B
    pin: GPIO04
    num_leds: 23
    rgb_order: GRB
    name: "Striscia LED (esterno)"
    on_turn_on:
      - switch.turn_on: output_led_strip
    on_turn_off:
      - switch.turn_off: output_led_strip
    effects:
      - addressable_lambda:
          name: "Alternanza Blu e Rosso"
          update_interval: 500ms
          lambda: |-
            static bool toggle = false;
            int half = it.size() / 2;
            for (int i = 0; i < it.size(); i++) {
              if ((toggle && i < half) || (!toggle && i >= half)) {
                it[i] = ESPColor(0, 0, 255);  // Blu
              } else {
                it[i] = ESPColor(255, 0, 0);  // Rosso
              }
            }
            toggle = !toggle;

Maybe it’s quite long, but there’s a “# FIXME” comment for the line that gets ignored…

Basically, this should be the logic:
There’s a garage door opened by two linear actuators and there’s a door switch.
While the door is moving (opening or closing) the led strip will show the “red_blue” effect.
At the end of the opening motion, [door sensor OPEN] the red_blue effect should turn to green (THIS is what’s not working), while at the end of the closing motion [door sensor CLOSED], the strip turns off.

Thanks for any help.

Would using the power_supply option for your light be a better way of doing that? See Light Component — ESPHome

And it shouldn’t be necessary to turn it off and on again. Just turn it on with the settings you want. If it is already on, only the color will change.

Yes, power_supply seems perfect (if it turns out I have to switch the LEDs off and then on again, I don’t have to worry about the relay, due to the cool down time).

… But this is exactly what’s not working. Now I’m going to do some more tests.

In the meantime, thanks for your suggestions.

I finally fixed it, but it took me a while.

The problem was how I defined the static green color under the script section:

- id: led_strip_static_green
    then:
      - light.turn_on:
          id: led_strip
          brightness: 100%
          red: 0%
          green: 100%
          blue: 0%

I changed it to:

- id: led_strip_static_green
    then:
      - light.turn_on:
          id: led_strip
          effect: "Static Green"

after moving the color setup into the light component, as an effect:

  - addressable_lambda:
      name: "Static Green"
      update_interval: 500ms
      lambda: |-
        it.all() = Color(0, 255, 0);

Being called like this, it doesn’t need the light.turn_off: led_strip

It doesn’t make much sense, but I can imagine why the two almost equivalent blocks of code are treated differently.

PS:
@nickrout I’m leaving the “power_supply” component in, although not strictly needed, because it seems more neat. :sunglasses: