ESPHome Script Component passing Parameters

Hi, following is an extract of my ESPhome program.


binary_sensor:
  - platform: homeassistant     # Contact Flag SS Porte Avant
    id: power_led
    entity_id: input_boolean.msg_flag_contact_ss_porte_av
    on_press:
      - script.execute:         
          id:         state_on_off
          r:          0
          g:          1
          flash_for:  2000

    on_release:
      - script.execute:         
          id:         state_on_off       
          r:          1
          g:          0
          flash_for:  4000

script:
  - id: state_on_off
    parameters:
      r:          int
      g:          int
      flash_for:  int 

    then:
      - light.turn_on: 
          id:           l2
          brightness:   50%
          red:          !lambda return r;
          green:        !lambda return g;
          blue:         0
          flash_length: !lambda return flash_for;

I am using Script passing Parameters to my script. So far I have been able to provide 3 parameters to my script (the colors for my RGB WS1282 and the length of time the light should stay on. This work perfectly.

I read the ESPHome documentation Automations and Templates — ESPHome, and find out that it support ARRAY of data, the text mention bool[] as example. But there is no example how to code the content neither how to use it in the script. How to assign the value of array[0] to r or array[1] to g etc.

I tried to find examples and used different combinations but could not figure how to code it.
Neither was I able to create a parameter for the id: for the light to be turned on.

Can anyone help me out by providing the code to create the array in the script.execute portion and how to assign the value red/green/blue etc in the script itself. The link to the documentation how to do it would also be greatly appreciate

My dream would be to combine all data types (bool, int, float and string) in the same array. If impossible then I will pass an array of the same data type.

The documentation shows that it supports bool, int, float and string. Besides those 4, you can also pass them in an array, so bool[], int[], float[] and string[].

And this answers your last sentence: in 1 array you can only pass values of the same type. Unless of course you convert them all to eg string and later back to the respective data type. But that is highly discouraged.

Now the question: how to use them? An example is better than a 1000 words:

script:
  - id: sample
    parameters:
      delay_ms: int[]
      should_delay: bool[]
    then:
      - delay: !lambda |-
          return should_delay[0] ? delay_ms[0] : 0;
      - delay: !lambda |-
          return delay_ms[1];

sensor:
  - platform: ...
    ...
    on_value:
      - lambda: |-
          std::vector<int> delay_ms_1{ 10, 20 };
          std::vector<bool> should_delay_2{ false };
          id(sample)->execute(delay_ms_1, should_delay_2);

In this example I am passing 2 arrays: first is an array of int and second an array of bool. Internally the arrays are C++ type std::vector so if you want to initiate such array it’s easiest to do so in a lambda function. The std::vector<int> is the type definition, the delay_ms_1 the name of the variable and the part between curly braces is the initialization of the vector content.

On the script side, you simply access the array values by using the square bracket notation, eg delay_ms[0] reads the first value from the array.

Now, to apply this for your case:

script:
   - id: state_on_off
     parameters:
        rgb: int[]
        flash_for: int
     then:
       - light.turn_on:
           ...
           red: !lambda return rgb[0];
           green: !lambda return rgb[1];
           blue: !lambda return rgb[2];
           flash_length: !lambda return flash_for;

binary_sensor:
  - platform: homeassistant
    ...
    on_press:
      - lambda: |-
          std::vector<int> light_rgb{ 0xFF, 0x20, 0x00 };
          id(state_on_off)->execute(light_rgb, 2000);

Lastly, the id for the light.turn_on: this property is not templatable and so you cannot use lambda to dynamically pass the id, unfortunately.

The workaround would be to switch the right light based on condition, for example through an if action or lambda switch statement.

1 Like

Found a way to work around the non-templatable id. It is by using an std::map to map an ID value of your choice (in this case an int) to the light component.

script:
   - id: state_on_off
     parameters:
        light_id: int
        rgb: int[]
        flash_for: int
     then:
       - lambda: |-
          static std::map<int, light::LightState*> light_map {
            { 1, id(light1) }, 
            { 2, id(light2) },
            { 3, id(light3) }
          };
          auto it = light_map.find(light_id);
          if (it != light_map.end()) 
            it->second->turn_on()
              .set_brightness(0.5)
              .set_rgb(rgb[0], rgb[1], rgb[2])
              .set_flash_length(flash_for)
              .perform();
2 Likes

WOW !
What a solution. It work great, I made a few minor adjustments and got it to work perfectly.
I changed the “light_rgb”to “rgb” to be consistant for definition and utilisation.
“flash_length” being also an integer, I included in the same array.
Finely, I use integer “0/1” instead of hexadecimal.

However, I cannot used .5 as a rgb color, should I change my array from int to float ?

I appreciate very much the detailed explanations explaining the concept as well as an excellent example. Following is my revised code.


binary_sensor:
  - platform: homeassistant     # Contact Flag SS Porte Avant
    id: power_led
    entity_id: input_boolean.msg_flag_contact_ss_porte_av
    on_press:
      - lambda: |-
          std::vector<int> rgb{1, 0, 0,2000};
          id(state_on_off)->execute(rgb);

    on_release:
      - lambda: |-
          std::vector<int> rgb{1, 0, 1, 4000};
          id(state_on_off)->execute(rgb);

script:
  - id: state_on_off
    parameters:
      rgb:          int[]
    then:
      - light.turn_on: 
          id:           l2
          brightness:   50%
          red:          !lambda return rgb[0];
          green:        !lambda return rgb[1];
          blue:         !lambda return rgb[2];
          flash_length: !lambda return rgb[3];

Thank you also for the work around to include the entity.id. This will take me more time to include your suggestion. Great thinking outside the box to use an index including the key in the array.

Ah, my bad. Apparently the red, green and blue require a percentage or fraction value, so either 0%-100% or 0.0-1.0

You’ll indeed need to change the type to float[]

Hi again, I have completed all my changes to include in a float all the variables including brightness.

I try very hard to use your concept of retrieving the entity_id from an index. But could not get it to work.
I am not a programmer, and I see thing that I have never seen before it>second->turn_on …

.set_brigthness
.set_rgb(… I do not understant these and what is a .perform()

for your information i am including a portion of my program that works perfectly,

binary_sensor:
  - platform: homeassistant    
    id: power_led
    entity_id: input_boolean.msg_flag_contact_ss_porte_av
    on_press:                      
      - lambda: |-
          std::vector<float> rgb{1, 0, 0,2000, .5};  
          id(state_on_off)->execute(rgb);
    on_release:                   
      - lambda: |-
          std::vector<float> rgb{0, 1, 0, 4000,1};     
          id(state_on_off)->execute(rgb);

script:
  - id: state_on_off   
    parameters:
      rgb:          float[]                           

    then:
      - light.turn_on: 
          id:           l2
          brightness:   !lambda return rgb[4];
          red:          !lambda return rgb[0];
          green:        !lambda return rgb[1];
          blue:         !lambda return rgb[2];
          flash_length: !lambda return rgb[3];

Ok, first of all, I’m missing some information how you’ve setup your light components and how you’d like to invoke it. So, I’ll make the following assumption:

You’ll still prefer a single parameter for the state_on_off script, being rgb which includes the id, color components, brightness and flash length. Something like this:

rgb{ 1, 2, 3, 4, 5, 6 } // 1 = id
                        // 2 = red
                        // 3 = green
                        // 4 = blue
                        // 5 = flash length
                        // 6 = brightness

Then you can change your code as follows:

light:
  - platform: ...
    id: l1 # Assuming that these are the id's of the lights in your yaml
  - platform: ...
    id: l2

binary_sensor:
  - platform: homeassistant
    id: power_led
    entity_id: input_boolean.msg_flag_contact_ss_porte_av
    on_press:
      - lambda: |-
          std::vector<float> rgb{1, 1, 0, 0, 2000, .5};
          id(state_on_off)->execute(rgb);
    on_release:
      - lambda: |-
          std::vector<float> rgb{1, 0, 1, 0, 4000, 1};
          id(state_on_off)->execute(rgb);

script:
  - id: state_on_off   
    parameters:
      rgb: float[]
    then:
       - lambda: |-
          // Extend this map with all the lights that you have
          static std::map<float, light::LightState*> light_map {
            { 1, id(l1) },
            { 2, id(l2) },
          };
          auto iterator = light_map.find(rgb[0]);
          if (iterator != light_map.end()) {
            auto light = iterator->second;
            light->turn_on()
              .set_brightness(rgb[4])
              .set_red(rgb[0])
              .set_green(rgb[1])
              .set_blue(rgb[2])
              .set_flash_length(rgb[3])
              .perform();
          }

Let’s break this down. In the std::vector<float> rgb{...} the array is extended with an additional element for the id, in the sample above the first value in the array, being 1.

In the script section the lambda function has a light_map which is a dictionary/lookup table. In this map you map an ID of your choice (=key) to the light component (=value). So ID=1 maps to lamp l1 and ID=2 to lamp l2, etc. You should extend this map with all the lights that you want to control through the script.

When the script is invoked, it reads the first value from the array, being the ID. Then it uses the map to find the related light component (in C++ terms: pointer to the LightState object).

Now that you have a reference to the light component you want to set its brightness, the RGB color (RGB=Red, Green, Blue) and the flash length. The functions which are used (set_brightness, set_red, etc) are the C++ way of setting the light properties:

light->turn_on()
  .set_brightness(rgb[4])
  .set_red(rgb[0])
  .set_green(rgb[1])
  .set_blue(rgb[2])
  .set_flash_length(rgb[3])
  .perform();

You can read it as, from light it should turn on, update its brightness, color and flash length. Then by invoking perform the light will execute all those actions.

2 Likes

Thank you very much, the whole thing works perfectly.

Following is my final code, hoping that someone in the community will benefit form it.

# ESPHome program to test how to control WS8212 LED strip
# WS8212 LED Strip can be divided in segment, each one can be address to Turn On/Off as well as control Brightness and Flash_length
# Credit and many thanks goes to CK Smart (home assistant community) for designing the Script  and how to use it
# Gaston Paradis June 17, 2023

substitutions:
  devicename: esp32-04
  friendly_name: "Testing LED"
  comment: "Testing WS2812LED"
  esp_id: ESP32_04
  board_type: esp32doit-devkit-v1
  led_segment_1 : l1        # Range 1 of WS8212 LED use in SCRIPT
  led_segment_2 : l2        # Range 2 of WS8212 LED in SCRIPT
 

esphome:
  friendly_name: ${friendly_name}
  comment: ${comment}
  on_boot:
    priority: -10
    then:
    - wait_until:
        api.connected:
    - logger.log: API is connected!
  
    - lambda: |-
        std::vector<float> ws2812{1, .5, 0,0,1,3000};
        id(state_on_off)->execute(ws2812); 
    - switch.turn_on: led_enabled

esp32:
  board: ${board_type}
  framework:
    type: arduino
    
packages:
  base:  !include
    file: package/basic.yaml           
    vars: 
      Uptime_update_inverval: 10min

light:
- platform: neopixelbus         # Configure WS2812 LED
  # name: "Status LED"          # Uncomment to make visable in Home Assistant and local WEB
  id: activity_led
  variant: WS2812
  pin: GPIO25
  num_leds: 2
  flash_transition_length: 1500ms
  type: GRB
  restore_mode: ALWAYS_OFF


- platform: partition           # Use LED [0] from the light with ID activity_led
  # name: "Partition Light 1"   # Uncomment to make visable in Home Assistant and local WEB
  id: ${led_segment_1}                         
  segments:
    - id: activity_led
      from: 0
      to:   0

- platform: partition           # Use LED [1] from the light with ID activity_led
  # name: "Partition Light 2"    # Uncomment to make visable in Home Assistant and local WEB
  id: l2
  segments:
    - id: ${led_segment_1} 
      from: 1
      to:   1      

switch:

  - platform: template    # Define switches to control LED from HA # LED enabled
    name: "${friendly_name} LED enabled"
    id: led_enabled
    icon: mdi:alarm-light-outline
    optimistic: true
    restore_state: true
    entity_category: config
  
button:         
  - platform: template  # Define buttons for turning LED On/Off via HA 
    name: Turn On/Off LED
    id: turn_led
    # Optional variables:
    icon: "mdi:button"
    on_press:
      then:
        if:        
          condition:
            - light.is_on: l1
          then:
            - light.turn_off: l1
          else:
            if:
              condition:
                - switch.is_on: led_enabled
              then:   # Array [id, Brightness, red, green, blue, flash_length]   
                    - lambda: |-
                       std::vector<float> ws2812{1, .75, 0,0,1,2000};
                       id(state_on_off)->execute(ws2812);                  

sensor:

  - platform: homeassistant     # Temperature Salon
    name: "Sensor From Home Assistant"
    entity_id: sensor.esp_15_temperature
    internal: false
    on_value_range: 
      - below: 22.0    # Array [id, Brightness, red, green, blue, flash_length]   
        then: # Array [id, Brightness, red, green, blue, flash_length]
              - lambda: |-
                 std::vector<float> ws2812{1, .5, 0, 0, 1, 2000};
                  id(state_on_off)->execute(ws2812);            
      - above: 22.0   # Array [id, Brightness, red, green, blue, flash_length]   
        below: 30.0
        then: # Array [id, Brightness, red, green, blue, flash_length]
              - lambda: |-
                 std::vector<float> ws2812{1, .5, 0, 1, 0, 2000};
                  id(state_on_off)->execute(ws2812); 
      - above: 30.0    # Array [id, Brightness, red, green, blue, flash_length]   
        then:
              - lambda: |-
                 std::vector<float> ws2812{1, .5, 1, 0, 0, 2000};
                  id(state_on_off)->execute(ws2812);      

  - platform: dht             # DHT Temperature/Humidity
    model: AUTO_DETECT
    pin: GPIO21               #   SDA
    temperature:
      name:  ${esp_id} Temperature
    humidity:  
      name:  ${esp_id} Humidity
    update_interval: 15s


binary_sensor:
  - platform: homeassistant    
    id: power_led
    entity_id: input_boolean.msg_flag_contact_ss_porte_av
    on_press:   # Array [id, Brightness, red, green, blue, flash_length]
      - lambda: |-
          std::vector<float> ws2812{2, .5, 1, 0, 0, 2000};
          id(state_on_off)->execute(ws2812);


    on_release:   # Array [id, Brightness, red, green, blue, flash_length]                
      - lambda: |-
          std::vector<float> ws2812{2 ,1 , 0, 1, 0, 4000};     
          id(state_on_off)->execute(ws2812);

script:
  - id: state_on_off   
    parameters:
      ws2812: float[]
    then:
       - lambda: |-
          // Extend this map with all the lights that you have
          static std::map<float, light::LightState*> light_map {
            { 1, id(${led_segment_1}) },
            { 2, id(${led_segment_2}) },
          };
          auto iterator = light_map.find(ws2812[0]);
          if (iterator != light_map.end()) {
            auto light = iterator->second;
            light->turn_on()
              .set_brightness(ws2812[1])
              .set_red(ws2812[2])
              .set_green(ws2812[3])
              .set_blue(ws2812[4])
              .set_flash_length(ws2812[5])
              .perform();
          }

time:
  - platform: sntp
    id: timer
    on_time:
      # Every minute
      - seconds: 0
        minutes: /1
        then:
          - repeat:
              count: 5
              then: # Array [id, brigthness, red, green, blue, flash_length]
                  - lambda: |-
                      std::vector<float> ws2812{2, .25, 1, 0, 0, 500};
                      id(state_on_off)->execute(ws2812);                       
                  - delay: 1s
1 Like