Light fader with curves: linear, exponential and smooth

Hello! I’m really interested on this feature, do you ahve any update on this since then?

Im still working on it.
Because I’m using absolute values opposed to percentages, I have to implement it for every type of lamp differently. I’m testing if the fading is smooth enough when using the percentages. Check back in a few weeks.
Kinds regards,
Ingrid

1 Like

Great script!
Why does the script turn off the light completely when setting the brightness_end to 1? I have to set it to 2 to dim lowest light without turning lamp off.

Feature for future: Would it be possible to add a curve that is more like a sinus or similar, that is slow also at the end and not only in the beginning of the dimming?

Hi Thomas,

The turn off with endpoint 1 is a bug :beetle: thanks for the feedback, I will look into this.
The sinus would be a good addition, maybe the easiest way to address this is to consider it two opposite exp5 formula’s…

New version is coming this weekend.

Kind regards Ingrid

@studioIngrid Just to check, does the above caveat mean that when the light is off, one has to define both brightness_start and brightness_end for the fade to be able to take place?

For instance, if the light were to be off and if I were to call this script while omitting brightness_start but with brightness_end being set to (say) 20, would that do nothing?

PS Just as a very minor thing—it looks like there’s an eensy typo in that caveat (“brighness_start” and “brighness_end” rather than “brightness_start” and “brightness_end”).

Got this working yesterday but with a small caveat.

If I use fade in and the light is off, light will turn on at the latest level it was, then go back down to 0 as I want it. After that fade it works normally.

I tried commenting this part out and editing it like so:

 # GET CURRENT STATE
  states = hass.states.get (entity_id)
  #b_cur = b_initial = states.attributes.get ('brightness') or 0
  b_cur = b_initial = 0
  t_cur = t_initial = states.attributes.get ('color_temp') or 0

But the effect remains.

Great script otherwise!

For me the fade in tests starts with whatever I have set brightness_start to be which is one.

What I am noticing for the fade out tests is that the light physical will turn off completely at the end of the duration, but in HA the light shows still on at a brightness of 1.

update: I may just take it back of what I said about about the fade out issue. I was testing with duration of 1 minute and less. I tried with 3 minute duration and HA shows turn off now.

I created a services.yaml for this script to use the UI selectors.
It would be nice to make color_temp optional as some dimmable lamps don’t have this feature.

#<config>/python_scripts/services.yaml
smooth_fader:
  name: Smooth Fader
  fields:
    entity_id:
      #     entity_id: light.entity ; required
      description: The light that will be turned on.
      example: light.myDimmer
      required: true
      selector:
        entity:
          include_entities:
            - light.myDimmer
    duration:
      #     duration: '00:00:00' ; required, time under 10 seconds is not advised
      description: Duration Time, Time under 10 seconds is not advised.
      example: "00:00:10"
      default: "00:00:10"
      required: true
      selector:
        time:
    brightness_start:
      #     brightness_start: 0-255 ; default: current
      description: Start brightness.
      example: 0
      default: 0
      selector:
        number:
          min: 0
          max: 255
    brightness_end:
      #     brightness_end: 0-255 ; default: current
      description: End brightness.
      example: 255
      default: 255
      selector:
        number:
          min: 0
          max: 255
    brightness_curve:
      #     brightness_curve: 'linear' or 'exp2' or 'exp5' or 'smooth' ; default: exp5
      description: Light brightness curve algorithm
      example: exp5
      default: exp5
      selector:
        select:
          options:
            - linear
            - exp2
            - exp5
            - smooth
    temperature_start:
      #     temperature_start: 154-370 ; default: current
      description: Start temperature
      example: 154
      default: 154
      selector:
        number:
          min: 154
          max: 370
    temperature_end:
      #     temperature_end: 154-370 ; default: current
      description: End temperature
      example: 154
      default: 154
      selector:
        number:
          min: 154
          max: 370
    temperature_curve:
      #     temperature_curve: 'linear' or 'exp2' or 'exp5' or 'smooth' ; default: exp2
      description: Light temperature curve algorithm
      example: exp2
      default: exp2
      selector:
        select:
          options:
            - linear
            - exp2
            - exp5
            - smooth
1 Like

Hi MiniCooper246, could you explain to me how i can use your script? I am interested, Thank you!

nice script, thank you! Would it be a lot of work to add color to it? Say for a sunrise simulation?

The Python Script itself is not made by me, only the services.yaml.
A service.yaml is a declaration File to use the script more easily from the HomeAssistant Web UI.
(service.yaml Docs)

But you have to edit it slightly in this area. it requires a list of light entities you want to be able to control.

selector:
        entity:
          include_entities:
            - light.myDimmer

Color is supported by default (i think) but i could not test it, because my lights don’t have a color feature. So I had to edit the original Script slightly to not bug, because it can’t access the color temp of my lights.

1 Like

Hi Community !
First of all, thank you very much for this script. Im trying to find some complex solution veeeeeeery long time ago, and this is so far the most stable and “smooth” option.
Can you please support me with 2details in this script ?

  • I would like to achieve dimming/fading UP or DOWN for my lights from "current state-so current brightness, temperature, colour)
    However, if i try to run the script WITH defined END BRIGHTNESS, without temperature values, the script throws an error that temperature should be defined. How can i please avoid this, and control only the BRIGHTNESS part (of course with duration and curve) ? With mono lights/strips is not such a big deal (however im using different temperatures during the day) but with RBG is that even worse.

  • Sometimes would be great to stop the script from further execution, if light is turned off or brightness changed manually during script running. Is is please possible somehow ?

I can test nearly immediate any change, if needed.

Thank you very much !

1 Like

Hi all, I am currently updating the script. Working on these changes:

  • add rgb color support
  • breakout for animations duration > 10 sec
  • breakout service to stop all animations
  • wrap in custom integration to call async

Will post when update is ready.

— Ingrid

4 Likes

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

1 Like

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