Light Fader by Transition Time

Taking inspiration from this post by Finny Samuel (Light Fader - Quick and Dirty), I wanted to create a script that would operate on the concept of specifying total transition time, and then have the script calculate the number and size of the incremental brightness changes required to arrive at the target brightness in the specified number of seconds.

The script requires entity_id, start_level_pct ( optional - assumes current brightness if omitted), end_level_pct, and transition. It then determines the sleep_delay and step_pct and iterates through a loop that looks very much like the one posted by Finny :grinning:!

The script will do either a fade in or a fade out (i.e. negative step_pct).

This Python script requires the Python Script component.

Hope some find this useful.

NOTES:

  • First time ever posting code … feedback appreciated

  • I found my light system will only permit values to be set as even %'s (values in increments of 2.55, rounded down). That is why I had to work with brightness_pct.

  • This code works with a Lutron Hub (basic consumer version, not pro).

Light Fader

## INPUTS
entity_id  = data.get('entity_id')
states = hass.states.get(entity_id)
current_level = states.attributes.get('brightness') or 0
current_level_pct = current_level/2.55 or 0     # start_level_pct is optional
start_level_pct = int(data.get('start_level_pct', current_level_pct ))
end_level_pct = int(data.get('end_level_pct'))
transition = data.get('transition')

## CALCULATE PARAMETERS FOR LOOP, BASED ON TRANSITION TIME FOR FADE
transition_secs = (int(transition[:2])*3600 + int(transition[3:5])*60
                   + int(transition[-2:]))    # convert string to total secs'
step_pct  = 1
sleep_delay = abs(transition_secs/(end_level_pct - start_level_pct))

# If fading out the step_pct will be negative (decrement each iteration)

if end_level_pct < start_level_pct: step_pct = step_pct * -1


## DOES THE WORK ...

# Since we check for equality of new_level_pct and current_level_pct
# in each loop -  and break if !=, we must initialize new_level_pct
# to equal start_level_pct, and then set actual light brightness_pct
# (a.k.a. current_level_pct) to equal start_level_pct.

new_level_pct = start_level_pct
data = { "entity_id" : entity_id, "brightness_pct" : round(start_level_pct) }
hass.services.call('light', 'turn_on', data)
time.sleep(1)  # without delay,'hass.states.get' would not get the new value

while round(new_level_pct) != end_level_pct :     ## until we get to new level
    states = hass.states.get(entity_id)           ##  acquire status of entity
    current_level = states.attributes.get('brightness') or 0
    current_level_pct = current_level/2.55 or 0
    if (round(current_level_pct) != round(new_level_pct)):
        logger.info('Exiting Fade In')                ## this indicates manual
        break;                                        ## intervention, so exit
    else :
        new_level_pct = new_level_pct + step_pct
        logger.info('Setting brightness of ' + str(entity_id) + ' from '
          + str(current_level_pct) + ' to ' + str(new_level_pct))
        data = { "entity_id" : entity_id,
                "brightness_pct" : round(new_level_pct) }
        hass.services.call('light', 'turn_on', data)
        time.sleep(sleep_delay)


##  Some test json input for Services in Developer tools
##{
##  "entity_id": "light.your_light_name",
##  "start_level_pct": "0",
##  "end_level_pct": "100",
##  "transition": "00:00:19"
##}
2 Likes

Thank you for this! Works great for my Lutron switches. Didn’t work at all with my rgb strips. Is there any downside to changing step levels more than once per second? The transitions can be really jarring when trying to go from 0 to 100 in 10 seconds.

Glad you found it useful.

I kind of arbitrarily decided that no more than once per second was desirable, but I totally get the lack of “smooth” with the once per second change over a short transition time. I’m not experienced enough with Python or Raspberry Pi computing to know for sure, but I sort of guessed that making, say, 10 service calls per second (as it would be in your 0 to 100 in 10 seconds scenario) might not work out so well. It would make sense that there is probably some practical limit to how small that sleep_delay can be, but I’m honestly not sure what that might be. So short replied made long, that was just a totally untested guess on my part.

I’ve mostly been using this for fades over 5 minutes, where it is really seamless, but if you do some experimenting I’d be curious to hear how it works out. If you comment out the “if” line, and the entire “else” section where the script calculates these parameters you would always get …

    step_pct  = 1
    sleep_delay = abs(transition_secs/(end_level_pct - start_level_pct))

… and then it would try to do a 1% brightness change in whatever fraction of a second sleep_delay turns out to be. If you or someone could determine what that threshold actually should be, it would be easy enough to modify that “if” to be a smaller increment than one second.

Side note: home assistant complains about using “sleep” in python scripts, indicating it might slow home assistant down, but - again not scientific - I haven’t noticed any issue so far.

Nothing seemed to happen if I commented out the “if” line and the “else” section. I messed around with the sleep delay and it works smoothly around 0.1-0.2 but screws up the time for the transition. Setting the transition time higher works as a bandaid to the solution. It’d be great if it could dynamically adjust the service call rate depending on the length of the transition.

Another option if you’re using node red: https://github.com/cflurin/node-red-contrib-dsm/wiki/Fade-in--out

Been busy and just now had time to do the testing with the small change intervals for myself. I had really been more interested in the long duration, gradual, fades, so the short fades didn’t really interest me. It actually seems to work just fine if you call the service more than once per second (incorrect assumption on my part!). Smooths it right out. I removed the logic that limits the brightness changes to no more than once per second in my original post above.

What I tried to suggest above, but maybe not very clearly, was to simply change this …

if transition_secs >= abs((end_level_pct - start_level_pct)):
    # This is a case where we change brightness 1% every 'sleep_delay' seconds
    step_pct  = 1
    sleep_delay = abs(transition_secs/(end_level_pct - start_level_pct))
else:
    # In this case we change 'step_pct' % every 1 seconds
    step_pct = abs(((end_level_pct - start_level_pct)/
               transition_secs))
    sleep_delay = 1

… to this:

step_pct  = 1
sleep_delay = abs(transition_secs/(end_level_pct - start_level_pct))

When I did that, and tried 0 to 100 in 10 seconds, it looked pretty good using a bulb with smooth transitions.

I couldn’t get your newer version to work. When I tried to transition from 0-100, it would light up at 1% but just stays there.

Where is the appropriate place for this code - in the automation section?

The code shown here is a python script. The discussion above is about how to alter the script.

If you read the below link, it explains how to setup the python script integration. Once done, you can copy and paste the above code to a text file and save to a .py file in the appropriate location. In my case, I named the file light_fader.py, and saved it to config/python_scripts.

I will caution that it may or may not work well with your particular light entities. In my case, I am using Lutron Caseta, and this script works just fine. But no harm in trying either way.

Once setup, and having restarted HASS, you could call the python script as a service in an Automation or Script. Setting the parameters you want for end_level_pct and transition time, the lights should dim or brighten, depending on the current brightness percentage.

Here is an example. NOTE: the service name will come from your python script name, so look in Developer Tools >> Services for the appropriate "python_script … " name in your installation.

# Gradually fade from current, to these brightnesses
- service: python_script.light_fader
  data:
    entity_id: light.kitchen_table
    end_level_pct: 12
    transition: '00:15:00'
- delay: '00:00:02'
- service: python_script.light_fader
  data:
    entity_id: light.front_foyer_foyer_star
    end_level_pct: 28
    transition: '00:15:00'

thank you so much - that is really helpful reply!

I got it working by adding this to my automation…thanks again!

#automation in my configuration.yaml
  - alias: "Media player playing"
    trigger:
      - platform: state
        entity_id: media_player.appletv
        from: 'paused'
        to: 'playing'
        for:
          seconds: 1
    condition:
      - condition: state
        entity_id: sun.sun
        state: 'below_horizon'
    action:
      service: python_script.light_fader
      data:
        entity_id: light.basement_lights
        end_level_pct: 0
        transition: '00:00:15'

I am now seeing an error in my home assistant log:

Any insight would be appreciated. Thanks!

core-ssh:/config# tail home-assistant.log 
    exec(compiled.code, restricted_globals, local)
  File "light_fader.py", line 14, in <module>
ZeroDivisionError: division by zero
2019-12-10 23:32:31 ERROR (SyncWorker_19) [homeassistant.components.python_script.light_fader.py] Error executing script: division by zero
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/python_script/__init__.py", line 195, in execute
    exec(compiled.code, restricted_globals, local)
  File "light_fader.py", line 14, in <module>
ZeroDivisionError: division by zero

Wild guess: start pct = end pct? Would explain the “divide by zero”

I am fighting this error with a simple call service try. I use Milights, simple python scripts and all else works though.

Error executing script: 'int' object is not subscriptable
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/python_script/__init__.py", line 195, in execute
    exec(compiled.code, restricted_globals, local)
  File "light_fader.py", line 12, in <module>
  File "/usr/local/lib/python3.7/site-packages/RestrictedPython/Eval.py", line 35, in default_guarded_getitem
    return ob[index]
TypeError: 'int' object is not subscriptable

Another day of trying, I dont really get it to work, but at least I have a new error :-X
Some more infos:

  • Normal transitions work fine in HA
  • I built an esp8266_milight_hub and integrated the bulbs via MQTT Autodiscovery. Transitions sthould also work regarding the documentation, but as “non-integer values are supported”, I wonder if this could be the cause of the error.
  • As I also tried setting different (100-500) values at the hubs "Default transition period (milliseconds)" setting.
  • Without further coding skills I would guess that the scripts wants an int but gets an float… or sth. like this.

I will try a manual MQTT setup later, but maybe you guys may have a clue whats wrong here.
Thanks
Kelvin

Error executing script: 'NoneType' object has no attribute 'lower'
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/python_script/__init__.py", line 195, in execute
    exec(compiled.code, restricted_globals, local)
  File "light_fader.py", line 3, in <module>
  File "/usr/src/homeassistant/homeassistant/core.py", line 887, in get
    return self._states.get(entity_id.lower())
AttributeError: 'NoneType' object has no attribute 'lower'

Thank you to Frank and Finny for creating the python code. My house is about 90% Lutron Caseta, and this has been a huge help in creating easy to manage transitions.

@mrsnyds Thanks for sharing this awesome script, works great!

Glad you found it useful. Kudos to Finny Samuel for the original post that gave inspiration to the structure of the routine (Light Fader - Quick and Dirty ).

could you change this script to use it for volume fading on a media player? It is a feature i have been looking for for a long time. Tried some things but did not get it working. I was thinking about swapping all the light services and attributes for music_player services and attributes.

I expect that you could, and the concept would be the same, but the values that you set in each iteration would be specific to whatever scale fits the device you are controlling (i.e. 1 - 255 works for lights, but it will be something else for your TV or receiver.)