Color Loop Blueprint with Configurable Colors and Transition Time

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

Been using this for a while now after switching from mine, with this config on Philips lightstrip I get silky smooth transition across the whole color spectrum with just the 3 primary colors defined:

I’ll confess I have no idea how it works as the HA ui only ever shows the light at being one of the 3 colors., but I’m happy with the results :slight_smile:

Any known issues with the color selection UI not working? Just installed this and it’s just blank where colors should be selected.

Haven’t had that happen yet, but you should be able to save the blueprint like that and then edit the resulting script, switch to yaml editor and add the colors in manually:

alias: Color Loop lightstrip
use_blueprint:
  path: mdolnik/color_loop.yaml
  input:
    light: light.lightstrip
    transition:
      hours: 0
      minutes: 0
      seconds: 30
    max_color_distance: 90
    color_1:
      - 255
      - 0
      - 0
    color_2:
      - 0
      - 0
      - 255
    color_3:
      - 0
      - 255
      - 0
    max_changes_per_second: 1
mode: single
1 Like

Ok, I confirm it works by edit yaml (I had same issue).
Just one question… Is there a way to save the light values before starting the loop and restore them once stopped?

I believe the following should work (I’m still fairly new to HA so there may be a better solution)

When turned on:
service - scene.create - Give it a name, choose your entities -
Activate your color changes

When turned off:
deactivate color changes
service - scene.apply - {your scene name}

I am also running into the problem where the color selector isn’t working, and I am also getting the error

Error: UndefinedError: list object has no element 1

In the variable declaration step. My guess is recent HA changes may have broken the script, but I am not certain.

I’m using this blueprint with a lightgroup. I have the case that sometimes I would like to turn off a single light in the group. Unfortunately, if I use the script directly on the lightgroup this light again gets turned on on the next iteration. Another option would be to have seperate colorloop scripts for the individual lights, but when they’re no in sync. Does anyone have an idea how this could be solved?

Also having the same issues , i think the recent changes are the thing why

This is one of the best posts I have ever read. The way you explain the path of the light with the diagram is genius and saved me a ton of time in a script I’m building to simulate a sunrise across the lights in my home.

EDIT I should note, my bulbs don’t support color transition, so I have done the calculations to adjust color slowly if you are interested in those.

I love this!

May I ask is there anyway to add randomness to the time? I’m thinking of trying to adapt this script to be able to make ocean effects and flame effects, but a constant transition time isn’t as realistic as variable ones.

I’ve tried going into Yaml mode and adding “{{ range(1, 31)|random}}” but that didn’t work.

Any suggestions?

Happy Holidays!

To add some context after half a year, soon after the last post I migrated the db to remote postgres db running on nas, disabled the zipped backups as the nas takes daily zfs snapshots and after half a year the uncompressed database size is

SELECT pg_size_pretty ( pg_database_size ('ha1db'))

pg_size_pretty
16 GB

So thats 14GB increase in 6 months say 2GB uncompressed a month. This might seem gigantic, but there are points to take into consideration:

  • I do not think any of it now comes from this blueprint as I only switch 3 colors with long delay
  • I have a :poop:ton of sensors and 2 linked remote HA instances
  • Data is set to be kept for 365 days
  • There’s 24TB free space on the nas