Light fader with curves: linear, exponential and smooth

Hi @studioIngrid !

Sounds great ! Can i help you with testing maybe to make the changes quicker ?
About the first part i mention, to “start dimming up or down” without brightness start defined, could be some achievements possible please ?

Thank you !

Greetings
Ronnie

@studioIngrid If I might make a request for the script update, might it be possible for the script to detect whether the target lights support changes in color temperature, and if not, to have it omit those commands?

I ask just since I have some Lutron lights that support brightness (but not color temperature), and at the moment, the script doesn’t seem to work for those lights.

(On the other hand, the script works fabulously for my Hue bulbs—which support both brightness and color temperature—so I can confirm that I’ve got the script installed properly, as best as I can tell.)

1 Like

Great idea, as I plan to also include RGB color change, so I will be sure to include this.

2 Likes

Thank you so much for this great script! I’m looking forward to the further improvements you’ve indicated in earlier posts, particularly breaking off if a manual change is detected, and rgb support. Are you planning to add xy_color support as part of rgb?

Thanks @studioIngrid, I’ve not tried your code, but it appears to be exactly what I’m after.

CAVEAT: I’m not a programmer
I may be able to chip in on the colour convesion issue that I want to resolve for another use case. The code below is more of an FYI as I can use your code as is, and noting my caveat, It would take me months to integrate it into yours.

To give context to the code, here’s the sequence of how it came about:

  • I have RGBW lights that can supports many of the variations of color definitions and color temperature
  • I found the Adaptive Lighting integration
  • I stopped using the integration because as it turns out, my fully capable RGBW lights would only go to a max mireds of 370 and the light colours when using RGB mode in adaptive lighting didn’t look correct
  • I then realised I could do my own colour conversion and use it in automations for my lights that uses the Adaptive Lighting colour temp and brightness profiles, but with my version of the colour conversion
  • I found this wikipedia page for an approximation of colour temp in the xy colour space, which I used in a template successfully
  • For various reasons, I wanted it to make it overly complicated and frustrating, so I turned to python and colour science
  • I hit a various home assistant lockdowns and walls, and ended up having to take the conversion code from the colour science library (many types of conversions).
  • I couldn’t get the key component (conversion of colour temp to xy) to work so I mash the colour science code together with the approximation I have above

With that said, this is the gist of how the various code blocks below work:

  • The Adaptive Lighting integration must be installed and when configuring in the UI, this checkbox must be checked: include_config_in_attributes
  • Template sensors are used to store the colour temp from adaptive lighting as the value and the colour conversions as attributes, but start with placeholders for the values
  • When adaptive lighting causes a change to the template sensor value, a generic automation calls the AppDaemon python code for the conversion
  • The App then updates the template sensors’ attributes

Note:

  • This code will throw errors on startup if you try to turn on lights prior to there being a change in the adpative lighting attributes
  • Adaptive lighting isn’t actually required if all you want is the conversion of the colour temp, you’ll just have to change the automation to suit, or take the maths out of the python code and put it in a template sensor
  • If you’re starting with Kelvin, to convert to mireds: mireds = 1,000,000 / Kelvin

I’m happy to answer any questions, but I don’t spend much time on this forum, so my answers will likely be delayed.

Template sensor example (the old school template sensor format is below):

- platform: template
  sensors:
    adaptive_lighting_mireds_bedroom:
      value_template: "{{ state_attr('switch.adaptive_lighting_bedroom', 'color_temp_mired') }}"
      attribute_templates:
        rgb_colour: 0
        hs_colour: 0
        xy_colour: 0
        kelvin: 0
        brightness_pct: "{{ state_attr('switch.adaptive_lighting_bedroom', 'brightness_pct') }}"

Create as many template sensors as you want. The naming is important for the automation, so make sure each sensor name starts with adaptive_lighting_mireds_ and change bedroom to the various unique identifiers of your choice.

Automation:

- alias: Update adaptive lighting mireds conversion sensors
  trigger:
  - platform: event
    event_type: state_changed
  condition:
  - condition: template
    value_template: '{{ trigger.event.data.entity_id.startswith("sensor.adaptive_lighting_mireds_")
      }}'
  action:
  - choose:
    - conditions:
      - condition: template
        value_template: '{{ is_state(trigger.event.data.entity_id, ''unknown'') }}'
      sequence:
      - service: persistent_notification.create
        data:
          title: Adaptive lighting mireds conversion sensor not found
          message: The adaptive lighting mireds conversion sensor for {{ trigger.event.data.entity_id
            }} could not be found. Please create it first.
    default:
    - event: adaptive_lighting_mireds_conversion
      event_data:
        entity_id: '{{ trigger.event.data.entity_id }}'
        value: '{{ trigger.event.data.new_state.state }}'

Apps.yaml

AdaptiveLightingMiredsConversion:
  module: adaptive_lighting_mireds_conversion
  class: AdaptiveLightingMiredsConversion

AppDaemon filename: adaptive_lighting_mireds_conversion.py in a folder of the same name

adaptive_lighting_mireds_conversion.py

import appdaemon.plugins.hass.hassapi as hass
import numpy as np

class AdaptiveLightingMiredsConversion(hass.Hass):

    def initialize(self):
        self.listen_event(self.trigger_conversion,
                            "adaptive_lighting_mireds_conversion")

    def trigger_conversion(self, event_name, data, kwargs):
        if 'entity_id' in data:
            self.sensor_entity_id = data["entity_id"]
            mireds = float(data["value"])
            cct = 1e6 / mireds
            rgb = self.colour_temperature_to_rgb(cct)
            hs = self.rgb_to_hs(rgb)
            xy = self.cct_to_xy(cct)
            kelvin = self.colour_mireds_to_kelvin(cct)
            self.log(f"RGB: {rgb}, HS: {hs}, xy: {xy}, kelvin: {kelvin}")

            # Update the sensor value
            self.set_state(self.sensor_entity_id, state=mireds,
                            attributes={"rgb_colour": rgb, "hs_colour": hs, "xy_colour": xy, "kelvin": kelvin})


    def cct_to_xy(self, cct):
        M = 1e6 / cct
        if cct < 4000:
            x = - 0.2661239 * 1e9 / (cct ** 3) - 0.2343580 * 1e6 / (cct ** 2) + \
                 0.8776956 * 1e3 / cct + 0.179910
        elif cct <= 25000:
            x = - 3.0258469 * 1e9 / (cct ** 3) + 2.1070379 * 1e6 / (cct ** 2) + \
                0.2226347 * 1e3 / cct + 0.240390
        else:
            x = - 0.11026621 * (M ** 3) + 0.0516377 * (M ** 2) + \
                0.075919 * M + 0.206556

        if cct < 2222:
            y = - 1.1063814 * (x ** 3) - 1.34811020 * \
                (x ** 2) + 2.18555832 * x - 0.20219683
        elif 2222 <= cct and cct < 4000:
            y = - 0.9549476 * (x ** 3) - 1.37418593 * (x ** 2) + 2.09137015 * x - 0.16748867
        elif 4000 <= cct and cct < 25000:
            y = 3.0817580 * (x ** 3) - 5.87338670 * \
                (x ** 2) + 3.75112997 * x - 0.37001483
        else: 
            y = 1

        return x, y


    def xyY_to_XYZ(self, xyY):
        x, y, Y = xyY
        X = (Y / y) * x
        Z = (Y / y) * (1 - x - y)
        return X, Y, Z


    def XYZ_to_sRGB(self, XYZ):
        M = np.array([[3.2406, - 1.5372, - 0.4986],
                        [- 0.9689, 1.8758, 0.0415],
                        [0.0557, - 0.2040, 1.0570]])
        linear_rgb = np.dot(M, XYZ)
        return linear_rgb


    def gamma_correct_sRGB(self, linear_rgb):
        gamma_corrected_rgb = np.where(
            linear_rgb <= 0.0031308,
            linear_rgb * 12.92,
            1.055 * (linear_rgb ** (1 / 2.4)) - 0.055)
        return gamma_corrected_rgb


    def colour_temperature_to_rgb(self, cct):
        # Step 1: Convert colour temperature to xy chromaticity coordinates
        xy = self.cct_to_xy(cct)

        # Step 2: Convert xyY to XYZ (assuming Y = 1)
        Y = 1.0
        XYZ = self.xyY_to_XYZ((xy[0], xy[1], Y))

        # Step 3: Convert XYZ to linear sRGB
        linear_rgb = self.XYZ_to_sRGB(XYZ)

        # Step 4: Apply gamma correction
        gamma_corrected_rgb = self.gamma_correct_sRGB(linear_rgb)

        # Clamp the RGB values to the [0, 1]
        # range and convert them to 8 - bit integers
        clamped_rgb = np.clip(gamma_corrected_rgb, 0, 1) * 255

        # Convert the NumPy array to a list
        return clamped_rgb.astype(int).tolist()


    def rgb_to_hs(self, rgb):
        r, g, b = [val / 255.0 for val in rgb]
        max_value = max(r, g, b)
        min_value = min(r, g, b)
        difference = max_value - min_value

        if max_value == min_value:
            h = 0
        elif max_value == r:
            h = (60 * ((g - b) / difference) + 360) % 360
        elif max_value == g:
            h = (60 * ((b - r) / difference) + 120) % 360
        elif max_value == b:
            h = (60 * ((r - g) / difference) + 240) % 360

        if max_value == 0:
            s = 0
        else:
            s = difference / max_value

        return h, s * 100


    def colour_mireds_to_kelvin(self, cct):
        return cct

Hi, sorry for ressucitate this topic, but I got a problem with my light.

I have a single WIFI dimmer that I want to turn it on and off from 0 to 60% in 30 minutes.
It is not RGB.

I installed this script, but when I run, I get this error about color_temp. How can I workarround ?

ERROR (SyncWorker_19) [homeassistant.components.python_script.smooth_fader.py] Error executing script: value must be at least 1 for dictionary value @ data['color_temp']
  File "/usr/src/homeassistant/homeassistant/components/python_script/__init__.py", line 224, in execute
  File "/usr/local/lib/python3.10/concurrent/futures/_base.py", line 451, in result
  File "/usr/local/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 232, in __call__
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 355, in _exec
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 351, in _exec
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 272, in __call__
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 818, in validate_callable
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 272, in __call__
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 229, in _run
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 355, in _exec
  File "/usr/local/lib/python3.10/site-packages/voluptuous/validators.py", line 353, in _exec
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 818, in validate_callable
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 272, in __call__
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 595, in validate_dict
  File "/usr/local/lib/python3.10/site-packages/voluptuous/schema_builder.py", line 433, in validate_mapping

Running with the code :

service: python_script.smooth_fader
data:
  entity_id: light.controller_dimmable_b6af10
  brightness_curve: linear
  brightness_start: 0
  brightness_end: 120
  duration: "00:00:30"```

Hi,

I haven’t worked on this code for a while. And I’m currently busy with other projects.
Did you check out Ashley’s script? Here.
I didn’t test is yet, but looks like it might be what you need.

— Ingrid

1 Like