Skip to content

Instantly share code, notes, and snippets.

@AllardKatan
Forked from mdolnik/color_loop.yaml
Last active November 26, 2023 00:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AllardKatan/705b993278ff780bae43e13f0e413dcf to your computer and use it in GitHub Desktop.
Save AllardKatan/705b993278ff780bae43e13f0e413dcf to your computer and use it in GitHub Desktop.
Color Loop
blueprint:
name: Color Loop
description: '# Color Loop
### Version: `2.0.x` (2023-11-25)
#### Tested on Home Assistant Version: `2023.11.3`
#### Blueprints Page: <https://community.home-assistant.io/t/427682>
Loops through predetermined colors for a chosen light. (version edited by Allard Katan to add randomizer)
Can be configured to either:
- Loop through colors whenever the light is on.
- Loop through colors only when both the light and a toggle switch are on.
## 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 section regarding solutions to mitigate this issue. If you do
not understand these issues, this blueprint should probably be avoided.**
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click here for solutions
to help avoid overloading Home Assistant</u></b></big></summary> <hr>
### 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;- automation.my_light_color_loop
&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)
<hr> </details> '
domain: automation
input:
light:
name: Light
description: ' The Light entity or Light Group entity to loop through the colors
chosen below. '
selector:
entity:
domain:
- light
- group
multiple: false
helper_toggle:
name: Color Loop Toggle
description: 'An ON/OFF toggle to enable/disable enable/disable the color looping
for the chosen Light.
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here For More Info</u></b></big></summary>
<hr>
A single Color Loop Toggle can be used across multiple Color Loop automations
to control multiple lights at the same time with each light having their own
set of colors / rules.
**If you don''t want to add a separate toggle and want the light to _always_
loop colors _whenever it is on_ then set this value to be the same light entity
as above.**
### General rules in how the toggle affects the color loop:
- If the `Toggle` is turned `ON` while the `Light` is `ON`: `Color looping
starts`.
- If the `Toggle` is turned `OFF` while the `Light` is `ON`: `Light is set
to current color and stops color-looping`.
- If the `Toggle` is turned `ON`/`OFF` while the `Light` is `OFF`: `Nothing
Happens`.
- If the `Light` is turned `ON` while the `Toggle` is `ON`: `Light is turned
on and Color looping starts`.
- If the `Light` is turned `ON` while the `Toggle` is `OFF`: `Light is turned
on and no Color Looping occurs`.
- If the `Light` is turned `OFF` while the `Toggle` is `ON`/`OFF`: `The light
turns OFF and Color Looping stops`.
<hr> </details>
<br>
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here For Configuration
Instructions</u></b></big></summary> <hr>
Configure using one of the options below.
### Option 1: Create Toggle via UI:
1. Go to `Settings` > `Devices & Services` > `Helpers`.
2. Press `+ Create Helper`.
3. Choose `Toggle`.
4. Add a `Name` and save.
5. Select this new Toggle for this blueprint.
### Option 2: Create Toggle via YML Configuration:
Add the following YML configuration:
<code> <!-- Code Blocks in markdown seem to be broken, the following is a
work-around --> input_boolean:
&nbsp;&nbsp;my_color_loop_toggle:
&nbsp;&nbsp;&nbsp;&nbsp;name: My Color Loop Toggle
&nbsp;&nbsp;&nbsp;&nbsp;icon: mdi:palette-outline </code>
Select this new Toggle for this blueprint.
### Option 3: Configure Color Loop to ALWAYS be running whenever the light
is on:
Set this value to be the same light entity as above.
_Note: Light Groups are intentionally excluded from being added as a toggle
as turning off individual lights would be problematic. You will need to add
a toggle entity here in order to add color looping to groups._
<hr> </details> '
default:
selector:
entity:
domain:
- light
- input_boolean
multiple: false
transition:
name: Transition Time
description: ' Choose the time it takes to cycle through each color. '
selector:
duration: {}
default:
hours: 0
minutes: 0
seconds: 15
randomizer:
name: Random start offset
description: 'A random time between zero and this number times the total cycle is given as a delay to the start of the automation.
Use a value >0 if you have multiple lights with multiple automations and you want them to not all change at the same moment'
default: 0
selector:
number:
min: 0
max: 1
step: 0.02
mode: slider
max_color_distance:
name: Max Color Distance
description: ' Helps control how the colors are transitioned.
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here For More Info</u></b></big></summary>
<hr>
This value controls how colors in between the chosen colors are dynamically
chosen in order to prevent issues where:
- The color fades to white (or a lighter color) in between the chosen colors.
- The color does not transition smoothly or at all.
The color wheel uses (Hue) which are degrees (0-359) which represent where
colors are along the curve of the wheel.
This `Max Color Distance` value basically means the cut-off amount of degrees
on the color wheel which should result in new in-between color(s) to be added.
If these extra colors are not added, then the color transition will follow
a straight path across the wheel, likely resulting in colors we don''t want
along the way.
For example: If we wanted to transition from Red to Cyan which are on the
opposite side of the color wheel (180 degrees apart):
- Setting `Max Color Distance` to `180` will cause the color transition to
go straight across resulting in a fade to white in between the colors.
![Image](https://user-images.githubusercontent.com/22206300/232822236-8cca7b37-0a72-4cbc-b198-4d1fb1dd2325.png)
- Setting `Max Color Distance` to `90` will have a new color added every `90`
degrees. In this case a new transitional color will be added at Purple.
![Image](https://user-images.githubusercontent.com/22206300/232822243-2a3b710b-b72f-4d5c-b4f3-e0f35374b602.png)
- Setting `Max Color Distance` to `60` will have a new color added every `60`
degrees. In this case two transitional colors will be added at Blue and Pink.
![Image](https://user-images.githubusercontent.com/22206300/232822244-a187e691-8c85-4266-a904-d6f179a0a0af.png)
- Setting `Max Color Distance` to `30` will have a new color added every `30`
degrees. In this case three transitional colors will be added at Red/Pink,
Purple, and Blue.
![Image](https://user-images.githubusercontent.com/22206300/232822245-60aea03f-845e-47ec-a4a1-4602c7601717.png)
- Setting `Max Color Distance` to `1` will have a new color added every `1`
degree. In this case the light will have a command sent to change to a new
color for EVERY color along the way.
![Image](https://user-images.githubusercontent.com/22206300/232822249-2db23295-3cde-430c-b482-be303b84f23f.png)
<hr> </details>
**For performance reasons, _keep this value as high as possible_. Lower numbers
will result in more commands being sent to the light putting more burden on
Home Assistant**
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here If You Are
Unsure What Value To Set This To</u></b></big></summary> <hr>
Try saving the Color Loop with `Max Color Distance` set to `180` and `Transition
Time` set to something quick like `3 seconds`.
- If the light fades smoothly but gets brighter/whiter between colors, then
reduce `Max Color Distance` by about `60` and save. Keep doing this until
the color fading is acceptable.
- **If there is no fading at all between colors** (it jumps directly from
color to color every 3 seconds) then this light does not support color transitions.
You may want to choose a number between `1` - `15` depending on how close
the colors are to each other and the desired `Transition Time`. This will
essentially fake the color transitions by sending color-change commands to
the light more often.
<hr> </details> '
default: 60
selector:
number:
min: 1.0
max: 180.0
step: 1.0
mode: slider
max_changes_per_second:
name: Max Changes Per Second
description: ' The maximum number of commands (per second) that can be performed
for color changes.
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here For More Info</u></b></big></summary>
<hr>
Without `Max Changes Per Second`, if a combination of `Transition Time` and
`Max Color Distance` were both being set to a low value, it would 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.
Note: This being set to 1 second does not mean that it will always be sending
commands every second. The transition times and/or how close the colors are
to each other will cause the time between commands to vary quite a bit, this
is basically just a speed limit.
<hr> </details>
**For performance reasons, this should be set as low as possible**, but with
lights which do not support color transitions, this can be increased in order
to have smoother color transitions. '
default: 1
selector:
number:
min: 1.0
max: 5.0
step: 1.0
mode: slider
color_1:
name: Color 1
description: ' Set to black to omit this color.
<!-- For some reason we need something directly above the details tag to have
it render properly --> <details> <summary><big><b><u>Click Here For More Info</u></b></big></summary>
<hr>
These are the main colors that will be looped 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.*
<hr> </details> '
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/AllardKatan/705b993278ff780bae43e13f0e413dcf
mode: restart
variables:
color_loop_light: !input light
helper_toggle: !input helper_toggle
toggle_is_used: '{{ color_loop_light != helper_toggle }}'
toggle_transition_check_time: 1.5
transition: !input transition
transition_seconds: '{{ ((transition.hours)*60*60) + ((transition.minutes)*60) +
transition.seconds }}'
randomizer_value: !input randomizer
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
color_hsv_list_csv: "{%- set data = namespace(entries=[]) -%} {%- for color_rgb
in color_rgbs -%}\n {%- if color_rgb != [0,0,0] -%}\n {%- set r = (color_rgb[0]/255)
-%}\n {%- set g = (color_rgb[1]/255) -%}\n {%- set b = (color_rgb[2]/255)
-%}\n {%- set maxRGB = max(r,g,b) -%}\n {%- set minRGB = min(r,g,b) -%}\n
\ {%- set chroma = maxRGB - minRGB -%}\n {%- if chroma == 0 -%}\n {%-
set h = 0 -%}\n {%- set s = 0 -%}\n {%- set v = maxRGB -%}\n {%-
else -%}\n {%- if r == minRGB -%}\n {%- set h = 3-((g-b)/chroma) -%}\n
\ {%- elif b == minRGB -%}\n {%- set h = 1-((r-g)/chroma) -%}\n {%-
else -%}\n {%- set h = 5-((b-r)/chroma) -%}\n {%- endif -%}\n {%-
set h = 60 * h -%}\n {%- set s = chroma / maxRGB -%}\n {%- set v = maxRGB
-%}\n {%- endif -%}\n {%- set h = h|round(2)|string -%}\n {%- set s =
s|round(2)|string -%}\n {%- set v = v|round(2)|string -%}\n {%- set comma_sep
= h + \"|\" + s + \"|\" + v -%}\n {%- set data.entries = data.entries + [comma_sep]
-%}\n {%- endif -%}\n{%- endfor -%} {{ data.entries | join(\",\") }}"
color_hsv_list: '{{ color_hsv_list_csv.split(",") }}'
color_count: '{{ color_hsv_list|length }}'
trigger:
- platform: state
entity_id:
- !input light
from: 'off'
to: 'on'
- platform: state
entity_id:
- !input helper_toggle
from: 'off'
to: 'on'
- platform: event
event_type: automation_reloaded
condition:
- condition: and
conditions:
- condition: state
entity_id: !input helper_toggle
state: 'on'
- condition: state
entity_id: !input light
state: 'on'
- '{{ toggle_is_used or trigger.id != "1" }}'
action:
if:
condition: template
value_template: '{{randomizer_value>0}}'
then:
- delay: '{{ (range(256)|random) *transition_seconds*randomizer_value/256 }}'
- alias: Change Color
repeat:
while:
- condition: and
conditions:
- condition: state
entity_id: !input light
state: 'on'
- condition: state
entity_id: !input helper_toggle
state: 'on'
sequence:
- variables:
i_total: '{{ repeat.index }}'
i_cur: '{{ (i_total - 1) % color_count }}'
i_next: '{{ i_total % color_count }}'
hsv_cur_csv: '{{ color_hsv_list[i_cur] }}'
hue_cur: '{{ hsv_cur_csv.split("|")[0] }}'
sat_cur: '{{ (hsv_cur_csv.split("|")[1])|float * 100 }}'
hsv_next_csv: '{{ color_hsv_list[i_next] }}'
hue_next: '{{ hsv_next_csv.split("|")[0] }}'
sat_next: '{{ (hsv_next_csv.split("|")[1])|float * 100 }}'
color_distance: '{{ ((hue_next - hue_cur + 540) % 360) - 180 }}'
color_distance_abs: '{{ color_distance|abs }}'
iterations_desired: "{% if color_distance_abs < max_color_distance %}\n {{
1 }}\n{% else %}\n {{ (color_distance_abs / max_color_distance)|round(0,
'floor') }}\n{% endif %}"
fade_iterations: '{{ min(iterations_desired, (max_changes_per_second * transition_seconds))
}}'
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: and
conditions:
- condition: state
entity_id: !input light
state: 'on'
- condition: state
entity_id: !input helper_toggle
state: 'on'
- variables:
lights_currently_on: '{{ expand(color_loop_light) | selectattr(''domain'',
''eq'', ''light'') | selectattr(''state'', ''eq'', ''on'') | map(attribute=''entity_id'')
| list }}'
t_total: '{{ repeat.index }}'
t_cur: '{{ (t_total - 1) % fade_iterations }}'
transition_time_length: '{{ transition_seconds / fade_iterations }}'
transition_time_start: '{{ as_timestamp(utcnow()) }}'
transition_time_end: '{{ as_timestamp(utcnow()) + transition_time_length
}}'
transition_hue_calc: '{{ hue_cur + (hue_step * t_cur)|round(2) }}'
transition_hue: "{% if transition_hue_calc > 360 %}\n {{ transition_hue_calc
- 360 }}\n{% elif transition_hue_calc < 0 %}\n {{ transition_hue_calc
+ 360 }}\n{% else %}\n {{ transition_hue_calc }}\n{% endif %}"
transition_sat: '{{ min(max((sat_cur + (sat_step * t_cur))|round(2), 0),
100) }}'
- service: light.turn_on
target:
entity_id: '{{ lights_currently_on }}'
data:
hs_color: '{{ [transition_hue, transition_sat] }}'
transition: '{{ transition_time_length }}'
- if:
- condition: or
conditions:
- '{{ toggle_is_used == false }}'
- '{{ transition_time_length <= toggle_transition_check_time }}'
then:
- delay: '{{ transition_time_length }}'
else:
- alias: Check to see if toggle is turned off during transition.
repeat:
until:
- condition: or
conditions:
- '{{ as_timestamp(utcnow()) >= transition_time_end }}'
- condition: state
entity_id: !input light
state: 'off'
sequence:
- if:
- condition: state
entity_id: !input helper_toggle
state: 'off'
then:
- service: light.turn_on
target:
entity_id: '{{ lights_currently_on }}'
data:
hs_color: '{{ state_attr(color_loop_light, ''hs_color'') }}'
transition: 0
- stop: Stopped because the toggle was switched off
else:
- variables:
delay: "{% set time_now = as_timestamp(utcnow()) %} {% if (time_now
+ toggle_transition_check_time) > transition_time_end %}\n {{
max(0, (transition_time_end - time_now)) }}\n{% else %}\n {{
toggle_transition_check_time }}\n{% endif %}\n"
- delay: '{{ delay }}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment