Using HSV / HSB to set colored lights

Hi,
I thought I’d share my code to use a hue, saturation and brightness value to set colored lights. The standard interface only supports XY or RGB, not HSV/HSB. I wanted to be able to chose a random hue value and set my colored lights using this hue and fixed saturation and brightness levels.

First I created 3 sliders to control the amount of saturation and brightness levels to be used as well as my preferred transition speed. Second, I created a script with template code to set the lights (actually requires 2 scripts).

1. Input sliders

movie_night_saturation:
  name: Color Saturation
  initial: 0.3
  min: 0
  max: 1
  step: 0.05

movie_night_brightness:
  name: Color Brightness
  initial: 0.8
  min: 0
  max: 1
  step: 0.05

movie_night_transition:
  name: Transition
  initial: 5
  min: 0
  max: 30
  step: 1

2. The scripts.
The script converts HSV / HSB to RGB and passes that to the colored lights interface. The rgb_color property requires a list of 3 colors like [178,245,32] and not strings. Using any template code before the [] wil make the value a string and not a list (the template returns a strings, which prevents us to set the rgb_color this way). In another post I provided a workaround of putting the RGB colors in a string and simply calling another script that parses out the values and sets the color of the lights.

Script 1: convert HSV / HSB to RGB

movie_night_color:
  sequence:
    - service: script.set_color
      data_template:
        id: group.living_room_colored_lights
        colors: >-
          {%- set h = (range(0, 360)|random)/360 %}
          {%- set s = states.input_slider.movie_night_saturation.state|float %}
          {%- set v = states.input_slider.movie_night_brightness.state|float %}

          {%- set i = (h * 6)|int %}
          {%- set f = h * 6 - i %}
          {%- set p = v * (1 - s) %}
          {%- set q = v * (1 - f * s) %}
          {%- set t = v * (1 - (1 - f) * s) %}

          {%- if i % 6 == 0 %}
            {% set r = v %}
            {% set g = t %}
            {% set b = p %}
          {%- elif i % 6 == 1 %}
            {% set r = q %}
            {% set g = v %}
            {% set b = p %}
          {%- elif i % 6 == 2 %}
            {% set r = p %}
            {% set g = v %}
            {% set b = t %}
          {%- elif i % 6 == 3 %}
            {% set r = p %}
            {% set g = q %}
            {% set b = v %}
          {%- elif i % 6 == 4 %}
            {% set r = t %}
            {% set g = p %}
            {% set b = v %}
          {%- elif i % 6 == 5 %}
            {% set r = v %}
            {% set g = p %}
            {% set b = q %}
          {%- endif %}

          {{ (255*r)|round(0) }}, {{ (255*g)|round(0) }}, {{ (255*b)|round(0) }}, {{ (360*h)|int }}

        transition: "{{ states.input_slider.movie_night_transition.state|int }}"

(code was modelled after this code)

You notice that this script calls another script (script.set_color) using a parameter called colors (and id and transition for flexibility).

Script 2: setting the lights to color

set_color:
  sequence:
    - service: light.turn_on
      data_template:
        entity_id: '{{ id }}'
        rgb_color: [ "{{ colors.split(',')[0]|int }}", "{{ colors.split(',')[1]|int }}", "{{ colors.split(',')[2]|int }}" ]
        transition: "{{ transition }}"

This script takes the parameter colors and for each RGB value it extracts the value from the string. Because the value of property rgb_color still starts with a [, it remains a list of values that is being passed to the interface.

I know that using XYZ (CIE 1931) is the proper way to go, but requires somewhat complex math to pick a random color and even crazier math to fix the saturation. This was much easier and works like a charm for my use case, and perhaps someone else’s.

4 Likes