Color Loop Blueprint with Configurable Colors and Transition Time

Loops through predetermined colors for a chosen light.

This will continue indefinitely until the either the light is turned off or the script is manually disabled.

READ THIS FIRST

Before using this blueprint, be warned that it can put a real heavy load on
Home Assistant and its database if configured to change colors at a quick pace.
While these color changes do not show up in the History or the Logbook,
they are still logged in the database unless manually configured to not do so.
If Home Assistant is running on an SD card on a Raspberry Pi, large amounts
of DB changes can technically cause damage over time
.
Refer to the next sections regarding solutions to mitigate this issue.
If you do not understand these issues, this blueprint should probably be avoided.

Solution 1: Exclude lights from recorder:

With that out of the way need a solution for the database spam, one solution
that works at the moment is to literally prevent the light(s) from getting
logged for anything. The following configuration could be added and
populated with the chosen lights and the entity ID of this script.

recorder:
  exclude:
    entities:
      # Exclude any logging of the chosen light.
      - light.my_light
      # Exclude any logging of this color loop script.
      - script.my_light_color_loop
    event_types:
      # Do not record ANY service calls
      - call_service

If a light group is used in this blueprint, you should add all individual
light entities to the exclusion above as well as the group ID.

Refer to Recorder Exclude Docs

Solution 2: Increase Commit Interval:

Another solution is to increase the commit interval. By default the DB is
written to once a second. Increasing this value will prevent the Database
from being spammed with frequent requests.

This will allow the lights to still be logged, but depending on how often
this light is configured to change colors, this can end up significantly
increasing the size of the DB.

recorder:
  # Write to the DB every 15 seconds instead of once a second.
  commit_interval: 15

Refer to Recorder Commit Interval Docs

Future solution:

A feature request has been added to see if there is a better way to do this
without needing to always exclude logging:
Allow an ability to call a service without any logging/recording

Credits: Inspired by C.G.B.Spender’s Blueprint with tweaks to make it a script instead of automation and to add an easier way to configure colors and transition time.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.
Link to Gist

Blueprint code:

blueprint:
  name: Color Loop
  description: '# Color Loop

    ### Version: `0.1.1` (2022-06-19)

    Loops through predetermined colors for a chosen light.

    This will continue indefinitely until the either the light is turned off or the
    script is manually disabled.

    ## READ THIS FIRST

    **Before using this blueprint, be warned that it can put a real heavy load on
    Home Assistant and its database if configured to change colors at a quick pace.
    While these color changes do not show up in the History or the Logbook,
    they are still logged in the database unless manually configured to not do so.
    [If Home Assistant is running on an SD card on a Raspberry Pi, large amounts
    of DB changes can technically cause damage over time](https://www.reddit.com/r/homeassistant/comments/jvwtv1/friendly_reminder_dont_use_a_sd_card_on_a_pi/).
    Refer to the next sections regarding solutions to mitigate this issue.
    If you do not understand these issues, this blueprint should probably be avoided.**


    ### Solution 1: Exclude lights from recorder:

    With that out of the way need a solution for the database spam, one solution
    that works at the moment is to literally prevent the light(s) from getting
    logged for *anything*. The following configuration could be added and
    populated with the chosen lights and the entity ID of this script.

    <code>
    <!-- Code Blocks in markdown seem to be broken, the following is a work-around -->
    recorder:

    &nbsp;&nbsp;exclude:

    &nbsp;&nbsp;&nbsp;&nbsp;entities:

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\# Exclude any logging of the chosen light.

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- light.my_light

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\# Exclude any logging of this color loop script.

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- script.my_light_color_loop

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;event_types:

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\# Do not record ANY service calls

    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- call_service
    </code>

    If a light group is used in this blueprint, you should add all individual
    light entities to the exclusion above as well as the group ID.

    Refer to [Recorder Exclude Docs](https://www.home-assistant.io/integrations/recorder#exclude)

    ### Solution 2: Increase Commit Interval:

    Another solution is to increase the commit interval. By default the DB is
    written to once a second. Increasing this value will prevent the Database
    from being spammed with frequent requests.

    This will allow the lights to still be logged, but depending on how often
    this light is configured to change colors, this can end up significantly
    increasing the size of the DB.

    <code>
    <!-- Code Blocks in markdown seem to be broken, the following is a work-around -->
    recorder:

    &nbsp;&nbsp;\# Write to the DB every 15 seconds instead of once a second.

    &nbsp;&nbsp;commit_interval: 15
    </code>

    Refer to [Recorder Commit Interval Docs](https://www.home-assistant.io/integrations/recorder#commit_interval)

    ### Future solution:

    A feature request has been added to see if there is a better way to do this
    without needing to *always* exclude logging:
    [Allow an ability to call a service without any logging/recording](https://community.home-assistant.io/t/allow-an-ability-to-call-a-service-without-any-logging-recording/428693)

    # Configuration:

    ## Light:

    For the Light, choose the light entity to loop through the colors chosen below.

    This script will continuously loop through colors until the script it manually
    stopped or the light is turned off.

    A group entity consisting of multiple lights can also be used. This has a
    caveat where the script does not always get disabled when turning the
    individual lights off. To stop the color loop for groups, either manually
    stop the script or turn the whole light group off.

    ***Note: Some lights do not support gradual transition between colors.
    In these cases, the light will instantly change to the next color after
    the transition time instead of gradually changing. Refer to the
    `Max Color Distance` section below for a means to resolve this.***

    ## Colors:

    Set the colors to loop through.

    Any colors set to black (R:0 G:0 B:0) will be omitted from the loop.

    *Note: There is no current way to re-order the chosen colors, you must change
    or remove (set to black) colors to change the order.*

    *Note: Since RGB colors do not translate well to colored lights (the current
    brightness of the light does not get changed by this script) the color previews
    may not look accurate to the final result.*

    ## Transition Time:

    Choose the time it takes to cycle through each color.

    ## Max Color Distance:

    Determines how many colors in between the chosen colors should be transitioned to.

    This prevents issues where the color fades to white in between colors by
    dynamically picking colors in between the chosen colors.

    This value basically means the minimum amount of degrees of the color wheel
    should result in an in-between color to be added.


    For Example: Red and Cyan are on the opposite side of the color wheel (180 degrees apart):

    - Choosing `180` will cause the color transition going straight across resulting in a fade to white in between the colors.

    - Choosing `90` will have a new color added every 90 degrees, so in this case a new transitional color will be added at Purple.

    - Choosing `60` will have a new color added every 60 degrees, so in this case two transitional colors will be added at Blue and Pink.

    - Choosing `30` will have a new color added every 30 degrees, so in this case three transitional colors will be added at Red/Pink, Purple, and Blue.

    - Choosing `1` will have a new color added every 1 degrees, so in this case the light will be set to a new color for EVERY color along the way.


    If using a light which does not support color transitions, this can be set
    to a very low number (eg 1-10) in order to fake the color transition by
    updating the colors gradually along the way.

    In summary, for performance reasons, keep this value as high as possible and
    lower it as needed to avoid the color going brighter in between the
    chosen colors.

    ## Max Changes Per Second:

    With the combination of `Transition Time` and `Max Color Distance` both being
    set to a low value, this could end up resulting in WAY too many calls to the
    light to update its color (up to 180 calls per second). In order to avoid
    overloading Home Assistant, this value can be used in order to set a maximum
    limit of how many calls per second to change the light during transition can
    be made.

    For performance reasons, this should be as low as possible, but with lights
    which do not support color transitions, this can be increased in order to
    have smoother color transitions.
    '
  domain: script
  input:
    light:
      name: Light
      selector:
        entity:
          domain:
            - light
            - group
          multiple: false
    transition:
      name: Transition Time
      selector:
        duration: {}
      default:
        hours: 0
        minutes: 0
        seconds: 15
    max_color_distance:
      name: Max Color Distance
      default: 60
      selector:
        number:
          min: 1
          max: 180
    max_changes_per_second:
      name: Max Changes Per Second
      default: 1
      selector:
        number:
          min: 1
          max: 5
    color_1:
      name: Color 1
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_2:
      name: Color 2
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_3:
      name: Color 3
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_4:
      name: Color 4
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_5:
      name: Color 5
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_6:
      name: Color 6
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_7:
      name: Color 7
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_8:
      name: Color 8
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_9:
      name: Color 9
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_10:
      name: Color 10
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_11:
      name: Color 11
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_12:
      name: Color 12
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
  source_url: https://gist.github.com/mdolnik/7147b5df4b08f7979afad02a5dd137a2

# If we trigger the script while its running, restart the script.
mode: restart

sequence:
- alias: Set up main variables
  variables:
    transition: !input 'transition'
    transition_seconds: '{{ ((transition.hours)*60*60) +  ((transition.minutes)*60)
      + transition.seconds }}'
    max_color_distance: !input 'max_color_distance'
    max_changes_per_second: !input 'max_changes_per_second'
    color_rgbs:
      - !input 'color_1'
      - !input 'color_2'
      - !input 'color_3'
      - !input 'color_4'
      - !input 'color_5'
      - !input 'color_6'
      - !input 'color_7'
      - !input 'color_8'
      - !input 'color_9'
      - !input 'color_10'
      - !input 'color_11'
      - !input 'color_12'
    # Convert the array of RGB colors to a comma separated list of HSV values.
    # Exclude any colors set to black.
    # https://community.home-assistant.io/t/using-hsv-hsb-to-set-colored-lights/15472
    # https://github.com/home-assistant/core/issues/33678#issuecomment-609424851
    # https://stackoverflow.com/a/56141280/4147996
    color_hsv_list: >-
      {%- set data = namespace(entries=[]) -%}
      {%- for color_rgb in color_rgbs -%}
        {%- if color_rgb != [0,0,0] -%}
          {%- set r = (color_rgb[0]/255) -%}
          {%- set g = (color_rgb[1]/255) -%}
          {%- set b = (color_rgb[2]/255) -%}
          {%- set maxRGB = max(r,g,b) -%}
          {%- set minRGB = min(r,g,b) -%}
          {%- set chroma = maxRGB - minRGB -%}
          {%- if chroma == 0 -%}
            {%- set h = 0 -%}
            {%- set s = 0 -%}
            {%- set v = maxRGB -%}
          {%- else -%}
            {%- if r == minRGB -%}
              {%- set h = 3-((g-b)/chroma) -%}
            {%- elif b == minRGB -%}
              {%- set h = 1-((r-g)/chroma) -%}
            {%- else -%}
              {%- set h = 5-((b-r)/chroma) -%}
            {%- endif -%}
            {%- set h = 60 * h -%}
            {%- set s = chroma / maxRGB -%}
            {%- set v = maxRGB -%}
          {%- endif -%}
          {%- set h = h|round(2)|string -%}
          {%- set s = s|round(2)|string -%}
          {%- set v = v|round(2)|string -%}
          {%- set comma_sep = h + "|" + s + "|" + v -%}
          {%- set data.entries = data.entries + [comma_sep] -%}
        {%- endif -%}
      {%- endfor -%}
      {{ data.entries | join(",") }}
    color_count: '{{ color_hsv_list.split(",")|length }}'

- alias: Change Color
  repeat:
    while:
    - condition: state
      entity_id: !input 'light'
      state: 'on'
    sequence:
    - variables:
        # Total iterations so-far.
        i_total: '{{ repeat.index }}'
        # Current color index we're on.
        i_cur: '{{ (i_total - 1) % color_count }}'
        # Next color index to change to.
        i_next: '{{ i_total % color_count }}'
        # Get the H/S of current colors.
        hsv_cur: '{{ color_hsv_list.split(",")[i_cur] }}'
        hue_cur: '{{ hsv_cur.split("|")[0] }}'
        sat_cur: '{{ (hsv_cur.split("|")[1])|float * 100 }}'
        # Get the H/S of next colors.
        hsv_next: '{{ color_hsv_list.split(",")[i_next] }}'
        hue_next: '{{ hsv_next.split("|")[0] }}'
        sat_next: '{{ (hsv_next.split("|")[1])|float * 100 }}'

        # Get the hue color distance.
        # https://stackoverflow.com/questions/1878907#comment119528981_7869457
        color_distance: '{{ ((hue_next - hue_cur + 540) % 360) - 180 }}'
        color_distance_abs: '{{ color_distance|abs }}'

        # Determine the ideal amount of steps to make between colors.
        iterations_desired: >-
          {% if color_distance_abs < max_color_distance %}
            {{ 1 }}
          {% else %}
            {{ (color_distance_abs / max_color_distance)|round(0, 'floor') }}
          {% endif %}

        # Limit the fade iterations based on configuration.
        fade_iterations: '{{ min(iterations_desired, (max_changes_per_second * transition_seconds)) }}'
        # Calculate what we need to increase/decrease the Hue/Sat by for each fade interation.
        hue_step: '{{ (color_distance / fade_iterations) }}'
        sat_step: '{{ ((sat_next - sat_cur) / fade_iterations) }}'

    - alias: Fade to Next Color
      repeat:
        count: '{{ fade_iterations }}'
        sequence:
          - condition: state
            entity_id: !input 'light'
            state: 'on'
          - variables:
              # Total fade iterations so-far.
              t_total: '{{ repeat.index }}'
              # Current fade iterations we're on.
              t_cur: '{{ (t_total - 1) % fade_iterations }}'
              # Divide the transition in by # of iterations.
              transition_fade: '{{ transition_seconds / fade_iterations }}'
              # Get the current Hue value and compensate if it rolls over 0 or 360.
              transition_hue_calc: '{{ hue_cur + (hue_step * t_cur)|round(2) }}'
              transition_hue: >-
                {% if transition_hue_calc > 360 %}
                  {{ transition_hue_calc - 360 }}
                {% elif transition_hue_calc < 0 %}
                  {{ transition_hue_calc + 360 }}
                {% else %}
                  {{ transition_hue_calc }}
                {% endif %}
              transition_sat: '{{ min(max((sat_cur + (sat_step * t_cur))|round(2), 0), 100) }}'

          # Transition to the next mid-way color or final color.
          - service: light.turn_on
            target:
              entity_id: !input 'light'
            data:
              hs_color: '{{ [transition_hue, transition_sat] }}'
              transition: '{{ transition_fade }}'
          - delay: '{{ transition_fade }}'
4 Likes

Hi I tried to recreate my blueprint with yours (as I was unable to change it to anything but 1second delay), but it seems to insert full white ever other transition for some reason. The light I’m using it on is a philips lightstrip. I took a video, it’s not super clear as the phone tries to correct the colors all the time, but you can see it’s not smooth with the flashes of white. This is with 1s delay as I wanted to recreate what I already have and adjust from there.

the video: https://www.youtube.com/watch?v=j-axwzQ9gx4
the script:

alias: Color Loop Lightstrip
use_blueprint:
  path: mdolnik/color_loop.yaml
  input:
    light: light.lightstrip
    light_colors:
      - Red
      - Salmon
      - Crimson
      - DarkRed
      - Pink
      - HotPink
      - DeepPink
      - Coral
      - OrangeRed
      - DarkOrange
      - Orange
      - Gold
      - Yellow
      - DarkKhaki
      - Lavender
      - Plum
      - Orchid
      - Magenta
      - MediumPurple
      - BlueViolet
      - Purple
      - Indigo
      - SlateBlue
      - GreenYellow
      - Lime
      - LightGreen
      - SpringGreen
      - Green
      - Olive
      - MediumAquamarine
      - LightSeaGreen
      - Teal
      - Cyan
      - Aquamarine
      - DarkTurquoise
      - CadetBlue
      - SteelBlue
      - LightSteelBlue
      - SkyBlue
      - DeepSkyBlue
      - DodgerBlue
      - RoyalBlue
      - Blue
      - Navy
      - Wheat
      - Tan
      - Goldenrod
      - Brown
      - Maroon
    transition:
      hours: 0
      minutes: 0
      seconds: 1
mode: single

I also took z2m output to see how the colors change (the double entries are caused by having the availability and/or last_seen enabled in z2m options - that is not the issue): https://o.o5.ddns.net/DmlAT

it seems to insert full white ever other transition for some reason.

Hmmm, not sure why that would be occurring, I tried mine with one second transition and the same list of colors and I’m only sort of get that occurring when the color change is drastic (eg Blue to Orange) which I assume is because, looking at a color wheel, the colors in between would be going directly across the center of the color wheel and white is in the center (I could be way off though).

So I did a quick test, try adding both of these scripts to your setup:

color_loop_lightstrip_good_colors:
  alias: Color Loop GOOD COLORS
  use_blueprint:
    path: mdolnik/color_loop.yaml
    input:
      light: light.lightstrip
      light_colors:
        - Red
        - OrangeRed
        - Orange
        - Gold
        - Yellow
        - GreenYellow
        - Lime
        - Green
        - Cyan
        - SkyBlue
        - Blue
        - Purple
        - Magenta
        - DeepPink
        - Salmon
      transition:
        hours: 0
        minutes: 0
        seconds: 2

color_loop_lightstrip_bad_colors:
  alias: Color Loop BAD COLORS
  use_blueprint:
    path: mdolnik/color_loop.yaml
    input:
      light: light.lightstrip
      light_colors:
        - Red
        - Cyan
        - OrangeRed
        - SkyBlue
        - Orange
        - Blue
        - Gold
        - Purple
        - Yellow
        - Magenta
        - GreenYellow
        - DeepPink
        - Lime
        - Salmon
        - Green
      transition:
        hours: 0
        minutes: 0
        seconds: 2

Both of these scripts use the exact same list of colors, but Color Loop GOOD COLORS sets the colors in order of “following the outside of the color wheel” and Color Loop BAD COLORS will have each color on the opposite side of the previous color.

On my light there is a huge difference in the transition where the “GOOD” one is smooth and the “BAD” one seems to brighten (or go white) between each color.

EDIT:
I’m not sure if all lights can do this, but if you change the transition time to something longer (eg 10 secs) you can see the live current colors in between the chosen colors if you inspect the current light in the UI while the script is running. The current in-between colors update roughly once a second and tend to kind of jump all over the place, but in general:
2022-06-04_11h22_04
2022-06-04_11h17_10

Also, if the previous “GOOD COLORS” script did not work for you, it may also be possible that some of the named colors are not supported by your bulb?

If you manually add an unsupported color (eg Qwerty) it ends up being white because it doesn’t know what to do with it. I doubt is this your issue as I assume that HA converts the named colors to RGB/HSL/XY before sending the command (your logs seem to back this up).

I made a quick alteration of my script to use the XY colors of your script. This should end up working the same as your old automation with the only difference being how it does the looping.

Blueprint (homeassistant/blueprints/mdolnik/color_loop_xy.yaml):

blueprint:
  name: Color Loop XY
  description: 'TESTING XY COLORS'
  domain: script
  input:
    light:
      name: Light
      selector:
        entity:
          domain: light
          multiple: false
    light_colors:
      name: Colors
      selector:
        select:
          multiple: true
          custom_value: true
          options:
          - '[ 0.217,0.077 ]'
          - '[ 0.157,0.05 ]'
          - '[ 0.136,0.04 ]'
          - '[ 0.137,0.065 ]'
          - '[ 0.141,0.137 ]'
          - '[ 0.146,0.238 ]'
          - '[ 0.151,0.343 ]'
          - '[ 0.157,0.457 ]'
          - '[ 0.164,0.591 ]'
          - '[ 0.17,0.703 ]'
          - '[ 0.172,0.747 ]'
          - '[ 0.199,0.724 ]'
          - '[ 0.269,0.665 ]'
          - '[ 0.36,0.588 ]'
          - '[ 0.444,0.517 ]'
          - '[ 0.527,0.447 ]'
          - '[ 0.612,0.374 ]'
          - '[ 0.677,0.319 ]'
          - '[ 0.701,0.299 ]'
          - '[ 0.667,0.284 ]'
          - '[ 0.581,0.245 ]'
          - '[ 0.477,0.196 ]'
          - '[ 0.385,0.155 ]'
          - '[ 0.301,0.116 ]'
          - '[ 0.217,0.077 ]'
    transition:
      name: Transition Time
      selector:
        duration: {}
      default:
        hours: 0
        minutes: 0
        seconds: 1
sequence:
- alias: Set up variables
  variables:
    light_colors: !input 'light_colors'
    color_count: '{{ light_colors|length }}'
    transition: !input 'transition'
    transition_seconds: '{{ ((transition.hours)*60*60) +  ((transition.minutes)*60)
      + transition.seconds }}'
- alias: LOOP!
  repeat:
    while:
    - condition: state
      entity_id: !input 'light'
      state: 'on'
    sequence:
    - variables:
        i_total: '{{ repeat.index }}'
        i_cur: '{{ i_total % color_count }}'
    - service: light.turn_on
      target:
        entity_id: !input 'light'
      data:
        xy_color: '{{ light_colors[i_cur] }}'
        transition: '{{ transition_seconds }}'
    - delay: '{{ transition_seconds }}'

Script:

color_loop_xy_test:
  alias: Color Loop Lightstrip XY
  use_blueprint:
    path: mdolnik/color_loop_xy.yaml
    input:
      light: light.color_lamp_tall_entry_way
      light_colors:
        - '[ 0.217,0.077 ]'
        - '[ 0.157,0.05 ]'
        - '[ 0.136,0.04 ]'
        - '[ 0.137,0.065 ]'
        - '[ 0.141,0.137 ]'
        - '[ 0.146,0.238 ]'
        - '[ 0.151,0.343 ]'
        - '[ 0.157,0.457 ]'
        - '[ 0.164,0.591 ]'
        - '[ 0.17,0.703 ]'
        - '[ 0.172,0.747 ]'
        - '[ 0.199,0.724 ]'
        - '[ 0.269,0.665 ]'
        - '[ 0.36,0.588 ]'
        - '[ 0.444,0.517 ]'
        - '[ 0.527,0.447 ]'
        - '[ 0.612,0.374 ]'
        - '[ 0.677,0.319 ]'
        - '[ 0.701,0.299 ]'
        - '[ 0.667,0.284 ]'
        - '[ 0.581,0.245 ]'
        - '[ 0.477,0.196 ]'
        - '[ 0.385,0.155 ]'
        - '[ 0.301,0.116 ]'
        - '[ 0.217,0.077 ]'
      transition:
        hours: 0
        minutes: 0
        seconds: 1
  mode: single

Maybe see if this works for you.

That-said if using XY works for you and the “good” color order does not work then that is a bummer.

That would mean the color loop would have to have a different means of setting the color on a per-light basis :roll_eyes:

If you add support to multiple light it can be an easy way to have a party mode script

Simply adding support for multiple entities would cause errors in the while condition:

while:
- condition: state
  entity_id: !input 'light'
  state: 'on'

…as it expects exactly one entity. If anyone knows more about state conditions supporting multiple entities, please chime in.

In the meantime you can use the Blueprint as-is on multiple lights by adding a group as the light entity…

groups.yaml:

my_custom_group:
  name: My Custom Group
  entities:
    - light.my_light_1
    - light.my_light_2
    - light.my_light_3

Then add group.my_custom_group as the light.

The group won’t show up in the auto-complete of the lights field unless the blueprint is changed from:

    light:
      name: Light
      selector:
        entity:
          domain: light
          multiple: false

to:

    light:
      name: Light
      selector:
        entity:
          domain:
            - light
            - group
          multiple: false

But manually adding group.my_custom_group seems to work without modifying the blueprint.

EDIT:
Also the notion of being able to disable the color loop by turning the light off doesn’t quite work well with a light group. If you have 3 lights running on the color loop and you turn one off, it will get turned right back on with the next iteration. You would need to be able to turn all 3 off within the loop transition time in order to have the script disabled which is like whack-a-mole if you have the transition set to one second. If you already have a button to turn on/off the group as a whole, or a button to turn on/off the script it should be fine though.

Hi, I tried the scripts with following results:

good_colors: seems to work perfect, but there’s 2 flashes of white around the light blue/blue - blue/red transition
bad_colors: white flash every transition
colors_xy: works exactly as mine, but the flashes are still there

I think it has to do with the order of the colors, look at how it disrupts around the blue / red (violet?). Could it just be this? I don’t have full mobility in right hand so it is 100% possible I misclick when filling the colors in, every 2-3 click my hand twitches and I select instead of click or such (though that should not be the issue in the scripts where you pre-filled the colors).

how the colors go on the wheel with colors_xy, let me know if you want me to record the other 2 too):

1 thing I struggle with is how to turn the script off from UI (is the reason why I made my BP automation for the little toggle in ui) I had to reboot HA after testing each script as when I switch the strip off and on the script still runs. Which leads me to ask what is the advantage of it being script (as it has been suggested to me few times)?

You can add the script as an item to the overview page which gives you a Run/Cancel link for a given script.

  1. Edit the Overview/Dashboard page
  2. Click + Add Card
  3. Search by Entity and type in the name of the script
  4. Check the checkbox for the script you want
  5. Click CONTINUE
    2022-06-05_10h42_20
  6. Click ADD TO DASHBOARD
    2022-06-05_10h42_38
  7. You now have a card which you click Run or Cancel to control the script.
    2022-06-05_10h43_06
    You can also select more scripts to have the card have multiple scripts or select lights and their corresponding scripts.

Also clicking on the script’s name will give you an edit link (pencil icon) to the script so you can easily update the colors or transition times.

The main issue (from what I can tell) with the automation you were using was it was triggered by a schedule process every single second. These types of things, while possible, are usually only intended for running a handful of times a day and not every second.
I think the main issue is not that the automation gets triggered so frequently, but that it would also logging this occurrence every single second in the history and logbook (unless you manually disable the logging) which would lead to all of these pointless database entries occurring.

I do agree it’s kind of annoying that there is no stop button in the UI Scripts page, only a Play button, but I figure most scripts are intended to be a “do something once” type of command.

The looping portion could be changed to be an automation, but (I haven’t tested this) I do not think turning off the automation would stop the loop, it would only stop the original triggering condition from starting a new process.

This is why the while loop in this script has the condition that the light has to be on in order to continue looping.

Hmmm, I’m not sure why this would occur, I haven’t had issues with this except when I was trying out adding light groups in my comment above. Regardless, adding the script Run/Cancel link to the dashboard will stop the looping to a new color (although if its halfway to its next color it will continue to the next color and stop)

1 Like

So with my comment above about the path of colors led me to want to overhaul this whole thing.

I did some testing with setting the colors on the light via rgb, hsl, xy, and color names, and for all of this whenever a color change “crossed the center of the color wheel” it would go white or become much whiter.

So the way we tell the light which color to change to seems to have no effect on reducing this “wheel center” issue, so it led me to thinking that we just need to tell the light to go to a color in between…

Say we want to transition from Red to Cyan, the transition would end up crossing the center ending up white halfway through:
2022-06-05_11h17_56

If we have the code determine what the halfway color would be, then we can avoid the center:
2022-06-05_11h18_45

This would still end up with a lighter orange 1/3 way there and lighter green 2/3 way there.

But if we add another midway point, then we can reduce the brightness issue even more:
2022-06-05_11h19_59

Basically the color wheel is a representation of Hue/Saturation:

  • Hue being the color changes that go around the wheel using values 0-360 (eg 360 degrees in a circle)
  • Saturation being the distance from the center 0-100 (percent)
  • The L in HSL is luminosity which is not on the color wheel as it is the brightness controlled separately for the light.

So all we would need to do is get the before/after color in HSL form, and find the midway values between the hue and saturation and divide up the duration in those parts.

I am currently about 75% towards getting this concept working which also adds a nice side-effect if you ramp up the number of midway points to a huge number, you can get a light which does not support color transitions (I have a few myself) now be able to gradually change colors as this will be constantly be sending new colors to the light.

I should have this update ready to post here in the next few days to a week depending on any issues I encounter.

2 Likes

Thank you for your detailed explanation and efforts on making this work!

I can see how the automation could spam the logbook and history which it absolutely did, but as you mentioned I disabled that manually in my setup. I didn’t really put much thought into how it could affect others, I had it running as automation for months before I left the procrastination stage and converted it to BP, by which time I forgot about most of its issues.

Happy to see someone more knowledgeable than me pick up on it, I look forward to the update!

1 Like

Awesome work folks.
I did give my tweak a few hours and ran into problems.
This is way past where I was.
I think using one of the number schemes to call colors and math to transition that is the way to go.
You also want to always ‘go around the circle’ the same direction.
Read somewhere that x,y works great for this because of how it does it, but being honest I have no idea how or why. Something about recycling after you get thru the entire color scale and start over. Not sure.

Some Calculus or Trig math over my head could probably make a perfect circle transition.

There are a lot of HA resources eaten up here, however. Need to take that into mind. The more ‘light-on’ commands sent, the more HA has to do.

No worries on figuring it out, I ended up getting it sorted last night. No Calculus or Trig math needed as the hue value in HSL/HSV is already “circular” as in its value is 0-360 (degrees) so we just need to gradually increase/decrease that number.

Refer to the next post for the draft version of this updated blueprint.

Indeed it does, I was under the impression that having this as a script instead of an automation would reduce the DB writes, I was wrong.

I assumed that since I didn’t see any updates in the logger or history when colors were changed that it leaves the DB alone… NOPE!

I tried using my new version of the script on 5 lights, and after 4 hours my DB increased 100 MBs. Turned the scripts off and after another 4 hours my DB only increased 150 KBs.

Looking in the DB it seems that there is:

  1. A state entry for the script starting a new color
  2. A state entry for the light checking to see if it’s already on.
  3. A state entry for every color change in between the colors.
  4. A state entry for the light checking to see if it’s already on (again).
  5. An event entry for every event fired (ie: EVERYTHING above is also duplicated here)

This basically means that anyone using this blueprint NEEDS to ensure that the light(s) are prevented from being logged otherwise they’re gonna have a bad time.

Because of this, I’ve temporarily disabled the import button from the main post, people can manually copy/paste the embedded yaml from the main post if they want to play around with it.

As promised here’s the latest version…

READ THIS FIRST

Before using this blueprint, be warned that it puts a real heavy load on Home Assistant and its database if configured to change colors at a quick pace. While these color changes do not show up in the History or the Logbook, they are still logged in the database unless manually configured to not do so. If Home Assistant is running on an SD card on a Raspberry Pi, large amounts of DB changes can technically cause damage over time. As of this post, this blueprint should be avoided unless you understand these issues.

With that out of the way we do need a solution for the database spam, the only thing I can think of that works at the moment is to literally exclude the light(s) from getting logged for anything.

recorder:
  exclude:
    entities:
      # Exclude any logging of the light.
      - light.my_light
      # Exclude any logging of the color loop script for this light.
      - script.my_light_color_loop
    event_types:
      # Don't record ANY service calls
      - call_service

I have added a feature request to see if there’s a better way: Allow an ability to call a service without any logging/recording

All the notes above are now added to the main post.

Now to the updated blueprint:

blueprint:
  name: Color Loop
  description: '## Color Loop:
    Version: `0.1.0` (2022-06-06)

    Loops through predetermined colors for a chosen light.

    This will continue indefinitely until the either the light is turned off or the
    script is manually disabled.

    ## Configuration:

    ### Light:

    For the Light, choose the light entity to loop through the colors chosen below.

    ***Note: This will NOT work for lights which do not gradually transition colors.
    In these cases, the light will instantly change to the next color after the transition
    time instead of gradually changing.***
    TODO: Update this to explain that its possible with certain settings.

    TODO: Explain to use groups if multiple lights are desired.

    TODO: We need to find out how to avoid DB spamming...

    ### Colors:

    Set the colors to loop through.

    Any colors set to black (R:0 G:0 B:0) will be omitted from the loop.

    *Note: There is no current way to re-order the chosen colors, you must change
    or remove (set to black) colors to change the order.*

    *Note: Since RGB colors do not translate well to colored lights (the current
    brightness of the light does not get changed by this script) the color previews
    may not look accurate to the final result. In order to get the closest result
    set the color picker to the TOP of the color selection.

    TODO: Refactor above or explain that this is different on mobile.

    ### Transition Time:

    Choose the time it takes to cycle through each color.

    ### Max Color Distance:

    Determines how many colors in between the chosen colors should be transitioned to.

    This prevents issues where the color fades to white in between colors by
    dynamically picking colors in between the chosen colors.

    This value basically means the minimum amount of degrees of the color wheel
    should result in an in-between color to be added.


    For Example: Red and Cyan are on the opposite side of the color wheel (180 degrees apart):

    - Choosing `180` will cause the color transition going straight across resulting in a fade to white in between the colors.

    - Choosing `90` will have a new color added every 90 degrees, so in this case a new transitional color will be added at Purple.

    - Choosing `60` will have a new color added every 60 degrees, so in this case two transitional colors will be added at Blue and Pink.

    - Choosing `30` will have a new color added every 30 degrees, so in this case three transitional colors will be added at Red/Pink, Purple, and Blue.

    - Choosing `1` will have a new color added every 1 degrees, so in this case the light will be set to a new color for EVERY color along the way.


    If using a light which does not support color transitions, this can be set
    to a very low number (eg 1-10) in order to fake the color transition by
    updating the colors gradually along the way.

    In summary, for performance reasons, keep this value as high as possible and
    lower it as needed to avoid the color going brighter in between the
    chosen colors.

    ### Max Changes Per Second:

    With the combination of `Transition Time` and `Max Color Distance` both being
    set to a low value, this could end up resulting in WAY too many calls to the
    light to update its color (up to 180 calls per second). In order to avoid
    overloading Home Assistant, this value can be used in order to set a maximum
    limit of how many calls per second to change the light during transition can
    be made.

    For performance reasons, this should be as low as possible, but with lights
    which do not support color transitions, this can be increased in order to
    have smoother color transitions.
    '
  domain: script
  input:
    light:
      name: Light
      selector:
        entity:
          domain:
            - light
            - group
          multiple: false
    transition:
      name: Transition Time
      selector:
        duration: {}
      default:
        hours: 0
        minutes: 0
        seconds: 15
    max_color_distance:
      name: Max Color Distance
      default: 60
      selector:
        number:
          min: 1
          max: 180
    max_changes_per_second:
      name: Max Changes Per Second
      default: 1
      selector:
        number:
          min: 1
          max: 5
    color_1:
      name: Color 1
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_2:
      name: Color 2
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_3:
      name: Color 3
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_4:
      name: Color 4
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_5:
      name: Color 5
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_6:
      name: Color 6
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_7:
      name: Color 7
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_8:
      name: Color 8
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_9:
      name: Color 9
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_10:
      name: Color 10
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_11:
      name: Color 11
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}
    color_12:
      name: Color 12
      description: 'Set to black to omit this color'
      default:
        - 0
        - 0
        - 0
      selector:
        color_rgb: {}

# If we trigger the script while its running, restart the script.
mode: restart

sequence:
- alias: Set up main variables
  variables:
    transition: !input 'transition'
    transition_seconds: '{{ ((transition.hours)*60*60) +  ((transition.minutes)*60)
      + transition.seconds }}'
    max_color_distance: !input 'max_color_distance'
    max_changes_per_second: !input 'max_changes_per_second'
    color_rgbs:
      - !input 'color_1'
      - !input 'color_2'
      - !input 'color_3'
      - !input 'color_4'
      - !input 'color_5'
      - !input 'color_6'
      - !input 'color_7'
      - !input 'color_8'
      - !input 'color_9'
      - !input 'color_10'
      - !input 'color_11'
      - !input 'color_12'
    # Convert the array of RGB colors to a comma separated list of HSV values.
    # Exclude any colors set to black.
    # https://community.home-assistant.io/t/using-hsv-hsb-to-set-colored-lights/15472
    # https://github.com/home-assistant/core/issues/33678#issuecomment-609424851
    # https://stackoverflow.com/a/56141280/4147996
    color_hsv_list: >-
      {%- set data = namespace(entries=[]) -%}
      {%- for color_rgb in color_rgbs -%}
        {%- if color_rgb != [0,0,0] -%}
          {%- set r = (color_rgb[0]/255) -%}
          {%- set g = (color_rgb[1]/255) -%}
          {%- set b = (color_rgb[2]/255) -%}
          {%- set maxRGB = max(r,g,b) -%}
          {%- set minRGB = min(r,g,b) -%}
          {%- set chroma = maxRGB - minRGB -%}
          {%- if chroma == 0 -%}
            {%- set h = 0 -%}
            {%- set s = 0 -%}
            {%- set v = maxRGB -%}
          {%- else -%}
            {%- if r == minRGB -%}
              {%- set h = 3-((g-b)/chroma) -%}
            {%- elif b == minRGB -%}
              {%- set h = 1-((r-g)/chroma) -%}
            {%- else -%}
              {%- set h = 5-((b-r)/chroma) -%}
            {%- endif -%}
            {%- set h = 60 * h -%}
            {%- set s = chroma / maxRGB -%}
            {%- set v = maxRGB -%}
          {%- endif -%}
          {%- set h = h|round(2)|string -%}
          {%- set s = s|round(2)|string -%}
          {%- set v = v|round(2)|string -%}
          {%- set comma_sep = h + "|" + s + "|" + v -%}
          {%- set data.entries = data.entries + [comma_sep] -%}
        {%- endif -%}
      {%- endfor -%}
      {{ data.entries | join(",") }}
    color_count: '{{ color_hsv_list.split(",")|length }}'

- alias: Change Color
  repeat:
    while:
    - condition: state
      entity_id: !input 'light'
      state: 'on'
    sequence:
    - variables:
        # Total iterations so-far.
        i_total: '{{ repeat.index }}'
        # Current color index we're on.
        i_cur: '{{ (i_total - 1) % color_count }}'
        # Next color index to change to.
        i_next: '{{ i_total % color_count }}'
        # Get the H/S of current colors.
        hsv_cur: '{{ color_hsv_list.split(",")[i_cur] }}'
        hue_cur: '{{ hsv_cur.split("|")[0] }}'
        sat_cur: '{{ (hsv_cur.split("|")[1])|float * 100 }}'
        # Get the H/S of next colors.
        hsv_next: '{{ color_hsv_list.split(",")[i_next] }}'
        hue_next: '{{ hsv_next.split("|")[0] }}'
        sat_next: '{{ (hsv_next.split("|")[1])|float * 100 }}'

        # Get the hue color distance.
        # https://stackoverflow.com/questions/1878907#comment119528981_7869457
        color_distance: '{{ ((hue_next - hue_cur + 540) % 360) - 180 }}'
        color_distance_abs: '{{ color_distance|abs }}'

        # Determine the ideal amount of steps to make between colors.
        iterations_desired: >-
          {% if color_distance_abs < max_color_distance %}
            {{ 1 }}
          {% else %}
            {{ (color_distance_abs / max_color_distance)|round(0, 'floor') }}
          {% endif %}

        # Limit the fade iterations based on configuration.
        fade_iterations: '{{ min(iterations_desired, (max_changes_per_second * transition_seconds)) }}'
        # Calculate what we need to increase/decrease the Hue/Sat by for each fade interation.
        hue_step: '{{ (color_distance / fade_iterations) }}'
        sat_step: '{{ ((sat_next - sat_cur) / fade_iterations) }}'

    - alias: Fade to Next Color
      repeat:
        count: '{{ fade_iterations }}'
        sequence:
          - condition: state
            entity_id: !input 'light'
            state: 'on'
          - variables:
              # Total fade iterations so-far.
              t_total: '{{ repeat.index }}'
              # Current fade iterations we're on.
              t_cur: '{{ (t_total - 1) % fade_iterations }}'
              # Divide the transition in by # of iterations.
              transition_fade: '{{ transition_seconds / fade_iterations }}'
              # Get the current Hue value and compensate if it rolls over 0 or 360.
              transition_hue_calc: '{{ hue_cur + (hue_step * t_cur)|round(2) }}'
              transition_hue: >-
                {% if transition_hue_calc > 360 %}
                  {{ transition_hue_calc - 360 }}
                {% elif transition_hue_calc < 0 %}
                  {{ transition_hue_calc + 360 }}
                {% else %}
                  {{ transition_hue_calc }}
                {% endif %}
              transition_sat: '{{ min(max((sat_cur + (sat_step * t_cur))|round(2), 0), 100) }}'

          # Transition to the next mid-way color or final color.
          - service: light.turn_on
            target:
              entity_id: !input 'light'
            data:
              hs_color: '{{ [transition_hue, transition_sat] }}'
              transition: '{{ transition_fade }}'
          - delay: '{{ transition_fade }}'

This adds the following changes:

  • Colors are now picked via UI color picker:
    • There is no such thing as a multi-value color picker selector (yet) so we have to have a fixed number of colors.
    • There is a max limit of 12 colors (easy enough to add more, but not sure one would want to do so)
    • Setting a color to black will omit the color from being used.
  • Groups are now allowed as well as light entities.
  • The color loop will now pick colors in between the main colors to avoid fading to white mid-transition.
  • In order to fine-tune the in-between colors a Max Color Distance setting has been added:
    • This will determine how many colors to add in between, based on the setting as well as how “far apart” the colors are.
  • In order to prevent colors from changing too quickly there is a Max Changes Per Second setting.

Future changes:

  • We really need to fix the problem of DB spamming without needing to exclude the light from ANY logging.
  • I would like to rename Max Color Distance, the name is a bit misleading and I can’t think of anything better at the moment.
  • Max Changes Per Second should probably be changed to Max Changes Per Minute so we can have limits slower than once a second.
1 Like

Hm, I have been running my version of the blueprint for literal months for 8h+ a day at 1 sec interval and my db (mariadb over network) is:

MariaDB [(none)]> SELECT TABLE_SCHEMA AS `Database`,  ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS `Size (MB)`  FROM information_schema.TABLES WHERE TABLE_SCHEMA="ha1db";
+----------+-----------+
| Database | Size (MB) |
+----------+-----------+
| ha1db    |   3218.83 |
+----------+-----------+
1 row in set (0.005 sec)

I do a full mysqldump every midnight via cron as a backup (and the full dump includes db’s of 2 more HA remote instances), and although I’m too inept to figure out how to only look at the size of the ha1db over last 7 days, the whole .sql file increases about 10-80Mb (and in some case even reduces in size) a day.

So If I interpret the data correctly I’m not seeing any drastic increase such as being suggested? There is also no observable load on the cpu whether the automation runs or not (rpi4b where both ha and z2m runs alongside some other containers).

edit: the only thing excluded in recorder.yaml is single entity being the automation itself

I had an idea to try something like this before to emulate the built-in color loop feature (found on Hue bulbs) on my other bulbs. I actually succeeded with a node-red flow, but I found that the zigbee network would become too bogged down by the commands.
I still will try this anyway as I’m intrigued :star_struck:

I guess I don’t have a sense of what a “normal” HA database size would look like. To me, 3 gigs seems quite large, but I guess it really comes down to how many devices you have and what they’re all doing.

When I tested this before (without excluding the entities from the DB) my DB went from ~300 MB to ~400 MB in a day which is like a 30% increase in the whole DB. That same increase on yours would not be as noticeable.

Another difference could be in my setup I had 5 lights running this color loop script, 2 of these lights support gradual transitions and 3 do not. All lights were configured to change colors roughly every minute and a half. But with this script, the 3 lights which do not support color transitions had to be configured to update the colors twice a second. (ie: While the end result was the same, 2 lights were sending events to the DB every 1.5 mins, and 3 lights sent events to the DB 2x per second).

My issue was mostly concerned with the sheer amount of needless logging that occurs, but I guess according to HA’s docs on the Recorder’s commit_interval the SD Card issue is less about the amount of logging that occurs, but is the frequency of how often logging occurs.

Maybe I am being protective and paranoid about this being an issue, but I don’t want to be responsible for breaking anyone’s setup.

Regardless, I have pushed a new version (0.1.1) with the main difference being that the docs have been heavily updated to explain the caveats with the event logging and now explains increasing the commit_interval can be an alternate solution to completely excluding the lights.
I have also reduced the limit of Max Changes Per Second from 10 to 5 as I don’t think anyone needs the colors to update that fast.

1 Like